#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl::test { static Bytes toBytes(std::uint8_t value) { return {value}; } 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(uint256 const& value) { return Bytes{value.begin(), value.end()}; } static Bytes toBytes(Issue const& issue) { Serializer s; s.addBitString(issue.currency); if (!isXRP(issue.currency)) s.addBitString(issue.account); auto const data = s.getData(); return data; } static Bytes toBytes(Asset const& asset) { if (asset.holds()) return toBytes(asset.get()); 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 Bytes toBytes(STNumber const& number) { Serializer msg; number.add(msg); auto const data = msg.getData(); return data; } static ApplyContext createApplyContext( test::jtx::Env& env, OpenView& ov, beast::Journal j, STTx const& tx = STTx(ttESCROW_FINISH, [](STObject&) {})) { ApplyContext ac{env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, j}; return ac; } static ApplyContext createApplyContext( test::jtx::Env& env, OpenView& ov, STTx const& tx = STTx(ttESCROW_FINISH, [](STObject&) {})) { return createApplyContext(env, ov, env.journal, tx); } class VirtualRuntime : public WasmRuntimeWrapper { Bytes buffer_; std::int64_t gas_ = 1'000'000; public: VirtualRuntime() : buffer_(1024 * 1024) { } Wmem getMem() override { return {.p = buffer_.data(), .s = buffer_.size()}; } std::int64_t getGas() override { gas_ -= 100; return gas_; } std::int64_t setGas(std::int64_t gas) override { if (gas == -2) return -1; if (gas < 0) { gas_ = std::numeric_limits::max(); } else { gas_ = gas; } return gas_; } void checkIdx(WasmValVec const& params, size_t i) const { if (i + 1 >= params.size()) Throw("Out of bounds"); if (params[i].kind != WASM_I32 || params[i + 1].kind != WASM_I32) Throw("Invalid params"); std::int32_t const ptr = params[i].of.i32; std::int32_t const size = params[i + 1].of.i32; std::int64_t const offset = (std::int64_t)ptr + size; if (ptr < 0 || size < 0 || std::cmp_greater_equal(offset, buffer_.size())) Throw("Out of bounds"); } [[nodiscard]] Slice getBuffer(WasmValVec const& params, size_t i) const { checkIdx(params, i); std::int32_t const ptr = params[i].of.i32; std::int32_t const size = params[i + 1].of.i32; return {&buffer_[ptr], static_cast(size)}; } [[nodiscard]] Bytes getBytes(WasmValVec const& params, size_t i) const { checkIdx(params, i); std::int32_t const ptr = params[i].of.i32; std::int32_t const size = params[i + 1].of.i32; return {&buffer_[ptr], &buffer_[ptr + size]}; } void setBytes(size_t ptr, void const* bytes, size_t size) { if (ptr + size >= buffer_.size()) Throw("Out of bounds"); memcpy(&buffer_[ptr], bytes, size); } template [[nodiscard]] [[nodiscard]] [[nodiscard]] [[nodiscard]] T getInt(WasmValVec const& params, size_t i) const { checkIdx(params, i); std::int32_t const ptr = params[i].of.i32; std::int32_t const size = params[i + 1].of.i32; if (size != sizeof(T)) Throw("Invalid size"); return *reinterpret_cast(&buffer_[ptr]); } [[nodiscard]] std::int32_t getInt32(WasmValVec const& params, size_t i) const { return getInt(params, i); } [[nodiscard]] std::uint32_t getUint32(WasmValVec const& params, size_t i) const { return getInt(params, i); } [[nodiscard]] std::int64_t getInt64(WasmValVec const& params, size_t i) const { return getInt(params, i); } [[nodiscard]] std::uint64_t getUint64(WasmValVec const& params, size_t i) const { return getInt(params, i); } }; template void ww_hlp(size_t& idx, E&& e, P&& params, Arg&& arg) { if constexpr (std::is_integral_v) { params[idx++] = std::is_same_v || std::is_same_v ? wasm_val_t WASM_I64_VAL(static_cast(arg)) : wasm_val_t WASM_I32_VAL(static_cast(arg)); } else if constexpr (std::is_same_v) { auto const* udata = reinterpret_cast(e); HostFunctions const* hf = reinterpret_cast(udata->first); auto* vrt = reinterpret_cast(hf->getRT()); auto const data = toBytes(std::forward(arg)); size_t const ptr = (idx << 10); vrt->setBytes(ptr, data.data(), data.size()); params[idx++] = wasm_val_t WASM_I32_VAL(static_cast(ptr)); params[idx++] = wasm_val_t WASM_I32_VAL(static_cast(data.size())); } else { auto const* udata = reinterpret_cast(e); HostFunctions const* hf = reinterpret_cast(udata->first); auto* vrt = reinterpret_cast(hf->getRT()); size_t const ptr = (idx << 10); vrt->setBytes(ptr, arg.data(), arg.size()); params[idx++] = wasm_val_t WASM_I32_VAL(static_cast(ptr)); params[idx++] = wasm_val_t WASM_I32_VAL(static_cast(arg.size())); } } // Helper wrapper to call WASM wrapper functions with automatic parameter packing template wasm_trap_t* ww(F&& f, E&& e, P&& params, P&& result, Args... args) { size_t idx = 0; (ww_hlp(idx, e, params, std::forward(args)), ...); // NOLINT return f(std::forward(e), params.get(), result.get()); // NOLINT } constexpr int64_t min64 = std::numeric_limits::min(); constexpr int64_t max64 = std::numeric_limits::max(); constexpr int32_t floatSize = 12; struct HostFuncImpl_test : public beast::unit_test::Suite { void testGetLedgerSqn() { testcase("getLedgerSqn"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.getLedgerSqn(); WasmValVec params(2), result(1); auto* trap = ww(getLedgerSqn_wrap, &import.at("get_ledger_sqn"), params, result, 0, sizeof(std::uint32_t)); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == sizeof(std::uint32_t)) && BEAST_EXPECT(vrt.getUint32(params, 0) == env.current()->header().seq); } } void testGetParentLedgerTime() { testcase("getParentLedgerTime"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.getParentLedgerTime(); WasmValVec params(2), result(1); auto* trap = ww(getParentLedgerTime_wrap, &import.at("get_parent_ledger_time"), params, result, 0, sizeof(std::uint32_t)); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == sizeof(std::uint32_t)) && BEAST_EXPECT( vrt.getUint32(params, 0) == env.current()->parentCloseTime().time_since_epoch().count()); } } void testGetParentLedgerHash() { testcase("getParentLedgerHash"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.getParentLedgerHash(); WasmValVec params(2), result(1); auto* trap = ww(getParentLedgerHash_wrap, &import.at("get_parent_ledger_hash"), params, result, 0, uint256::size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == uint256::size()); auto const resultBytes = vrt.getBytes(params, 0); auto const expectedHash = env.current()->header().parentHash; BEAST_EXPECT( resultBytes.size() == uint256::size() && std::memcmp(resultBytes.data(), expectedHash.data(), uint256::size()) == 0); } } void testGetBaseFee() { testcase("getBaseFee"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // hfs.getBaseFee(); { WasmValVec params(2), result(1); auto* trap = ww(getBaseFee_wrap, &import.at("get_base_fee"), params, result, 0, sizeof(std::uint32_t)); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == sizeof(std::uint32_t)) && BEAST_EXPECT(vrt.getUint32(params, 0) == env.current()->fees().base.drops()); } } void testIsAmendmentEnabled() { testcase("isAmendmentEnabled"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // Use featureTokenEscrow for testing auto const amendmentId = featureTokenEscrow; // hfs.isAmendmentEnabled(amendmentId); { WasmValVec params(2), result(1); vrt.setBytes(0, amendmentId.data(), uint256::size()); auto* trap = ww(isAmendmentEnabled_wrap, &import.at("amendment_enabled"), params, result, 0, uint256::size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 1); } std::string const amendmentName = "TokenEscrow"; // hfs.isAmendmentEnabled(amendmentName); { WasmValVec params(2), result(1); vrt.setBytes(0, amendmentName.data(), amendmentName.size()); auto* trap = ww(isAmendmentEnabled_wrap, &import.at("amendment_enabled"), params, result, 0, amendmentName.size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 1); } uint256 const fakeId; // hfs.isAmendmentEnabled(fakeId); { WasmValVec params(2), result(1); vrt.setBytes(0, fakeId.data(), uint256::size()); auto* trap = ww(isAmendmentEnabled_wrap, &import.at("amendment_enabled"), params, result, 0, uint256::size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0); } std::string const fakeName = "FakeAmendment"; // hfs.isAmendmentEnabled(fakeName); { WasmValVec params(2), result(1); vrt.setBytes(0, fakeName.data(), fakeName.size()); auto* trap = ww(isAmendmentEnabled_wrap, &import.at("amendment_enabled"), params, result, 0, fakeName.size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0); } } void testCacheLedgerObj() { testcase("cacheLedgerObj"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, 2); auto const accountKeylet = keylet::account(env.master); { VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // hfs.cacheLedgerObj(accountKeylet.key, -1); { WasmValVec params(3), result(1); vrt.setBytes(0, accountKeylet.key.data(), uint256::size()); auto* trap = ww(cacheLedgerObj_wrap, &import.at("cache_ledger_obj"), params, result, 0, uint256::size(), -1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::SlotOutRange)); } // hfs.cacheLedgerObj(accountKeylet.key, 257); { WasmValVec params(3), result(1); vrt.setBytes(0, accountKeylet.key.data(), uint256::size()); auto* trap = ww(cacheLedgerObj_wrap, &import.at("cache_ledger_obj"), params, result, 0, uint256::size(), 257); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::SlotOutRange)); } // hfs.cacheLedgerObj(dummyEscrow.key, 0); { WasmValVec params(3), result(1); vrt.setBytes(0, dummyEscrow.key.data(), uint256::size()); auto* trap = ww(cacheLedgerObj_wrap, &import.at("cache_ledger_obj"), params, result, 0, uint256::size(), 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::LedgerObjNotFound)); } // hfs.cacheLedgerObj(accountKeylet.key, 0); { WasmValVec params(3), result(1); vrt.setBytes(0, accountKeylet.key.data(), uint256::size()); auto* trap = ww(cacheLedgerObj_wrap, &import.at("cache_ledger_obj"), params, result, 0, uint256::size(), 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 1); } vrt.setGas(2'000'000); for (int i = 1; i <= 256; ++i) { // hfs.cacheLedgerObj(accountKeylet.key, i); WasmValVec params(3), result(1); vrt.setBytes(0, accountKeylet.key.data(), uint256::size()); auto* trap = ww(cacheLedgerObj_wrap, &import.at("cache_ledger_obj"), params, result, 0, uint256::size(), i); if (!(BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECTS( result[0].of.i32 == i, "result: " + std::to_string(result[0].of.i32) + ", expected: " + std::to_string(i)))) break; } // hfs.cacheLedgerObj(accountKeylet.key, 0); { WasmValVec params(3), result(1); vrt.setBytes(0, accountKeylet.key.data(), uint256::size()); auto* trap = ww(cacheLedgerObj_wrap, &import.at("cache_ledger_obj"), params, result, 0, uint256::size(), 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::SlotsFull)); } } { VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); vrt.setGas(2'000'000); for (int i = 1; i <= 256; ++i) { // hfs.cacheLedgerObj(accountKeylet.key, 0); WasmValVec params(3), result(1); vrt.setBytes(0, accountKeylet.key.data(), uint256::size()); auto* trap = ww(cacheLedgerObj_wrap, &import.at("cache_ledger_obj"), params, result, 0, uint256::size(), 0); if (!(BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECTS( result[0].of.i32 == i, "result: " + std::to_string(result[0].of.i32) + ", expected: " + std::to_string(i)))) break; } // hfs.cacheLedgerObj(accountKeylet.key, 0); { WasmValVec params(3), result(1); vrt.setBytes(0, accountKeylet.key.data(), uint256::size()); auto* trap = ww(cacheLedgerObj_wrap, &import.at("cache_ledger_obj"), params, result, 0, uint256::size(), 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::SlotsFull)); } } } void testGetTxField() { testcase("getTxField"); using namespace test::jtx; std::string const credIdHex = "0011223344556677889900112233445566778899001122334455667788990011"; uint256 credId; BEAST_EXPECT(credId.parseHex(credIdHex)); Env env{*this}; OpenView ov{*env.current()}; STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) { obj.setAccountID(sfAccount, env.master.id()); obj.setAccountID(sfOwner, env.master.id()); obj.setFieldU32(sfOfferSequence, env.seq(env.master)); obj.setFieldArray(sfMemos, STArray{}); STVector256 credIds; credIds.pushBack(credId); obj.setFieldV256(sfCredentialIDs, credIds); }); ApplyContext ac = createApplyContext(env, ov, stx); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); { VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // hfs.getTxField(sfAccount); { WasmValVec params(3), result(1); auto* trap = ww(getTxField_wrap, &import.at("get_tx_field"), params, result, sfAccount.getCode(), 0, AccountID::size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == AccountID::size()); auto const accountBytes = vrt.getBytes(params, 1); BEAST_EXPECT(std::ranges::equal(accountBytes, env.master.id())); } // hfs.getTxField(sfOwner); { WasmValVec params(3), result(1); auto* trap = ww(getTxField_wrap, &import.at("get_tx_field"), params, result, sfOwner.getCode(), 0, AccountID::size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == AccountID::size()); auto const ownerBytes = vrt.getBytes(params, 1); BEAST_EXPECT(std::ranges::equal(ownerBytes, env.master.id())); } // hfs.getTxField(sfTransactionType); { WasmValVec params(3), result(1); auto* trap = ww(getTxField_wrap, &import.at("get_tx_field"), params, result, sfTransactionType.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 > 0); auto txTypeBytes = vrt.getBytes(params, 1); txTypeBytes.resize(result[0].of.i32); BEAST_EXPECT(txTypeBytes == toBytes(ttESCROW_FINISH)); } // hfs.getTxField(sfOfferSequence); { WasmValVec params(3), result(1); auto* trap = ww(getTxField_wrap, &import.at("get_tx_field"), params, result, sfOfferSequence.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 > 0); auto offerSeqBytes = vrt.getBytes(params, 1); offerSeqBytes.resize(result[0].of.i32); BEAST_EXPECT(offerSeqBytes == toBytes(env.seq(env.master))); } // hfs.getTxField(sfDestination); { WasmValVec params(3), result(1); auto* trap = ww(getTxField_wrap, &import.at("get_tx_field"), params, result, sfDestination.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FieldNotFound)); } // hfs.getTxField(sfMemos); { WasmValVec params(3), result(1); auto* trap = ww(getTxField_wrap, &import.at("get_tx_field"), params, result, sfMemos.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::NotLeafField)); } // hfs.getTxField(sfCredentialIDs); { WasmValVec params(3), result(1); auto* trap = ww(getTxField_wrap, &import.at("get_tx_field"), params, result, sfCredentialIDs.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECTS( result[0].of.i32 == static_cast(HostFunctionError::NotLeafField), std::to_string(result[0].of.i32)); } // hfs.getTxField(kSfInvalid); { WasmValVec params(3), result(1); auto* trap = ww(getTxField_wrap, &import.at("get_tx_field"), params, result, kSfInvalid.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FieldNotFound)); } // hfs.getTxField(kSfGeneric); { WasmValVec params(3), result(1); auto* trap = ww(getTxField_wrap, &import.at("get_tx_field"), params, result, kSfGeneric.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FieldNotFound)); } } { auto const iouAsset = env.master["USD"]; STTx const stx2 = STTx(ttAMM_DEPOSIT, [&](auto& obj) { obj.setAccountID(sfAccount, env.master.id()); obj.setFieldIssue(sfAsset, STIssue{sfAsset, xrpIssue()}); obj.setFieldIssue(sfAsset2, STIssue{sfAsset2, iouAsset.issue()}); }); ApplyContext ac2 = createApplyContext(env, ov, stx2); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac2, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // hfs.getTxField(sfAsset); { WasmValVec params(3), result(1); auto* trap = ww(getTxField_wrap, &import.at("get_tx_field"), params, result, sfAsset.getCode(), 0, 256); std::vector const expectedAsset(20, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 > 0); auto assetBytes = vrt.getBytes(params, 1); assetBytes.resize(result[0].of.i32); BEAST_EXPECT(assetBytes == expectedAsset); } // hfs.getTxField(sfAsset2); { WasmValVec params(3), result(1); auto* trap = ww(getTxField_wrap, &import.at("get_tx_field"), params, result, sfAsset2.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 > 0); auto asset2Bytes = vrt.getBytes(params, 1); asset2Bytes.resize(result[0].of.i32); BEAST_EXPECT(asset2Bytes == toBytes(Asset(iouAsset))); } } { auto const iouAsset = env.master["GBP"]; auto const mptId = makeMptID(1, env.master); STTx const stx2 = STTx(ttAMM_DEPOSIT, [&](auto& obj) { obj.setAccountID(sfAccount, env.master.id()); obj.setFieldIssue(sfAsset, STIssue{sfAsset, iouAsset.issue()}); obj.setFieldIssue(sfAsset2, STIssue{sfAsset2, MPTIssue{mptId}}); }); ApplyContext ac2 = createApplyContext(env, ov, stx2); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac2, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // hfs.getTxField(sfAsset); { WasmValVec params(3), result(1); auto* trap = ww(getTxField_wrap, &import.at("get_tx_field"), params, result, sfAsset.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECT(result[0].of.i32 > 0)) { auto assetBytes = vrt.getBytes(params, 1); assetBytes.resize(result[0].of.i32); BEAST_EXPECT(assetBytes == toBytes(Asset(iouAsset))); } } // hfs.getTxField(sfAsset2); { WasmValVec params(3), result(1); auto* trap = ww(getTxField_wrap, &import.at("get_tx_field"), params, result, sfAsset2.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECT(result[0].of.i32 > 0)) { auto assetBytes = vrt.getBytes(params, 1); assetBytes.resize(result[0].of.i32); BEAST_EXPECT(assetBytes == toBytes(Asset(mptId))); } } } { std::uint8_t const expectedScale = 8; STTx const stx2 = STTx(ttMPTOKEN_ISSUANCE_CREATE, [&](auto& obj) { obj.setAccountID(sfAccount, env.master.id()); obj.setFieldU8(sfAssetScale, expectedScale); }); ApplyContext ac2 = createApplyContext(env, ov, stx2); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac2, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // hfs.getTxField(sfAssetScale); { WasmValVec params(3), result(1); auto* trap = ww(getTxField_wrap, &import.at("get_tx_field"), params, result, sfAssetScale.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECT(result[0].of.i32 > 0)) { auto assetBytes = vrt.getBytes(params, 1); assetBytes.resize(result[0].of.i32); BEAST_EXPECT(std::ranges::equal(assetBytes, toBytes(expectedScale))); } } } } void testGetCurrentLedgerObjField() { testcase("getCurrentLedgerObjField"); using namespace test::jtx; using namespace std::chrono; Env env{*this}; // Fund the account and create an escrow so the ledger object exists env(escrow::create(env.master, env.master, XRP(100)), escrow::kFinishTime(env.now() + 1s)); env.close(); OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); // Find the escrow ledger object auto const escrowKeylet = keylet::escrow(env.master, env.seq(env.master) - 1); BEAST_EXPECT(env.le(escrowKeylet)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, escrowKeylet); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // hfs.getCurrentLedgerObjField(sfAccount); { WasmValVec params(3), result(1); auto* trap = ww(getCurrentLedgerObjField_wrap, &import.at("get_current_ledger_obj_field"), params, result, sfAccount.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) { auto accountBytes = vrt.getBytes(params, 1); accountBytes.resize(result[0].of.i32); BEAST_EXPECT(std::ranges::equal(accountBytes, env.master.id())); } } // hfs.getCurrentLedgerObjField(sfAmount); { WasmValVec params(3), result(1); auto* trap = ww(getCurrentLedgerObjField_wrap, &import.at("get_current_ledger_obj_field"), params, result, sfAmount.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECT(result[0].of.i32 > 0)) { auto amountBytes = vrt.getBytes(params, 1); amountBytes.resize(result[0].of.i32); BEAST_EXPECT(amountBytes == toBytes(XRP(100))); } } // hfs.getCurrentLedgerObjField(sfPreviousTxnID); { WasmValVec params(3), result(1); auto* trap = ww(getCurrentLedgerObjField_wrap, &import.at("get_current_ledger_obj_field"), params, result, sfPreviousTxnID.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECT(result[0].of.i32 > 0)) { auto previousTxnIdBytes = vrt.getBytes(params, 1); previousTxnIdBytes.resize(result[0].of.i32); BEAST_EXPECT(previousTxnIdBytes == toBytes(env.tx()->getTransactionID())); } } // hfs.getCurrentLedgerObjField(sfOwner); { WasmValVec params(3), result(1); auto* trap = ww(getCurrentLedgerObjField_wrap, &import.at("get_current_ledger_obj_field"), params, result, sfOwner.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FieldNotFound)); } { auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master) + 5); VirtualRuntime vrt2; WasmHostFunctionsImpl hfs2(ac, dummyEscrow); auto import2 = xrpl::createWasmImport(hfs2); hfs2.setRT(&vrt2); // hfs2.getCurrentLedgerObjField(sfAccount); { WasmValVec params(3), result(1); auto* trap = ww(getCurrentLedgerObjField_wrap, &import2.at("get_current_ledger_obj_field"), params, result, sfAccount.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::LedgerObjNotFound)); } } } void testGetLedgerObjField() { testcase("getLedgerObjField"); using namespace test::jtx; using namespace std::chrono; Env env{*this}; // Fund the account and create an escrow so the ledger object exists env(escrow::create(env.master, env.master, XRP(100)), escrow::kFinishTime(env.now() + 1s)); env.close(); OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const accountKeylet = keylet::account(env.master.id()); auto const escrowKeylet = keylet::escrow(env.master.id(), env.seq(env.master) - 1); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, escrowKeylet); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // hfs.cacheLedgerObj(accountKeylet.key, 1); { WasmValVec params(3), result(1); vrt.setBytes(0, accountKeylet.key.data(), uint256::size()); auto* trap = ww(cacheLedgerObj_wrap, &import.at("cache_ledger_obj"), params, result, 0, uint256::size(), 1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 1); } // hfs.getLedgerObjField(1, sfAccount); { WasmValVec params(4), result(1); auto* trap = ww(getLedgerObjField_wrap, &import.at("get_ledger_obj_field"), params, result, 1, sfAccount.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) { auto accountBytes = vrt.getBytes(params, 2); accountBytes.resize(result[0].of.i32); BEAST_EXPECT(std::ranges::equal(accountBytes, env.master.id())); } } // hfs.getLedgerObjField(1, sfBalance); { WasmValVec params(4), result(1); auto* trap = ww(getLedgerObjField_wrap, &import.at("get_ledger_obj_field"), params, result, 1, sfBalance.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECT(result[0].of.i32 > 0)) { auto balanceBytes = vrt.getBytes(params, 2); balanceBytes.resize(result[0].of.i32); BEAST_EXPECT(balanceBytes == toBytes(env.balance(env.master))); } } // hfs.getLedgerObjField(0, sfAccount); { WasmValVec params(4), result(1); auto* trap = ww(getLedgerObjField_wrap, &import.at("get_ledger_obj_field"), params, result, 0, sfAccount.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::SlotOutRange)); } // hfs.getLedgerObjField(257, sfAccount); { WasmValVec params(4), result(1); auto* trap = ww(getLedgerObjField_wrap, &import.at("get_ledger_obj_field"), params, result, 257, sfAccount.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::SlotOutRange)); } // hfs.getLedgerObjField(2, sfAccount); { WasmValVec params(4), result(1); auto* trap = ww(getLedgerObjField_wrap, &import.at("get_ledger_obj_field"), params, result, 2, sfAccount.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::EmptySlot)); } // hfs.getLedgerObjField(1, sfOwner); { WasmValVec params(4), result(1); auto* trap = ww(getLedgerObjField_wrap, &import.at("get_ledger_obj_field"), params, result, 1, sfOwner.getCode(), 0, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FieldNotFound)); } } void testGetTxNestedField() { testcase("getTxNestedField"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; std::string const credIdHex = "0011223344556677889900112233445566778899001122334455667788990011"; uint256 credId; BEAST_EXPECT(credId.parseHex(credIdHex)); // Create a transaction with a nested array field STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) { obj.setAccountID(sfAccount, env.master.id()); STArray memos; STObject memoObj(sfMemo); memoObj.setFieldVL(sfMemoData, Slice("hello", 5)); memos.push_back(memoObj); obj.setFieldArray(sfMemos, memos); STVector256 credIds; credIds.pushBack(credId); obj.setFieldV256(sfCredentialIDs, credIds); }); ApplyContext ac = createApplyContext(env, ov, stx); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // hfs.getTxNestedField(locator); { // Locator for sfMemos[0].sfMemo.sfMemoData // Locator is a sequence of int32_t codes: // [sfMemos.getCode(), 0, sfMemoData.getCode()] std::vector const locatorVec = {sfMemos.getCode(), 0, sfMemoData.getCode()}; vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); WasmValVec params(4), result(1); auto* trap = ww(getTxNestedField_wrap, &import.at("get_tx_nested_field"), params, result, 0, locatorVec.size() * sizeof(int32_t), 256, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) { auto memoDataBytes = vrt.getBytes(params, 2); memoDataBytes.resize(result[0].of.i32); std::string const memoData(memoDataBytes.begin(), memoDataBytes.end()); BEAST_EXPECT(memoData == "hello"); } } // hfs.getTxNestedField(locator); { // Locator for sfCredentialIDs[0] std::vector locatorVec = {sfCredentialIDs.getCode(), 0}; vrt.setBytes( 0, reinterpret_cast(locatorVec.data()), locatorVec.size() * sizeof(int32_t)); WasmValVec params(4), result(1); auto* trap = ww(getTxNestedField_wrap, &import.at("get_tx_nested_field"), params, result, 0, locatorVec.size() * sizeof(int32_t), 256, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) { auto credIdBytes = vrt.getBytes(params, 2); credIdBytes.resize(result[0].of.i32); std::string const credIdResult(credIdBytes.begin(), credIdBytes.end()); BEAST_EXPECT(strHex(credIdResult) == credIdHex); } } // hfs.getTxNestedField(locator); { // can use the nested locator for base fields too std::vector locatorVec = {sfAccount.getCode()}; vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); WasmValVec params(4), result(1); auto* trap = ww(getTxNestedField_wrap, &import.at("get_tx_nested_field"), params, result, 0, locatorVec.size() * sizeof(int32_t), 256, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) { auto accountBytes = vrt.getBytes(params, 2); accountBytes.resize(result[0].of.i32); BEAST_EXPECT(std::ranges::equal(accountBytes, env.master.id())); } } // hfs.getTxNestedField(locator); { // unaligned locator std::vector locatorVec(sizeof(int32_t) + 1); auto const accountFieldCode = sfAccount.getCode(); memcpy(locatorVec.data() + 1, &accountFieldCode, sizeof(int32_t)); vrt.setBytes(0, locatorVec.data() + 1, sizeof(int32_t)); WasmValVec params(4), result(1); auto* trap = ww(getTxNestedField_wrap, &import.at("get_tx_nested_field"), params, result, 0, sizeof(int32_t), 256, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) { auto accountBytes = vrt.getBytes(params, 2); accountBytes.resize(result[0].of.i32); BEAST_EXPECT(std::ranges::equal(accountBytes, env.master.id())); } } auto expectError = [&](std::vector const& locatorVec, HostFunctionError expectedError) { vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); WasmValVec params(4), result(1); // hfs.getTxNestedField(locator); auto* trap = ww(getTxNestedField_wrap, &import.at("get_tx_nested_field"), params, result, 0, locatorVec.size() * sizeof(int32_t), 256, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECTS( result[0].of.i32 == static_cast(expectedError), std::to_string(result[0].of.i32)); }; // hfs.getTxNestedField(locator); // Locator for non-existent base field expectError( {sfSigners.getCode(), // sfSigners does not exist 0, sfAccount.getCode()}, HostFunctionError::FieldNotFound); // hfs.getTxNestedField(locator); // Locator for non-existent index expectError( {sfMemos.getCode(), 1, // index 1 does not exist sfMemoData.getCode()}, HostFunctionError::IndexOutOfBounds); // hfs.getTxNestedField(locator); // Locator for non-existent index expectError( {sfCredentialIDs.getCode(), 1}, // index 1 does not exist HostFunctionError::IndexOutOfBounds); // hfs.getTxNestedField(locator); // Locator for negative index (STArray) expectError( {sfMemos.getCode(), -1, // negative index sfMemoData.getCode()}, HostFunctionError::IndexOutOfBounds); // hfs.getTxNestedField(locator); // Locator for negative index (STVector256) expectError( {sfCredentialIDs.getCode(), -1}, // negative index HostFunctionError::IndexOutOfBounds); // hfs.getTxNestedField(locator); // Locator for non-existent nested field expectError( {sfMemos.getCode(), 0, sfURI.getCode()}, // sfURI does not exist in the memo HostFunctionError::FieldNotFound); // hfs.getTxNestedField(locator); // Locator for non-existent base sfield expectError( {fieldCode(20000, 20000), // nonexistent SField code 0, sfAccount.getCode()}, HostFunctionError::InvalidField); // hfs.getTxNestedField(locator); // Locator for non-existent nested sfield expectError( {sfMemos.getCode(), // nonexistent SField code 0, fieldCode(20000, 20000)}, HostFunctionError::InvalidField); // hfs.getTxNestedField(locator); // Locator for negative base sfield code (-1 = kSfInvalid, exists in map but not in tx) expectError( {-1, // kSfInvalid's field code 0, sfAccount.getCode()}, HostFunctionError::FieldNotFound); // hfs.getTxNestedField(locator); // Locator for zero base sfield code (0 = kSfGeneric, exists in map but not in tx) expectError( {0, // kSfGeneric's field code 0, sfAccount.getCode()}, HostFunctionError::FieldNotFound); // hfs.getTxNestedField(locator); // Locator for very negative base sfield code (not in knownCodeToField map) expectError( {std::numeric_limits::min(), 0, sfAccount.getCode()}, HostFunctionError::InvalidField); // hfs.getTxNestedField(locator); // Locator for negative nested sfield code in STObject context // (sfMemos[0] is an STObject, then -1 is looked up as SField) expectError( {sfMemos.getCode(), 0, -1}, // -1 = kSfInvalid, exists in map but not in memo object HostFunctionError::FieldNotFound); // hfs.getTxNestedField(locator); // Locator for STArray expectError({sfMemos.getCode()}, HostFunctionError::NotLeafField); // hfs.getTxNestedField(locator); // Locator for STVector256 expectError({sfCredentialIDs.getCode()}, HostFunctionError::NotLeafField); // hfs.getTxNestedField(locator); // Locator for nesting into non-array/object field expectError( {sfAccount.getCode(), // sfAccount is not an array or object 0, sfAccount.getCode()}, HostFunctionError::LocatorMalformed); // hfs.getTxNestedField(locator); // Locator for empty locator expectError({}, HostFunctionError::LocatorMalformed); // hfs.getTxNestedField(locator); // Locator for malformed locator (not multiple of 4) { std::vector locatorVec = {sfMemos.getCode()}; vrt.setBytes(0, locatorVec.data(), 3); WasmValVec params(4), result(1); auto* trap = ww(getTxNestedField_wrap, &import.at("get_tx_nested_field"), params, result, 0, 3, 256, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::LocatorMalformed)); } } void testGetCurrentLedgerObjNestedField() { testcase("getCurrentLedgerObjNestedField"); using namespace test::jtx; Env env{*this}; Account const alice("alice"); Account const becky("becky"); // Create a SignerList for env.master env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); // Find the signer ledger object auto const signerKeylet = keylet::signers(env.master.id()); BEAST_EXPECT(env.le(signerKeylet)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, signerKeylet); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // hfs.getCurrentLedgerObjNestedField(baseLocatorSlice); // Locator for base field { std::vector baseLocator = {sfSignerQuorum.getCode()}; vrt.setBytes(0, baseLocator.data(), baseLocator.size() * sizeof(int32_t)); WasmValVec params(4), result(1); auto* trap = ww(getCurrentLedgerObjNestedField_wrap, &import.at("get_current_ledger_obj_nested_field"), params, result, 0, baseLocator.size() * sizeof(int32_t), 256, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) { auto signerQuorumBytes = vrt.getBytes(params, 2); signerQuorumBytes.resize(result[0].of.i32); BEAST_EXPECT(signerQuorumBytes == toBytes(static_cast(2))); } } auto expectError = [&](std::vector const& locatorVec, HostFunctionError expectedError) { vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); WasmValVec params(4), result(1); // hfs.getCurrentLedgerObjNestedField(locator); auto* trap = ww(getCurrentLedgerObjNestedField_wrap, &import.at("get_current_ledger_obj_nested_field"), params, result, 0, locatorVec.size() * sizeof(int32_t), 256, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECTS( result[0].of.i32 == static_cast(expectedError), std::to_string(result[0].of.i32)); }; // hfs.getCurrentLedgerObjNestedField(locator); // Locator for non-existent base field expectError( {sfSigners.getCode(), // sfSigners does not exist 0, sfAccount.getCode()}, HostFunctionError::FieldNotFound); // hfs.getCurrentLedgerObjNestedField(locator); // Locator for nesting into non-array/object field expectError( {sfSignerQuorum.getCode(), // sfSignerQuorum is not an array or object 0, sfAccount.getCode()}, HostFunctionError::LocatorMalformed); // hfs.getCurrentLedgerObjNestedField(emptyLocator); // Locator for empty locator { WasmValVec params(4), result(1); auto* trap = ww(getCurrentLedgerObjNestedField_wrap, &import.at("get_current_ledger_obj_nested_field"), params, result, 0, 0, 256, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::LocatorMalformed)); } // hfs.getCurrentLedgerObjNestedField(malformedLocator); // Locator for malformed locator (not multiple of 4) { std::vector malformedLocatorVec = {sfMemos.getCode()}; vrt.setBytes(0, malformedLocatorVec.data(), 3); WasmValVec params(4), result(1); auto* trap = ww(getCurrentLedgerObjNestedField_wrap, &import.at("get_current_ledger_obj_nested_field"), params, result, 0, 3, 256, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::LocatorMalformed)); } // hfs.getCurrentLedgerObjNestedField(locator); { auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master) + 5); VirtualRuntime vrt2; WasmHostFunctionsImpl dummyHfs(ac, dummyEscrow); auto import2 = xrpl::createWasmImport(dummyHfs); dummyHfs.setRT(&vrt2); std::vector const locatorVec = {sfAccount.getCode()}; vrt2.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); WasmValVec params(4), result(1); auto* trap = ww(getCurrentLedgerObjNestedField_wrap, &import2.at("get_current_ledger_obj_nested_field"), params, result, 0, locatorVec.size() * sizeof(int32_t), 256, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECTS( result[0].of.i32 == static_cast(HostFunctionError::LedgerObjNotFound), std::to_string(result[0].of.i32)); } } void testGetLedgerObjNestedField() { testcase("getLedgerObjNestedField"); using namespace test::jtx; Env env{*this}; Account const alice("alice"); Account const becky("becky"); // Create a SignerList for env.master env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); env.close(); OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // Cache the SignerList ledger object in slot 1 auto const signerListKeylet = keylet::signers(env.master.id()); // hfs.cacheLedgerObj(signerListKeylet.key, 1); { WasmValVec params(3), result(1); vrt.setBytes(0, signerListKeylet.key.data(), uint256::size()); auto* trap = ww(cacheLedgerObj_wrap, &import.at("cache_ledger_obj"), params, result, 0, uint256::size(), 1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 1); } // Locator for sfSignerEntries[0].sfAccount { std::vector const locatorVec = { sfSignerEntries.getCode(), 0, sfAccount.getCode()}; // hfs.getLedgerObjNestedField(1, locator); vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); WasmValVec params(5), result(1); auto* trap = ww(getLedgerObjNestedField_wrap, &import.at("get_ledger_obj_nested_field"), params, result, 1, 0, locatorVec.size() * sizeof(int32_t), 256, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) { auto aliceIdBytes = vrt.getBytes(params, 3); aliceIdBytes.resize(result[0].of.i32); BEAST_EXPECT(std::ranges::equal(aliceIdBytes, alice.id())); } } // Locator for sfSignerEntries[1].sfAccount { std::vector const locatorVec = { sfSignerEntries.getCode(), 1, sfAccount.getCode()}; // hfs.getLedgerObjNestedField(1, locator); vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); WasmValVec params(5), result(1); auto* trap = ww(getLedgerObjNestedField_wrap, &import.at("get_ledger_obj_nested_field"), params, result, 1, 0, locatorVec.size() * sizeof(int32_t), 256, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) { auto beckyIdBytes = vrt.getBytes(params, 3); beckyIdBytes.resize(result[0].of.i32); BEAST_EXPECT(std::ranges::equal(beckyIdBytes, becky.id())); } } // Locator for sfSignerEntries[0].sfSignerWeight { std::vector const locatorVec = { sfSignerEntries.getCode(), 0, sfSignerWeight.getCode()}; // hfs.getLedgerObjNestedField(1, locator); vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); WasmValVec params(5), result(1); auto* trap = ww(getLedgerObjNestedField_wrap, &import.at("get_ledger_obj_nested_field"), params, result, 1, 0, locatorVec.size() * sizeof(int32_t), 256, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) { // Should be 1 auto const expected = toBytes(static_cast(1)); auto weightBytes = vrt.getBytes(params, 3); weightBytes.resize(result[0].of.i32); BEAST_EXPECT(weightBytes == expected); } } // Locator for base field sfSignerQuorum { std::vector const locatorVec = {sfSignerQuorum.getCode()}; // hfs.getLedgerObjNestedField(1, locator); vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); WasmValVec params(5), result(1); auto* trap = ww(getLedgerObjNestedField_wrap, &import.at("get_ledger_obj_nested_field"), params, result, 1, 0, locatorVec.size() * sizeof(int32_t), 256, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) { auto const expected = toBytes(static_cast(2)); auto quorumBytes = vrt.getBytes(params, 3); quorumBytes.resize(result[0].of.i32); BEAST_EXPECT(quorumBytes == expected); } } // Helper for error checks auto expectError = [&](std::vector const& locatorVec, HostFunctionError expectedError, int slot = 1) { // hfs.getLedgerObjNestedField(slot, locator); vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); WasmValVec params(5), result(1); auto* trap = ww(getLedgerObjNestedField_wrap, &import.at("get_ledger_obj_nested_field"), params, result, slot, 0, locatorVec.size() * sizeof(int32_t), 256, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECTS( result[0].of.i32 == static_cast(expectedError), std::to_string(result[0].of.i32)); }; // Error: base field not found expectError( {sfSigners.getCode(), // sfSigners does not exist 0, sfAccount.getCode()}, HostFunctionError::FieldNotFound); // Error: index out of bounds expectError( {sfSignerEntries.getCode(), 2, // index 2 does not exist sfAccount.getCode()}, HostFunctionError::IndexOutOfBounds); // Error: nested field not found expectError( { sfSignerEntries.getCode(), 0, sfDestination.getCode() // sfDestination does not exist }, HostFunctionError::FieldNotFound); // Error: invalid field code expectError( {fieldCode(99999, 99999), 0, sfAccount.getCode()}, HostFunctionError::InvalidField); // Error: invalid nested field code expectError( {sfSignerEntries.getCode(), 0, fieldCode(99999, 99999)}, HostFunctionError::InvalidField); // Error: slot out of range expectError({sfSignerQuorum.getCode()}, HostFunctionError::SlotOutRange, 0); expectError({sfSignerQuorum.getCode()}, HostFunctionError::SlotOutRange, 257); // Error: empty slot expectError({sfSignerQuorum.getCode()}, HostFunctionError::EmptySlot, 2); // Error: locator for STArray (not leaf field) expectError({sfSignerEntries.getCode()}, HostFunctionError::NotLeafField); // Error: nesting into non-array/object field expectError( {sfSignerQuorum.getCode(), 0, sfAccount.getCode()}, HostFunctionError::LocatorMalformed); // Error: empty locator expectError({}, HostFunctionError::LocatorMalformed); // Error: locator malformed (not multiple of 4) { std::vector const locatorVec = {sfSignerEntries.getCode()}; // hfs.getLedgerObjNestedField(1, locator); vrt.setBytes(0, locatorVec.data(), 3); WasmValVec params(5), result(1); auto* trap = ww(getLedgerObjNestedField_wrap, &import.at("get_ledger_obj_nested_field"), params, result, 1, 0, 3, 256, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::LocatorMalformed)); } } void testGetTxArrayLen() { testcase("getTxArrayLen"); using namespace test::jtx; std::string const credIdHex = "0011223344556677889900112233445566778899001122334455667788990011"; uint256 credId; BEAST_EXPECT(credId.parseHex(credIdHex)); Env env{*this}; OpenView ov{*env.current()}; // Transaction with an array field STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) { obj.setAccountID(sfAccount, env.master.id()); STArray memos; { STObject memoObj(sfMemo); memoObj.setFieldVL(sfMemoData, Slice("hello", 5)); memos.push_back(memoObj); } { STObject memoObj(sfMemo); memoObj.setFieldVL(sfMemoData, Slice("world", 5)); memos.push_back(memoObj); } obj.setFieldArray(sfMemos, memos); STVector256 credIds; credIds.pushBack(credId); obj.setFieldV256(sfCredentialIDs, credIds); }); ApplyContext ac = createApplyContext(env, ov, stx); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // Should return 2 for sfMemos // hfs.getTxArrayLen(sfMemos); { WasmValVec params(1), result(1); auto* trap = ww(getTxArrayLen_wrap, &import.at("get_tx_array_len"), params, result, sfMemos.getCode()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECT(result[0].of.i32 > 0)) BEAST_EXPECT(result[0].of.i32 == 2); } // Should return error for non-array field // hfs.getTxArrayLen(sfAccount); { WasmValVec params(1), result(1); auto* trap = ww(getTxArrayLen_wrap, &import.at("get_tx_array_len"), params, result, sfAccount.getCode()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECT(result[0].of.i32 == static_cast(HostFunctionError::NoArray)); } // Should return error for missing array field // hfs.getTxArrayLen(sfSigners); { WasmValVec params(1), result(1); auto* trap = ww(getTxArrayLen_wrap, &import.at("get_tx_array_len"), params, result, sfSigners.getCode()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FieldNotFound)); } // Should return 1 for sfCredentialIDs // hfs.getTxArrayLen(sfCredentialIDs); { WasmValVec params(1), result(1); auto* trap = ww(getTxArrayLen_wrap, &import.at("get_tx_array_len"), params, result, sfCredentialIDs.getCode()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECT(result[0].of.i32 > 0)) BEAST_EXPECT(result[0].of.i32 == 1); } } void testGetCurrentLedgerObjArrayLen() { testcase("getCurrentLedgerObjArrayLen"); using namespace test::jtx; Env env{*this}; Account const alice("alice"); Account const becky("becky"); // Create a SignerList for env.master env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); env.close(); OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const signerKeylet = keylet::signers(env.master.id()); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, signerKeylet); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // hfs.getCurrentLedgerObjArrayLen(sfSignerEntries); { WasmValVec params(1), result(1); auto* trap = ww(getCurrentLedgerObjArrayLen_wrap, &import.at("get_current_ledger_obj_array_len"), params, result, sfSignerEntries.getCode()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECT(result[0].of.i32 > 0)) BEAST_EXPECT(result[0].of.i32 == 2); } // hfs.getCurrentLedgerObjArrayLen(sfMemos); { WasmValVec params(1), result(1); auto* trap = ww(getCurrentLedgerObjArrayLen_wrap, &import.at("get_current_ledger_obj_array_len"), params, result, sfMemos.getCode()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FieldNotFound)); } // Should return NO_ARRAY for non-array field // hfs.getCurrentLedgerObjArrayLen(sfAccount); { WasmValVec params(1), result(1); auto* trap = ww(getCurrentLedgerObjArrayLen_wrap, &import.at("get_current_ledger_obj_array_len"), params, result, sfAccount.getCode()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECT(result[0].of.i32 == static_cast(HostFunctionError::NoArray)); } { auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master) + 5); VirtualRuntime vrt2; WasmHostFunctionsImpl dummyHfs(ac, dummyEscrow); auto import2 = xrpl::createWasmImport(dummyHfs); dummyHfs.setRT(&vrt2); // auto const len = dummyHfs.getCurrentLedgerObjArrayLen(sfMemos); WasmValVec params(1), result(1); auto* trap = ww(getCurrentLedgerObjArrayLen_wrap, &import2.at("get_current_ledger_obj_array_len"), params, result, sfMemos.getCode()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::LedgerObjNotFound)); } } void testGetLedgerObjArrayLen() { testcase("getLedgerObjArrayLen"); using namespace test::jtx; Env env{*this}; Account const alice("alice"); Account const becky("becky"); // Create a SignerList for env.master env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); env.close(); OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); auto const signerListKeylet = keylet::signers(env.master.id()); // hfs.cacheLedgerObj(signerListKeylet.key, 1); { WasmValVec params(3), result(1); vrt.setBytes(0, signerListKeylet.key.data(), uint256::size()); auto* trap = ww(cacheLedgerObj_wrap, &import.at("cache_ledger_obj"), params, result, 0, uint256::size(), 1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 1); } { // hfs.getLedgerObjArrayLen(1, sfSignerEntries); WasmValVec params(2), result(1); auto* trap = ww(getLedgerObjArrayLen_wrap, &import.at("get_ledger_obj_array_len"), params, result, 1, sfSignerEntries.getCode()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECT(result[0].of.i32 > 0)) { // Should return 2 for sfSignerEntries BEAST_EXPECT(result[0].of.i32 == 2); } } { // hfs.getLedgerObjArrayLen(0, sfSignerEntries); WasmValVec params(2), result(1); auto* trap = ww(getLedgerObjArrayLen_wrap, &import.at("get_ledger_obj_array_len"), params, result, 0, sfSignerEntries.getCode()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECT(result[0].of.i32 == static_cast(HostFunctionError::SlotOutRange)); } { // Should return error for non-array field // hfs.getLedgerObjArrayLen(1, sfAccount); WasmValVec params(2), result(1); auto* trap = ww(getLedgerObjArrayLen_wrap, &import.at("get_ledger_obj_array_len"), params, result, 1, sfAccount.getCode()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECT(result[0].of.i32 == static_cast(HostFunctionError::NoArray)); } { // Should return error for empty slot // hfs.getLedgerObjArrayLen(2, sfSignerEntries); WasmValVec params(2), result(1); auto* trap = ww(getLedgerObjArrayLen_wrap, &import.at("get_ledger_obj_array_len"), params, result, 2, sfSignerEntries.getCode()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECT(result[0].of.i32 == static_cast(HostFunctionError::EmptySlot)); } { // Should return error for missing array field // hfs.getLedgerObjArrayLen(1, sfMemos); WasmValVec params(2), result(1); auto* trap = ww(getLedgerObjArrayLen_wrap, &import.at("get_ledger_obj_array_len"), params, result, 1, sfMemos.getCode()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FieldNotFound)); } } void testGetTxNestedArrayLen() { testcase("getTxNestedArrayLen"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) { STArray memos; STObject memoObj(sfMemo); memoObj.setFieldVL(sfMemoData, Slice("hello", 5)); memos.push_back(memoObj); obj.setFieldArray(sfMemos, memos); }); ApplyContext ac = createApplyContext(env, ov, stx); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // Helper for error checks auto expectError = [&](std::vector const& locatorVec, HostFunctionError expectedError) { // hfs.getTxNestedArrayLen(locator); vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); WasmValVec params(2), result(1); auto* trap = ww(getTxNestedArrayLen_wrap, &import.at("get_tx_nested_array_len"), params, result, 0, locatorVec.size() * sizeof(int32_t)); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECTS( result[0].of.i32 == static_cast(expectedError), std::to_string(result[0].of.i32)); }; // Locator for sfMemos { std::vector locatorVec = {sfMemos.getCode()}; // hfs.getTxNestedArrayLen(locator); vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); WasmValVec params(2), result(1); auto* trap = ww(getTxNestedArrayLen_wrap, &import.at("get_tx_nested_array_len"), params, result, 0, locatorVec.size() * sizeof(int32_t)); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECT(result[0].of.i32 == 1); } // Error: non-array field expectError({sfAccount.getCode()}, HostFunctionError::NoArray); // Error: missing field expectError({sfSigners.getCode()}, HostFunctionError::FieldNotFound); } void testGetCurrentLedgerObjNestedArrayLen() { testcase("getCurrentLedgerObjNestedArrayLen"); using namespace test::jtx; Env env{*this}; Account const alice("alice"); Account const becky("becky"); // Create a SignerList for env.master env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); env.close(); OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const signerKeylet = keylet::signers(env.master.id()); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, signerKeylet); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // Helper for error checks auto expectError = [&](std::vector const& locatorVec, HostFunctionError expectedError) { // hfs.getCurrentLedgerObjNestedArrayLen(locator); vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); WasmValVec params(2), result(1); auto* trap = ww(getCurrentLedgerObjNestedArrayLen_wrap, &import.at("get_current_ledger_obj_nested_array_len"), params, result, 0, locatorVec.size() * sizeof(int32_t)); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECTS( result[0].of.i32 == static_cast(expectedError), std::to_string(result[0].of.i32)); }; // Locator for sfSignerEntries { std::vector locatorVec = {sfSignerEntries.getCode()}; // hfs.getCurrentLedgerObjNestedArrayLen(locator); vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); WasmValVec params(2), result(1); auto* trap = ww(getCurrentLedgerObjNestedArrayLen_wrap, &import.at("get_current_ledger_obj_nested_array_len"), params, result, 0, locatorVec.size() * sizeof(int32_t)); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECT(result[0].of.i32 == 2); } // Error: non-array field expectError({sfSignerQuorum.getCode()}, HostFunctionError::NoArray); // Error: missing field expectError({sfSigners.getCode()}, HostFunctionError::FieldNotFound); { auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master) + 5); VirtualRuntime vrt2; WasmHostFunctionsImpl dummyHfs(ac, dummyEscrow); auto import2 = xrpl::createWasmImport(dummyHfs); dummyHfs.setRT(&vrt2); std::vector locatorVec = {sfAccount.getCode()}; // auto const result = dummyHfs.getCurrentLedgerObjNestedArrayLen(locator); vrt2.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); WasmValVec params(2), result(1); auto* trap = ww(getCurrentLedgerObjNestedArrayLen_wrap, &import2.at("get_current_ledger_obj_nested_array_len"), params, result, 0, locatorVec.size() * sizeof(int32_t)); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECTS( result[0].of.i32 == static_cast(HostFunctionError::LedgerObjNotFound), std::to_string(result[0].of.i32)); } } void testGetLedgerObjNestedArrayLen() { testcase("getLedgerObjNestedArrayLen"); using namespace test::jtx; Env env{*this}; Account const alice("alice"); Account const becky("becky"); env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); env.close(); OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); auto const signerListKeylet = keylet::signers(env.master.id()); // hfs.cacheLedgerObj(signerListKeylet.key, 1); { WasmValVec params(3), result(1); vrt.setBytes(0, signerListKeylet.key.data(), uint256::size()); auto* trap = ww(cacheLedgerObj_wrap, &import.at("cache_ledger_obj"), params, result, 0, uint256::size(), 1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 1); } // Locator for sfSignerEntries std::vector locatorVec = {sfSignerEntries.getCode()}; // hfs.getLedgerObjNestedArrayLen(1, locator); { vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); WasmValVec params(3), result(1); auto* trap = ww(getLedgerObjNestedArrayLen_wrap, &import.at("get_ledger_obj_nested_array_len"), params, result, 1, 0, locatorVec.size() * sizeof(int32_t)); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); if (BEAST_EXPECT(result[0].of.i32 > 0)) BEAST_EXPECT(result[0].of.i32 == 2); } // Helper for error checks auto expectError = [&](std::vector const& locatorVec, HostFunctionError expectedError, int slot = 1) { // hfs.getLedgerObjNestedArrayLen(slot, locator); vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); WasmValVec params(3), result(1); auto* trap = ww(getLedgerObjNestedArrayLen_wrap, &import.at("get_ledger_obj_nested_array_len"), params, result, slot, 0, locatorVec.size() * sizeof(int32_t)); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); BEAST_EXPECTS( result[0].of.i32 == static_cast(expectedError), std::to_string(result[0].of.i32)); }; // Error: non-array field expectError({sfSignerQuorum.getCode()}, HostFunctionError::NoArray); // Error: missing field expectError({sfSigners.getCode()}, HostFunctionError::FieldNotFound); // Slot out of range expectError(locatorVec, HostFunctionError::SlotOutRange, 0); expectError(locatorVec, HostFunctionError::SlotOutRange, 257); // Empty slot expectError(locatorVec, HostFunctionError::EmptySlot, 2); // Error: empty locator expectError({}, HostFunctionError::LocatorMalformed); // Error: locator malformed (not multiple of 4) { // hfs.getLedgerObjNestedArrayLen(1, malformedLocator); vrt.setBytes(0, locatorVec.data(), 3); WasmValVec params(3), result(1); auto* trap = ww(getLedgerObjNestedArrayLen_wrap, &import.at("get_ledger_obj_nested_array_len"), params, result, 1, 0, 3); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::LocatorMalformed)); } // Error: locator for non-STArray field expectError( {sfSignerQuorum.getCode(), 0, sfAccount.getCode()}, HostFunctionError::LocatorMalformed); } void testUpdateData() { testcase("updateData"); using namespace test::jtx; Env env{*this}; env(escrow::create(env.master, env.master, XRP(100)), escrow::kFinishTime(env.now() + std::chrono::seconds(1))); env.close(); OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const escrowKeylet = keylet::escrow(env.master, env.seq(env.master) - 1); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, escrowKeylet); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // Should succeed for small data Bytes data(10, 0x42); // hfs.updateData(Slice(data.data(), data.size())); { vrt.setBytes(0, data.data(), data.size()); WasmValVec params(2), result(1); auto* trap = ww(updateData_wrap, &import.at("update_data"), params, result, 0, data.size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == data.size()); BEAST_EXPECT(hfs.getData() && *hfs.getData() == data); } // Should fail for too large data Bytes bigData(maxWasmDataLength + 1, 0x42); // hfs.updateData(Slice(bigData.data(), bigData.size())); { vrt.setBytes(0, bigData.data(), bigData.size()); WasmValVec params(2), result(1); auto* trap = ww(updateData_wrap, &import.at("update_data"), params, result, 0, bigData.size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == HfErrorToInt(HostFunctionError::DataFieldTooLarge)); } } void testCheckSignature() { testcase("checkSignature"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // Generate a keypair and sign a message auto const kp = generateKeyPair(KeyType::Secp256k1, randomSeed()); PublicKey const& pk = kp.first; SecretKey const& sk = kp.second; std::string const& message = "hello signature"; auto const sig = sign(pk, sk, Slice(message.data(), message.size())); // Should succeed for valid signature { // hfs.checkSignature( // Slice(message.data(), message.size()), // Slice(sig.data(), sig.size()), // Slice(pk.data(), pk.size())); vrt.setBytes(0, message.data(), message.size()); vrt.setBytes(256, sig.data(), sig.size()); vrt.setBytes(512, pk.data(), pk.size()); WasmValVec params(6), result(1); auto* trap = ww(checkSignature_wrap, &import.at("check_sig"), params, result, 0, message.size(), 256, sig.size(), 512, pk.size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 1); } // Should fail for invalid signature { std::string badSig(sig.size(), 0xFF); // hfs.checkSignature( // Slice(message.data(), message.size()), // Slice(badSig.data(), badSig.size()), // Slice(pk.data(), pk.size())); vrt.setBytes(0, message.data(), message.size()); vrt.setBytes(256, badSig.data(), badSig.size()); vrt.setBytes(512, pk.data(), pk.size()); WasmValVec params(6), result(1); auto* trap = ww(checkSignature_wrap, &import.at("check_sig"), params, result, 0, message.size(), 256, badSig.size(), 512, pk.size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0); } // Should fail for invalid public key { std::string badPk(pk.size(), 0x00); // hfs.checkSignature( // Slice(message.data(), message.size()), // Slice(sig.data(), sig.size()), // Slice(badPk.data(), badPk.size())); vrt.setBytes(0, message.data(), message.size()); vrt.setBytes(256, sig.data(), sig.size()); vrt.setBytes(512, badPk.data(), badPk.size()); WasmValVec params(6), result(1); auto* trap = ww(checkSignature_wrap, &import.at("check_sig"), params, result, 0, message.size(), 256, sig.size(), 512, badPk.size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == HfErrorToInt(HostFunctionError::InvalidParams)); } // Should fail for empty public key { // hfs.checkSignature( // Slice(message.data(), message.size()), // Slice(sig.data(), sig.size()), // Slice(nullptr, 0)); vrt.setBytes(0, message.data(), message.size()); vrt.setBytes(256, sig.data(), sig.size()); WasmValVec params(6), result(1); auto* trap = ww(checkSignature_wrap, &import.at("check_sig"), params, result, 0, message.size(), 256, sig.size(), 512, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == HfErrorToInt(HostFunctionError::InvalidParams)); } // Should fail for empty signature { // hfs.checkSignature( // Slice(message.data(), message.size()), // Slice(nullptr, 0), // Slice(pk.data(), pk.size())); vrt.setBytes(0, message.data(), message.size()); vrt.setBytes(512, pk.data(), pk.size()); WasmValVec params(6), result(1); auto* trap = ww(checkSignature_wrap, &import.at("check_sig"), params, result, 0, message.size(), 256, 0, 512, pk.size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0); } // Should fail for empty message { // hfs.checkSignature( // Slice(nullptr, 0), Slice(sig.data(), sig.size()), Slice(pk.data(), pk.size())); vrt.setBytes(256, sig.data(), sig.size()); vrt.setBytes(512, pk.data(), pk.size()); WasmValVec params(6), result(1); auto* trap = ww(checkSignature_wrap, &import.at("check_sig"), params, result, 0, 0, 256, sig.size(), 512, pk.size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0); } } void testComputeSha512HalfHash() { testcase("computeSha512HalfHash"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); std::string data = "hello world"; // hfs.computeSha512HalfHash(Slice(data.data(), data.size())); { vrt.setBytes(0, data.data(), data.size()); WasmValVec params(4), result(1); auto* trap = ww(computeSha512HalfHash_wrap, &import.at("compute_sha512_half"), params, result, 0, data.size(), 256, uint256::size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == uint256::size()); // Should match direct call to sha512Half auto expected = sha512Half(Slice(data.data(), data.size())); auto hashBytes = vrt.getBytes(params, 2); BEAST_EXPECT(std::ranges::equal(hashBytes, expected)); } } void testKeyletFunctions() { testcase("keylet functions"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); WasmHostFunctionsImpl hfs(ac, dummyEscrow); VirtualRuntime vrt; auto const usdIssue = env.master["USD"].issue(); auto const masterID = env.master.id(); auto const baseMpt = makeMptID(1, masterID); auto imp = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // Lambda to compare a Bytes (std::vector) to a keylet auto compareKeylet = [](std::vector const& bytes, Keylet const& kl) { return std::ranges::equal(bytes, kl.key); }; { auto const expected = keylet::account(masterID); WasmValVec params(4), result(1); auto* trap = ww( accountKeylet_wrap, &imp.at("account_keylet"), params, result, masterID, 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 2); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(accountKeylet_wrap, &imp.at("account_keylet"), params, result, xrpAccount(), 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); } { auto const expected = keylet::amm(xrpIssue(), usdIssue); WasmValVec params(6), result(1); auto* trap = ww(ammKeylet_wrap, &imp.at("amm_keylet"), params, result, xrpIssue(), usdIssue, 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 4); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(ammKeylet_wrap, &imp.at("amm_keylet"), params, result, xrpIssue(), xrpIssue(), 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); auto* trap3 = ww(ammKeylet_wrap, &imp.at("amm_keylet"), params, result, baseMpt, xrpIssue(), 1024, 32); BEAST_EXPECT( !trap3 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); } { auto const expected = keylet::check(masterID, 1u); WasmValVec params(6), result(1); auto* trap = ww(checkKeylet_wrap, &imp.at("check_keylet"), params, result, masterID, toBytes(1u), 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 4); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(checkKeylet_wrap, &imp.at("check_keylet"), params, result, xrpAccount(), toBytes(1u), 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); } std::string const credTypeStr = "test"; Slice const credType(credTypeStr.data(), credTypeStr.size()); Account const alice("alice"); { auto const expected = keylet::credential(masterID, masterID, credType); WasmValVec params(8), result(1); auto* trap = ww(credentialKeylet_wrap, &imp.at("credential_keylet"), params, result, masterID, masterID, credType, 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 6); BEAST_EXPECT(compareKeylet(actual, expected)); } std::string_view constexpr longCredTypeStr = "abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]" "asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p"; Slice const longCredType(longCredTypeStr.data(), longCredTypeStr.size()); static_assert(longCredTypeStr.size() > kMaxCredentialTypeLength); auto* trap2 = ww(credentialKeylet_wrap, &imp.at("credential_keylet"), params, result, masterID, alice.id(), longCredType, 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); auto* trap3 = ww(credentialKeylet_wrap, &imp.at("credential_keylet"), params, result, xrpAccount(), alice.id(), credType, 1024, 32); BEAST_EXPECT( !trap3 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); auto* trap4 = ww(credentialKeylet_wrap, &imp.at("credential_keylet"), params, result, masterID, xrpAccount(), credType, 1024, 32); BEAST_EXPECT( !trap4 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); } { auto const expected = keylet::did(masterID); WasmValVec params(4), result(1); auto* trap = ww(didKeylet_wrap, &imp.at("did_keylet"), params, result, masterID, 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 2); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(didKeylet_wrap, &imp.at("did_keylet"), params, result, xrpAccount(), 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); } { auto const expected = keylet::delegate(masterID, alice.id()); WasmValVec params(6), result(1); auto* trap = ww(delegateKeylet_wrap, &imp.at("delegate_keylet"), params, result, masterID, alice.id(), 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 4); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(delegateKeylet_wrap, &imp.at("delegate_keylet"), params, result, masterID, masterID, 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); auto* trap3 = ww(delegateKeylet_wrap, &imp.at("delegate_keylet"), params, result, masterID, xrpAccount(), 1024, 32); BEAST_EXPECT( !trap3 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); auto* trap4 = ww(delegateKeylet_wrap, &imp.at("delegate_keylet"), params, result, xrpAccount(), masterID, 1024, 32); BEAST_EXPECT( !trap4 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); } { auto const expected = keylet::depositPreauth(masterID, alice.id()); WasmValVec params(6), result(1); auto* trap = ww(depositPreauthKeylet_wrap, &imp.at("deposit_preauth_keylet"), params, result, masterID, alice.id(), 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 4); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(depositPreauthKeylet_wrap, &imp.at("deposit_preauth_keylet"), params, result, masterID, masterID, 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); auto* trap3 = ww(depositPreauthKeylet_wrap, &imp.at("deposit_preauth_keylet"), params, result, masterID, xrpAccount(), 1024, 32); BEAST_EXPECT( !trap3 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); auto* trap4 = ww(depositPreauthKeylet_wrap, &imp.at("deposit_preauth_keylet"), params, result, xrpAccount(), masterID, 1024, 32); BEAST_EXPECT( !trap4 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); } { auto const expected = keylet::escrow(masterID, 1u); WasmValVec params(6), result(1); auto* trap = ww(escrowKeylet_wrap, &imp.at("escrow_keylet"), params, result, masterID, toBytes(1u), 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 4); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(escrowKeylet_wrap, &imp.at("escrow_keylet"), params, result, xrpAccount(), toBytes(1u), 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); } Currency const usd = toCurrency("USD"); { auto const expected = keylet::line(masterID, alice.id(), usd); WasmValVec params(8), result(1); auto* trap = ww(lineKeylet_wrap, &imp.at("line_keylet"), params, result, masterID, alice.id(), usd, 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 6); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(lineKeylet_wrap, &imp.at("line_keylet"), params, result, masterID, masterID, usd, 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); auto* trap3 = ww(lineKeylet_wrap, &imp.at("line_keylet"), params, result, masterID, xrpAccount(), usd, 1024, 32); BEAST_EXPECT( !trap3 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); auto* trap4 = ww(lineKeylet_wrap, &imp.at("line_keylet"), params, result, xrpAccount(), masterID, usd, 1024, 32); BEAST_EXPECT( !trap4 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); auto* trap5 = ww(lineKeylet_wrap, &imp.at("line_keylet"), params, result, masterID, alice.id(), toCurrency(""), 1024, 32); BEAST_EXPECT( !trap5 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); } { auto const expected = keylet::mptIssuance(1u, masterID); WasmValVec params(6), result(1); auto* trap = ww(mptIssuanceKeylet_wrap, &imp.at("mpt_issuance_keylet"), params, result, masterID, toBytes(1u), 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 4); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(mptIssuanceKeylet_wrap, &imp.at("mpt_issuance_keylet"), params, result, xrpAccount(), toBytes(1u), 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); } { auto const expected = keylet::mptoken(baseMpt, alice.id()); WasmValVec params(6), result(1); auto* trap = ww(mptokenKeylet_wrap, &imp.at("mptoken_keylet"), params, result, baseMpt, alice.id(), 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 4); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(mptokenKeylet_wrap, &imp.at("mptoken_keylet"), params, result, MPTID{}, alice.id(), 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); auto* trap3 = ww(mptokenKeylet_wrap, &imp.at("mptoken_keylet"), params, result, baseMpt, xrpAccount(), 1024, 32); BEAST_EXPECT( !trap3 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); } { auto const expected = keylet::nftoffer(masterID, 1u); WasmValVec params(6), result(1); auto* trap = ww(nftOfferKeylet_wrap, &imp.at("nft_offer_keylet"), params, result, masterID, toBytes(1u), 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 4); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(nftOfferKeylet_wrap, &imp.at("nft_offer_keylet"), params, result, xrpAccount(), toBytes(1u), 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); } { auto const expected = keylet::offer(masterID, 1u); WasmValVec params(6), result(1); auto* trap = ww(offerKeylet_wrap, &imp.at("offer_keylet"), params, result, masterID, toBytes(1u), 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 4); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(offerKeylet_wrap, &imp.at("offer_keylet"), params, result, xrpAccount(), toBytes(1u), 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); } { auto const expected = keylet::oracle(masterID, 1u); WasmValVec params(6), result(1); auto* trap = ww(oracleKeylet_wrap, &imp.at("oracle_keylet"), params, result, masterID, toBytes(1u), 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 4); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(oracleKeylet_wrap, &imp.at("oracle_keylet"), params, result, xrpAccount(), toBytes(1u), 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); } { auto const expected = keylet::payChan(masterID, alice.id(), 1u); WasmValVec params(8), result(1); auto* trap = ww(paychanKeylet_wrap, &imp.at("paychan_keylet"), params, result, masterID, alice.id(), toBytes(1u), 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 6); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(paychanKeylet_wrap, &imp.at("paychan_keylet"), params, result, masterID, masterID, toBytes(1u), 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); auto* trap3 = ww(paychanKeylet_wrap, &imp.at("paychan_keylet"), params, result, masterID, xrpAccount(), toBytes(1u), 1024, 32); BEAST_EXPECT( !trap3 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); auto* trap4 = ww(paychanKeylet_wrap, &imp.at("paychan_keylet"), params, result, xrpAccount(), masterID, toBytes(1u), 1024, 32); BEAST_EXPECT( !trap4 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); } { auto const expected = keylet::permissionedDomain(masterID, 1u); WasmValVec params(6), result(1); auto* trap = ww(permissionedDomainKeylet_wrap, &imp.at("permissioned_domain_keylet"), params, result, masterID, toBytes(1u), 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 4); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(permissionedDomainKeylet_wrap, &imp.at("permissioned_domain_keylet"), params, result, xrpAccount(), toBytes(1u), 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); } { auto const expected = keylet::signers(masterID); WasmValVec params(4), result(1); auto* trap = ww( signersKeylet_wrap, &imp.at("signers_keylet"), params, result, masterID, 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 2); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(signersKeylet_wrap, &imp.at("signers_keylet"), params, result, xrpAccount(), 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); } { auto const expected = keylet::kTicket(masterID, 1u); WasmValVec params(6), result(1); auto* trap = ww(ticketKeylet_wrap, &imp.at("ticket_keylet"), params, result, masterID, toBytes(1u), 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 4); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(ticketKeylet_wrap, &imp.at("ticket_keylet"), params, result, xrpAccount(), toBytes(1u), 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); } { auto const expected = keylet::vault(masterID, 1u); WasmValVec params(6), result(1); auto* trap = ww(vaultKeylet_wrap, &imp.at("vault_keylet"), params, result, masterID, toBytes(1u), 1024, 32); if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) { auto const actual = vrt.getBytes(params, 4); BEAST_EXPECT(compareKeylet(actual, expected)); } auto* trap2 = ww(vaultKeylet_wrap, &imp.at("vault_keylet"), params, result, xrpAccount(), toBytes(1u), 1024, 32); BEAST_EXPECT( !trap2 && result[0].kind == WASM_I32 && result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); } } void testGetNFT() { testcase("getNFT"); using namespace test::jtx; Env env{*this}; Account const alice("alice"); env.fund(XRP(1000), alice); env.close(); // Mint NFT for alice uint256 const nftId = token::getNextID(env, alice, 0u, 0u); std::string const uri = "https://example.com/nft"; env(token::mint(alice), token::Uri(uri)); env.close(); uint256 const nftId2 = token::getNextID(env, alice, 0u, 0u); env(token::mint(alice)); env.close(); OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(alice, env.seq(alice)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // Should succeed for valid NFT { // hfs.getNFT(alice.id(), nftId); vrt.setBytes(0, alice.id().data(), AccountID::size()); vrt.setBytes(256, nftId.data(), uint256::size()); WasmValVec params(6), result(1); auto* trap = ww(getNFT_wrap, &import.at("get_nft"), params, result, 0, AccountID::size(), 256, uint256::size(), 512, 256); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 > 0)) { auto uriBytes = vrt.getBytes(params, 4); uriBytes.resize(result[0].of.i32); BEAST_EXPECT(std::ranges::equal(uriBytes, uri)); } } // Should fail for invalid account { // hfs.getNFT(xrpAccount(), nftId); vrt.setBytes(0, xrpAccount().data(), AccountID::size()); vrt.setBytes(256, nftId.data(), uint256::size()); WasmValVec params(6), result(1); auto* trap = ww(getNFT_wrap, &import.at("get_nft"), params, result, 0, AccountID::size(), 256, uint256::size(), 512, 256); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32)) BEAST_EXPECT(result[0].of.i32 == HfErrorToInt(HostFunctionError::InvalidAccount)); } // Should fail for invalid nftId { // hfs.getNFT(alice.id(), uint256()); uint256 zeroId; vrt.setBytes(0, alice.id().data(), AccountID::size()); vrt.setBytes(256, zeroId.data(), uint256::size()); WasmValVec params(6), result(1); auto* trap = ww(getNFT_wrap, &import.at("get_nft"), params, result, 0, AccountID::size(), 256, uint256::size(), 512, 256); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32)) BEAST_EXPECT(result[0].of.i32 == HfErrorToInt(HostFunctionError::InvalidParams)); } // Should fail for invalid nftId { auto const badId = token::getNextID(env, alice, 0u, 1u); // hfs.getNFT(alice.id(), badId); vrt.setBytes(0, alice.id().data(), AccountID::size()); vrt.setBytes(256, badId.data(), uint256::size()); WasmValVec params(6), result(1); auto* trap = ww(getNFT_wrap, &import.at("get_nft"), params, result, 0, AccountID::size(), 256, uint256::size(), 512, 256); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == HfErrorToInt(HostFunctionError::LedgerObjNotFound)); } { // hfs.getNFT(alice.id(), nftId2); vrt.setBytes(0, alice.id().data(), AccountID::size()); vrt.setBytes(256, nftId2.data(), uint256::size()); WasmValVec params(6), result(1); auto* trap = ww(getNFT_wrap, &import.at("get_nft"), params, result, 0, AccountID::size(), 256, uint256::size(), 512, 256); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32)) BEAST_EXPECT(result[0].of.i32 == HfErrorToInt(HostFunctionError::FieldNotFound)); } } void testGetNFTIssuer() { testcase("getNFTIssuer"); using namespace test::jtx; Env env{*this}; // Mint NFT for env.master uint32_t const taxon = 12345; uint256 const nftId = token::getNextID(env, env.master, taxon); env(token::mint(env.master, taxon)); env.close(); OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // Should succeed for valid NFT id { // hfs.getNFTIssuer(nftId); vrt.setBytes(0, nftId.data(), uint256::size()); WasmValVec params(4), result(1); auto* trap = ww(getNFTIssuer_wrap, &import.at("get_nft_issuer"), params, result, 0, uint256::size(), 256, AccountID::size()); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == AccountID::size())) { auto issuerBytes = vrt.getBytes(params, 2); BEAST_EXPECT(std::ranges::equal(issuerBytes, env.master.id())); } } // Should fail for zero NFT id { // hfs.getNFTIssuer(uint256()); uint256 zeroId; vrt.setBytes(0, zeroId.data(), uint256::size()); WasmValVec params(4), result(1); auto* trap = ww(getNFTIssuer_wrap, &import.at("get_nft_issuer"), params, result, 0, uint256::size(), 256, AccountID::size()); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32)) BEAST_EXPECT(result[0].of.i32 == HfErrorToInt(HostFunctionError::InvalidParams)); } } void testGetNFTTaxon() { testcase("getNFTTaxon"); using namespace test::jtx; Env env{*this}; uint32_t const taxon = 54321; uint256 const nftId = token::getNextID(env, env.master, taxon); env(token::mint(env.master, taxon)); env.close(); OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // hfs.getNFTTaxon(nftId); vrt.setBytes(0, nftId.data(), uint256::size()); WasmValVec params(4), result(1); auto* trap = ww(getNFTTaxon_wrap, &import.at("get_nft_taxon"), params, result, 0, uint256::size(), 256, sizeof(uint32_t)); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == sizeof(uint32_t))) { BEAST_EXPECT(vrt.getUint32(params, 2) == taxon); } } void testGetNFTFlags() { testcase("getNFTFlags"); using namespace test::jtx; Env env{*this}; // Mint NFT with default flags uint256 const nftId = token::getNextID(env, env.master, 0u, tfTransferable); env(token::mint(env.master, 0), Txflags(tfTransferable)); env.close(); OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.getNFTFlags(nftId); vrt.setBytes(0, nftId.data(), uint256::size()); WasmValVec params(2), result(1); auto* trap = ww( getNFTFlags_wrap, &import.at("get_nft_flags"), params, result, 0, uint256::size()); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32)) BEAST_EXPECT(result[0].of.i32 == tfTransferable); } // Should return 0 for zero NFT id { // hfs.getNFTFlags(uint256()); uint256 zeroId; vrt.setBytes(0, zeroId.data(), uint256::size()); WasmValVec params(2), result(1); auto* trap = ww( getNFTFlags_wrap, &import.at("get_nft_flags"), params, result, 0, uint256::size()); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32)) BEAST_EXPECT(result[0].of.i32 == 0); } } void testGetNFTTransferFee() { testcase("getNFTTransferFee"); using namespace test::jtx; Env env{*this}; uint16_t const transferFee = 250; uint256 const nftId = token::getNextID(env, env.master, 0u, tfTransferable, transferFee); env(token::mint(env.master, 0), token::XferFee(transferFee), Txflags(tfTransferable)); env.close(); OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.getNFTTransferFee(nftId); vrt.setBytes(0, nftId.data(), uint256::size()); WasmValVec params(2), result(1); auto* trap = ww(getNFTTransferFee_wrap, &import.at("get_nft_transfer_fee"), params, result, 0, uint256::size()); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32)) BEAST_EXPECT(result[0].of.i32 == transferFee); } // Should return 0 for zero NFT id { // hfs.getNFTTransferFee(uint256()); uint256 zeroId; vrt.setBytes(0, zeroId.data(), uint256::size()); WasmValVec params(2), result(1); auto* trap = ww(getNFTTransferFee_wrap, &import.at("get_nft_transfer_fee"), params, result, 0, uint256::size()); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32)) BEAST_EXPECT(result[0].of.i32 == 0); } } void testGetNFTSerial() { testcase("getNFTSerial"); using namespace test::jtx; Env env{*this}; // Mint NFT with serial 0 uint256 const nftId = token::getNextID(env, env.master, 0u); auto const serial = env.seq(env.master); env(token::mint(env.master)); env.close(); OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.getNFTSerial(nftId); vrt.setBytes(0, nftId.data(), uint256::size()); WasmValVec params(4), result(1); auto* trap = ww(getNFTSerial_wrap, &import.at("get_nft_serial"), params, result, 0, uint256::size(), 256, sizeof(uint32_t)); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == sizeof(uint32_t))) { BEAST_EXPECT(vrt.getUint32(params, 2) == serial); } } // Should return 0 for zero NFT id { // hfs.getNFTSerial(uint256()); uint256 zeroId; vrt.setBytes(0, zeroId.data(), uint256::size()); WasmValVec params(4), result(1); auto* trap = ww(getNFTSerial_wrap, &import.at("get_nft_serial"), params, result, 0, uint256::size(), 256, sizeof(uint32_t)); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == sizeof(uint32_t))) { BEAST_EXPECT(vrt.getUint32(params, 2) == 0); } } } void testTrace() { testcase("trace"); using namespace test::jtx; { Env env(*this); OpenView ov{*env.current()}; test::StreamSink sink{beast::Severity::Trace}; beast::Journal const jlog{sink}; ApplyContext ac = createApplyContext(env, ov, jlog); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); WasmHostFunctionsImpl hfs(ac, dummyEscrow); VirtualRuntime vrt; auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); std::string const msg = "test trace"; std::string data = "abc"; auto const slice = Slice(data.data(), data.size()); // hfs.trace(msg, slice, false); { vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); vrt.setBytes(256, slice.data(), slice.size()); WasmValVec params(5), result(1); auto* trap = ww(trace_wrap, &import.at("trace"), params, result, 0, msg.size(), 256, slice.size(), 0); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0)) { auto const messages = sink.messages().str(); BEAST_EXPECT(messages.find(msg) != std::string::npos); } } // hfs.trace(msg, slice, true); { vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); vrt.setBytes(256, slice.data(), slice.size()); WasmValVec params(5), result(1); auto* trap = ww(trace_wrap, &import.at("trace"), params, result, 0, msg.size(), 256, slice.size(), 1); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0)) { auto const messages = sink.messages().str(); std::string hex; hex.reserve(data.size() * 2); boost::algorithm::hex(data.begin(), data.end(), std::back_inserter(hex)); BEAST_EXPECT(messages.find(msg) != std::string::npos); BEAST_EXPECT(messages.find(hex) != std::string::npos); } } } { // logs disabled (trace < error) Env env(*this); OpenView ov{*env.current()}; test::StreamSink sink{beast::Severity::Error}; beast::Journal const jlog{sink}; ApplyContext ac = createApplyContext(env, ov, jlog); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); WasmHostFunctionsImpl hfs(ac, dummyEscrow); VirtualRuntime vrt; auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); std::string const msg = "test trace"; std::string data = "abc"; auto const slice = Slice(data.data(), data.size()); // hfs.trace(msg, slice, false); vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); vrt.setBytes(256, slice.data(), slice.size()); WasmValVec params(5), result(1); auto* trap = ww(trace_wrap, &import.at("trace"), params, result, 0, msg.size(), 256, slice.size(), 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0); auto const messages = sink.messages().str(); BEAST_EXPECT(messages.empty()); } } void testTraceNum() { testcase("traceNum"); using namespace test::jtx; { Env env(*this); OpenView ov{*env.current()}; test::StreamSink sink{beast::Severity::Trace}; beast::Journal const jlog{sink}; ApplyContext ac = createApplyContext(env, ov, jlog); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); WasmHostFunctionsImpl hfs(ac, dummyEscrow); VirtualRuntime vrt; auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); std::string const msg = "trace number"; int64_t const num = 123456789; // hfs.traceNum(msg, num); vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); WasmValVec params(3), result(1); auto* trap = ww(traceNum_wrap, &import.at("trace_num"), params, result, 0, msg.size(), num); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0)) { auto const messages = sink.messages().str(); BEAST_EXPECT(messages.find(msg) != std::string::npos); BEAST_EXPECT(messages.find(std::to_string(num)) != std::string::npos); } } { // logs disabled Env env(*this); OpenView ov{*env.current()}; test::StreamSink sink{beast::Severity::Error}; beast::Journal const jlog{sink}; ApplyContext ac = createApplyContext(env, ov, jlog); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); WasmHostFunctionsImpl hfs(ac, dummyEscrow); VirtualRuntime vrt; auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); std::string const msg = "trace number"; int64_t const num = 123456789; // hfs.traceNum(msg, num); vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); WasmValVec params(3), result(1); auto* trap = ww(traceNum_wrap, &import.at("trace_num"), params, result, 0, msg.size(), num); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0); auto const messages = sink.messages().str(); BEAST_EXPECT(messages.empty()); } } void testTraceAccount() { testcase("traceAccount"); using namespace test::jtx; { Env env(*this); OpenView ov{*env.current()}; test::StreamSink sink{beast::Severity::Trace}; beast::Journal const jlog{sink}; ApplyContext ac = createApplyContext(env, ov, jlog); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); WasmHostFunctionsImpl hfs(ac, dummyEscrow); VirtualRuntime vrt; auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); std::string const msg = "trace account"; auto const& accountId = env.master.id(); // hfs.traceAccount(msg, env.master.id()); vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); vrt.setBytes(256, accountId.data(), accountId.size()); WasmValVec params(4), result(1); auto* trap = ww(traceAccount_wrap, &import.at("trace_account"), params, result, 0, msg.size(), 256, accountId.size()); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0)) { auto const messages = sink.messages().str(); BEAST_EXPECT(messages.find(msg) != std::string::npos); BEAST_EXPECT(messages.find(env.master.human()) != std::string::npos); } } { // logs disabled Env env(*this); OpenView ov{*env.current()}; test::StreamSink sink{beast::Severity::Error}; beast::Journal const jlog{sink}; ApplyContext ac = createApplyContext(env, ov, jlog); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); WasmHostFunctionsImpl hfs(ac, dummyEscrow); VirtualRuntime vrt; auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); std::string msg = "trace account"; auto const& accountId = env.master.id(); // hfs.traceAccount(msg, env.master.id()); vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); vrt.setBytes(256, accountId.data(), accountId.size()); WasmValVec params(4), result(1); auto* trap = ww(traceAccount_wrap, &import.at("trace_account"), params, result, 0, msg.size(), 256, accountId.size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0); auto const messages = sink.messages().str(); BEAST_EXPECT(messages.empty()); } } void testTraceAmount() { testcase("traceAmount"); using namespace test::jtx; { Env env(*this); OpenView ov{*env.current()}; test::StreamSink sink{beast::Severity::Trace}; beast::Journal const jlog{sink}; ApplyContext ac = createApplyContext(env, ov, jlog); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); WasmHostFunctionsImpl hfs(ac, dummyEscrow); VirtualRuntime vrt; auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); std::string const msg = "trace amount"; STAmount const amount = XRP(12345); { // hfs.traceAmount(msg, amount); Bytes amountBytes = toBytes(amount); vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); vrt.setBytes(256, amountBytes.data(), amountBytes.size()); WasmValVec params(4), result(1); auto* trap = ww(traceAmount_wrap, &import.at("trace_amount"), params, result, 0, msg.size(), 256, amountBytes.size()); if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0)) { auto const messages = sink.messages().str(); BEAST_EXPECT(messages.find(msg) != std::string::npos); BEAST_EXPECT(messages.find(amount.getFullText()) != std::string::npos); } } // IOU amount Account const alice("alice"); env.fund(XRP(1000), alice); env.close(); STAmount const iouAmount = env.master["USD"](100); { // hfs.traceAmount(msg, iouAmount); Bytes amountBytes = toBytes(iouAmount); vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); vrt.setBytes(256, amountBytes.data(), amountBytes.size()); WasmValVec params(4), result(1); auto* trap = ww(traceAmount_wrap, &import.at("trace_amount"), params, result, 0, msg.size(), 256, amountBytes.size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0); } // MPT amount { auto const mptId = makeMptID(42, env.master.id()); Asset const mptAsset = Asset(mptId); STAmount const mptAmount(mptAsset, 123456); // hfs.traceAmount(msg, mptAmount); Bytes amountBytes = toBytes(mptAmount); vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); vrt.setBytes(256, amountBytes.data(), amountBytes.size()); WasmValVec params(4), result(1); auto* trap = ww(traceAmount_wrap, &import.at("trace_amount"), params, result, 0, msg.size(), 256, amountBytes.size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0); } } { // logs disabled Env env(*this); OpenView ov{*env.current()}; test::StreamSink sink{beast::Severity::Error}; beast::Journal const jlog{sink}; ApplyContext ac = createApplyContext(env, ov, jlog); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); WasmHostFunctionsImpl hfs(ac, dummyEscrow); VirtualRuntime vrt; auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); std::string const msg = "trace amount"; STAmount const amount = XRP(12345); // hfs.traceAmount(msg, amount); Bytes amountBytes = toBytes(amount); vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); vrt.setBytes(256, amountBytes.data(), amountBytes.size()); WasmValVec params(4), result(1); auto* trap = ww(traceAmount_wrap, &import.at("trace_amount"), params, result, 0, msg.size(), 256, amountBytes.size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0); auto const messages = sink.messages().str(); BEAST_EXPECT(messages.empty()); } } // clang-format off int const normalExp = 18; Bytes const floatIntMin = {0xF3, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x00, 0x00, 0x00, 0x01}; // -2^63 Bytes const floatIntZero = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00}; // 0 Bytes const floatIntMax = {0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}; // 2^63-1 Bytes const floatUIntMax = {0x19, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, 0x00, 0x00, 0x00, 0x01}; // 2^64-1 Bytes const floatMaxExp = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00}; // 1e(Number::kMaxExponent + normalExp) Bytes const floatPreMaxExp = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF}; // 1e(Number::kMaxExponent + normalExp - 1) Bytes const floatMinusMaxExp = {0xF2, 0x1F, 0x49, 0x4C, 0x58, 0x9C, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00}; // -1e(Number::kMaxExponent + normalExp) Bytes const floatMinExp = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00}; // 1e(Number::kMinExponent - normalExp) Bytes const floatMax = {0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x80, 0x00}; // Number::kMaxRep e(Number::kMaxExponent - normalExp) Bytes const floatMaxIOU = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x63, 0xFF, 0x9C, 0x00, 0x00, 0x00, 0x4E}; // 9999999999999999e(96) Bytes const floatMinIOU = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x9D}; // 1e(-96 - 3 + normalExp = -81) Bytes const float1 = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // 1 Bytes const floatMinus1 = {0xF2, 0x1F, 0x49, 0x4C, 0x58, 0x9C, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // -1 Bytes const float1More = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x03, 0xE8, 0xFF, 0xFF, 0xFF, 0xEE}; // 1.000 000 000 000 001 Bytes const float2 = {0x1B, 0xC1, 0x6D, 0x67, 0x4E, 0xC8, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // 2 Bytes const float10 = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEF}; // 10 Bytes const floatPi = {0x2B, 0x99, 0x2D, 0xDF, 0xA2, 0x32, 0x48, 0xE8, 0xFF, 0xFF, 0xFF, 0xEE}; // 3.141592653589793 Bytes const floatInvalidZero = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00}; // INVALID Bytes const floatMinus3 = {0xD6, 0x5D, 0xDB, 0xE5, 0x09, 0xD4, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // -3 std::string const invalid = "invalid_data"; // clang-format on template void printFloats(std::string_view descr, T m, int e) { Serializer msg; Number n; if constexpr (std::is_signed_v) { n = Number(static_cast(m), e); } else { n = Number(static_cast(m), e, Number::Normalized{}); } STNumber(sfNumber, n).add(msg); auto const& data = msg.modData(); std::cout << std::setw(24) << descr << " m: " << std::setw(20) << n.mantissa() << ", e: " << std::setw(8) << n.exponent() << ", hex: "; std::cout << std::hex << std::uppercase << std::setfill('0'); for (auto const& c : data) std::cout << std::setw(2) << (unsigned)c << " "; std::cout << std::dec << std::setfill(' ') << std::endl; } void printNumbersBin() { printFloats("int64.min", std::numeric_limits::min(), 0); printFloats("zero", 0, 0); printFloats("int64.max", std::numeric_limits::max(), 0); printFloats("uint64.max", std::numeric_limits::max(), 0); printFloats("Number 1 max exp", 1, Number::kMaxExponent + normalExp); printFloats("Number (max exp - 1)", 1, Number::kMaxExponent + normalExp - 1); printFloats("Number -1 max exp", -1, Number::kMaxExponent + normalExp); printFloats("Number.max", Number::kMaxRep, Number::kMaxExponent); printFloats("Number min positive", 1, Number::kMinExponent + normalExp); printFloats( "Number.min", std::numeric_limits::min(), Number::kMaxExponent - normalExp); printFloats("STAmount.max", STAmount::kMaxValue, STAmount::kMaxOffset); printFloats("STAmount min positive", STAmount::kMinValue, STAmount::kMinOffset); printFloats("one", 1, 0); printFloats("-one", -1, 0); printFloats("1,00...01", 1'000'000'000'000'001, -15); printFloats("two", 2, 0); printFloats("ten", 10, 0); printFloats("pi", 3141592653589793, -15); printFloats("-three", -3, 0); return; } void testTraceFloat() { testcase("traceFloat"); using namespace test::jtx; { Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); WasmHostFunctionsImpl hfs(ac, dummyEscrow); VirtualRuntime vrt; auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); std::string const msg = "trace float"; { // hfs.traceFloat(msg, makeSlice(invalid)); vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); vrt.setBytes(256, reinterpret_cast(invalid.data()), invalid.size()); WasmValVec params(4), result(1); auto* trap = ww(traceFloat_wrap, &import.at("trace_opaque_float"), params, result, 0, msg.size(), 256, invalid.size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0); } { // hfs.traceFloat(msg, makeSlice(floatMaxExp)); vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); vrt.setBytes(256, floatMaxExp.data(), floatMaxExp.size()); WasmValVec params(4), result(1); auto* trap = ww(traceFloat_wrap, &import.at("trace_opaque_float"), params, result, 0, msg.size(), 256, floatMaxExp.size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0); } } { // logs disabled Env env(*this); OpenView ov{*env.current()}; test::StreamSink sink{beast::Severity::Error}; beast::Journal const jlog{sink}; ApplyContext ac = createApplyContext(env, ov, jlog); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); WasmHostFunctionsImpl hfs(ac, dummyEscrow); VirtualRuntime vrt; auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); std::string const msg = "trace float"; // hfs.traceFloat(msg, makeSlice(invalid)); vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); vrt.setBytes(256, reinterpret_cast(invalid.data()), invalid.size()); WasmValVec params(4), result(1); auto* trap = ww(traceFloat_wrap, &import.at("trace_opaque_float"), params, result, 0, msg.size(), 256, invalid.size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0); auto const messages = sink.messages().str(); BEAST_EXPECT(messages.empty()); } } void testFloatFromInt() { testcase("floatFromInt"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.floatFromInt(min64, -1); WasmValVec params(4), result(1); auto* trap = ww(floatFromInt_wrap, &import.at("float_from_int"), params, result, min64, 0, floatSize, -1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatFromInt(min64, 4); WasmValVec params(4), result(1); auto* trap = ww(floatFromInt_wrap, &import.at("float_from_int"), params, result, min64, 0, floatSize, 4); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatFromInt(min64, 0); WasmValVec params(4), result(1); auto* trap = ww(floatFromInt_wrap, &import.at("float_from_int"), params, result, min64, 0, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 1); BEAST_EXPECT(resultBytes == floatIntMin); } { // hfs.floatFromInt(0, 0); WasmValVec params(4), result(1); auto* trap = ww(floatFromInt_wrap, &import.at("float_from_int"), params, result, 0ll, 0, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 1); BEAST_EXPECT(resultBytes == floatIntZero); } { // hfs.floatFromInt(max64, 0); WasmValVec params(4), result(1); auto* trap = ww(floatFromInt_wrap, &import.at("float_from_int"), params, result, max64, 0, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 1); BEAST_EXPECT(resultBytes == floatIntMax); } } void testFloatFromUint() { testcase("floatFromUint"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.floatFromUint(std::numeric_limits::min(), -1); WasmValVec params(5), result(1); uint64_t val = std::numeric_limits::min(); vrt.setBytes(0, &val, sizeof(val)); auto* trap = ww(floatFromUint_wrap, &import.at("float_from_uint"), params, result, 0, 8, 16, floatSize, -1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatFromUint(std::numeric_limits::min(), 4); WasmValVec params(5), result(1); uint64_t val = std::numeric_limits::min(); vrt.setBytes(0, &val, sizeof(val)); auto* trap = ww(floatFromUint_wrap, &import.at("float_from_uint"), params, result, 0, 8, 16, floatSize, 4); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatFromUint(0, 0); WasmValVec params(5), result(1); uint64_t val = 0; vrt.setBytes(0, &val, sizeof(val)); auto* trap = ww(floatFromUint_wrap, &import.at("float_from_uint"), params, result, 0, 8, 16, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 2); BEAST_EXPECT(resultBytes == floatIntZero); } { // hfs.floatFromUint(std::numeric_limits::max(), 0); WasmValVec params(5), result(1); uint64_t val = std::numeric_limits::max(); vrt.setBytes(0, &val, sizeof(val)); auto* trap = ww(floatFromUint_wrap, &import.at("float_from_uint"), params, result, 0, 8, 16, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 2); BEAST_EXPECT(resultBytes == floatUIntMax); } } void testfloatFromMantExp() { testcase("floatFromMantExp"); using namespace test::jtx; using namespace wasm_float; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.floatFromMantExp(1, 0, -1); WasmValVec params(5), result(1); auto* trap = ww(floatFromMantExp_wrap, &import.at("float_compare"), params, result, 1ll, 0, 0, floatSize, -1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatFromMantExp(1, 0, 4); WasmValVec params(5), result(1); auto* trap = ww(floatFromMantExp_wrap, &import.at("float_compare"), params, result, 1ll, 0, 0, floatSize, 4); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatFromMantExp(1, Number::kMaxExponent + normalExp + 1, 0); WasmValVec params(5), result(1); auto* trap = ww(floatFromMantExp_wrap, &import.at("float_compare"), params, result, 1ll, Number::kMaxExponent + normalExp + 1, 0, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatFromMantExp(1, Number::kMinExponent + normalExp - 1, 0); WasmValVec params(5), result(1); auto* trap = ww(floatFromMantExp_wrap, &import.at("float_compare"), params, result, 1ll, Number::kMinExponent + normalExp - 1, 0, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 2); BEAST_EXPECT(resultBytes == floatIntZero); } { // hfs.floatFromMantExp(1, Number::kMaxExponent + normalExp, 0); WasmValVec params(5), result(1); auto* trap = ww(floatFromMantExp_wrap, &import.at("float_compare"), params, result, 1ll, Number::kMaxExponent + normalExp, 0, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 2); BEAST_EXPECT(resultBytes == floatMaxExp); } { // hfs.floatFromMantExp(-1, Number::kMaxExponent + normalExp, 0); WasmValVec params(5), result(1); auto* trap = ww(floatFromMantExp_wrap, &import.at("float_compare"), params, result, -1ll, Number::kMaxExponent + normalExp, 0, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 2); BEAST_EXPECT(resultBytes == floatMinusMaxExp); } { // hfs.floatFromMantExp(1, Number::kMaxExponent + normalExp - 1, 0); WasmValVec params(5), result(1); auto* trap = ww(floatFromMantExp_wrap, &import.at("float_compare"), params, result, 1ll, Number::kMaxExponent + normalExp - 1, 0, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 2); BEAST_EXPECT(resultBytes == floatPreMaxExp); } { // hfs.floatFromMantExp(STAmount::kMaxValue, STAmount::kMaxOffset, 0); WasmValVec params(5), result(1); auto* trap = ww(floatFromMantExp_wrap, &import.at("float_compare"), params, result, static_cast(STAmount::kMaxValue), STAmount::kMaxOffset, 0, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 2); BEAST_EXPECT(resultBytes == floatMaxIOU); } { // hfs.floatFromMantExp(1, Number::kMinExponent + normalExp, 0); WasmValVec params(5), result(1); auto* trap = ww(floatFromMantExp_wrap, &import.at("float_compare"), params, result, 1ll, Number::kMinExponent - normalExp, 0, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 2); BEAST_EXPECT(resultBytes == floatMinExp); } { // hfs.floatFromMantExp(10, -1, 0); WasmValVec params(5), result(1); auto* trap = ww(floatFromMantExp_wrap, &import.at("float_compare"), params, result, 10ll, -1, 0, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 2); BEAST_EXPECT(resultBytes == float1); } { // hfs.floatFromMantExp(1, Number::kMaxExponent + normalExp + 1, 0); WasmValVec params(5), result(1); auto* trap = ww(floatFromMantExp_wrap, &import.at("float_compare"), params, result, 1ll, Number::kMaxExponent + normalExp + 1, 0, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } } void testFloatCompare() { testcase("floatCompare"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.floatCompare(Slice(), Slice()); WasmValVec params(4), result(1); auto* trap = ww(floatCompare_wrap, &import.at("float_add"), params, result, 0, 0, 0, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatCompare(makeSlice(floatInvalidZero), Slice()); WasmValVec params(4), result(1); vrt.setBytes(0, floatInvalidZero.data(), floatInvalidZero.size()); auto* trap = ww(floatCompare_wrap, &import.at("float_add"), params, result, 0, floatSize, 0, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatCompare(makeSlice(float1), makeSlice(invalid)); WasmValVec params(4), result(1); vrt.setBytes(0, float1.data(), float1.size()); vrt.setBytes(floatSize, invalid.data(), invalid.size()); auto* trap = ww(floatCompare_wrap, &import.at("float_add"), params, result, 0, floatSize, floatSize, invalid.size()); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatCompare(makeSlice(floatIntMin), makeSlice(floatIntZero)); WasmValVec params(4), result(1); vrt.setBytes(0, floatIntMin.data(), floatIntMin.size()); vrt.setBytes(floatSize, floatIntZero.data(), floatIntZero.size()); auto* trap = ww(floatCompare_wrap, &import.at("float_add"), params, result, 0, floatSize, floatSize, floatSize); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 2); } { // hfs.floatCompare(makeSlice(floatIntMax), makeSlice(floatIntZero)); WasmValVec params(4), result(1); vrt.setBytes(0, floatIntMax.data(), floatIntMax.size()); vrt.setBytes(floatSize, floatIntZero.data(), floatIntZero.size()); auto* trap = ww(floatCompare_wrap, &import.at("float_add"), params, result, 0, floatSize, floatSize, floatSize); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 1); } { // hfs.floatCompare(makeSlice(float1), makeSlice(float1)); WasmValVec params(4), result(1); vrt.setBytes(0, float1.data(), float1.size()); vrt.setBytes(floatSize, float1.data(), float1.size()); auto* trap = ww(floatCompare_wrap, &import.at("float_add"), params, result, 0, floatSize, floatSize, floatSize); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 0); } } void testFloatAdd() { testcase("floatAdd"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.floatAdd(Slice(), Slice(), -1); WasmValVec params(7), result(1); auto* trap = ww(floatAdd_wrap, &import.at("float_subtract"), params, result, 0, 0, 0, 0, 0, floatSize, -1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatAdd(Slice(), Slice(), 0); WasmValVec params(7), result(1); auto* trap = ww(floatAdd_wrap, &import.at("float_subtract"), params, result, 0, 0, 0, 0, 0, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatAdd(makeSlice(float1), makeSlice(invalid), 0); WasmValVec params(7), result(1); vrt.setBytes(0, float1.data(), float1.size()); vrt.setBytes(floatSize, invalid.data(), invalid.size()); auto* trap = ww(floatAdd_wrap, &import.at("float_subtract"), params, result, 0, floatSize, floatSize, invalid.size(), 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatAdd(makeSlice(floatMaxIOU), makeSlice(floatMaxExp), 0); // max IOU is too small to make any change WasmValVec params(7), result(1); vrt.setBytes(0, floatMaxIOU.data(), floatMaxIOU.size()); vrt.setBytes(floatSize, floatMaxExp.data(), floatMaxExp.size()); auto* trap = ww(floatAdd_wrap, &import.at("float_subtract"), params, result, 0, floatSize, floatSize, floatSize, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 4); BEAST_EXPECT(resultBytes == floatMaxExp); } { // hfs.floatAdd(makeSlice(floatIntMin), makeSlice(floatIntZero), 0); WasmValVec params(7), result(1); vrt.setBytes(0, floatIntMin.data(), floatIntMin.size()); vrt.setBytes(floatSize, floatIntZero.data(), floatIntZero.size()); auto* trap = ww(floatAdd_wrap, &import.at("float_subtract"), params, result, 0, floatSize, floatSize, floatSize, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 4); BEAST_EXPECT(resultBytes == floatIntMin); } { // hfs.floatAdd(makeSlice(floatIntMax), makeSlice(floatIntMin), 0);// // Number can't hold int64.min, it is rounded and we get -3, not -1 WasmValVec params(7), result(1); vrt.setBytes(0, floatIntMax.data(), floatIntMax.size()); vrt.setBytes(floatSize, floatIntMin.data(), floatIntMin.size()); auto* trap = ww(floatAdd_wrap, &import.at("float_subtract"), params, result, 0, floatSize, floatSize, floatSize, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 4); BEAST_EXPECT(resultBytes == floatMinus3); } } void testFloatSubtract() { testcase("floatSubtract"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.floatSubtract(Slice(), Slice(), -1); WasmValVec params(7), result(1); auto* trap = ww(floatSubtract_wrap, &import.at("float_from_mant_exp"), params, result, 0, 0, 0, 0, 0, floatSize, -1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatSubtract(Slice(), Slice(), 0); WasmValVec params(7), result(1); auto* trap = ww(floatSubtract_wrap, &import.at("float_from_mant_exp"), params, result, 0, 0, 0, 0, 0, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatSubtract(makeSlice(float1), makeSlice(invalid), 0); WasmValVec params(7), result(1); vrt.setBytes(0, float1.data(), float1.size()); vrt.setBytes(floatSize, invalid.data(), invalid.size()); auto* trap = ww(floatSubtract_wrap, &import.at("float_from_mant_exp"), params, result, 0, floatSize, floatSize, invalid.size(), floatSize * 2, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatSubtract(makeSlice(floatMinusMaxExp), makeSlice(floatMaxIOU), 0); WasmValVec params(7), result(1); vrt.setBytes(0, floatMinusMaxExp.data(), floatMinusMaxExp.size()); vrt.setBytes(floatSize, floatMaxIOU.data(), floatMaxIOU.size()); auto* trap = ww(floatSubtract_wrap, &import.at("float_from_mant_exp"), params, result, 0, floatSize, floatSize, floatSize, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 4); BEAST_EXPECT(resultBytes == floatMinusMaxExp); } { // hfs.floatSubtract(makeSlice(floatIntMin), makeSlice(floatIntZero), 0); WasmValVec params(7), result(1); vrt.setBytes(0, floatIntMin.data(), floatIntMin.size()); vrt.setBytes(floatSize, floatIntZero.data(), floatIntZero.size()); auto* trap = ww(floatSubtract_wrap, &import.at("float_from_mant_exp"), params, result, 0, floatSize, floatSize, floatSize, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 4); BEAST_EXPECT(resultBytes == floatIntMin); } { // hfs.floatSubtract(makeSlice(floatIntZero), makeSlice(float1), 0); WasmValVec params(7), result(1); vrt.setBytes(0, floatIntZero.data(), floatIntZero.size()); vrt.setBytes(floatSize, float1.data(), float1.size()); auto* trap = ww(floatSubtract_wrap, &import.at("float_from_mant_exp"), params, result, 0, floatSize, floatSize, floatSize, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 4); BEAST_EXPECT(resultBytes == floatMinus1); } } void testFloatMultiply() { testcase("floatMultiply"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.floatMultiply(Slice(), Slice(), -1); WasmValVec params(7), result(1); auto* trap = ww(floatMultiply_wrap, &import.at("float_multiply"), params, result, 0, 0, 0, 0, 0, floatSize, -1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatMultiply(Slice(), Slice(), 0); WasmValVec params(7), result(1); auto* trap = ww(floatMultiply_wrap, &import.at("float_multiply"), params, result, 0, 0, 0, 0, 0, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatMultiply(makeSlice(float1), makeSlice(invalid), 0); WasmValVec params(7), result(1); vrt.setBytes(0, float1.data(), float1.size()); vrt.setBytes(floatSize, invalid.data(), invalid.size()); auto* trap = ww(floatMultiply_wrap, &import.at("float_multiply"), params, result, 0, floatSize, floatSize, invalid.size(), 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatMultiply(makeSlice(floatMax), makeSlice(float1More), 0); WasmValVec params(7), result(1); vrt.setBytes(0, floatMax.data(), floatMax.size()); vrt.setBytes(floatSize, float1More.data(), float1More.size()); auto* trap = ww(floatMultiply_wrap, &import.at("float_multiply"), params, result, 0, floatSize, floatSize, floatSize, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatComputationError)); } { // hfs.floatMultiply(makeSlice(float1), makeSlice(float1), 0); WasmValVec params(7), result(1); vrt.setBytes(0, float1.data(), float1.size()); vrt.setBytes(floatSize, float1.data(), float1.size()); auto* trap = ww(floatMultiply_wrap, &import.at("float_multiply"), params, result, 0, floatSize, floatSize, floatSize, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 4); BEAST_EXPECT(resultBytes == float1); } { // hfs.floatMultiply(makeSlice(floatIntZero), makeSlice(floatMaxIOU), 0); WasmValVec params(7), result(1); vrt.setBytes(0, floatIntZero.data(), floatIntZero.size()); vrt.setBytes(floatSize, floatMaxIOU.data(), floatMaxIOU.size()); auto* trap = ww(floatMultiply_wrap, &import.at("float_multiply"), params, result, 0, floatSize, floatSize, floatSize, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 4); BEAST_EXPECT(resultBytes == floatIntZero); } { // hfs.floatMultiply(makeSlice(float10), makeSlice(floatPreMaxExp), 0); WasmValVec params(7), result(1); vrt.setBytes(0, float10.data(), float10.size()); vrt.setBytes(floatSize, floatPreMaxExp.data(), floatPreMaxExp.size()); auto* trap = ww(floatMultiply_wrap, &import.at("float_multiply"), params, result, 0, floatSize, floatSize, floatSize, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 4); BEAST_EXPECT(resultBytes == floatMaxExp); } } void testFloatDivide() { testcase("floatDivide"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.floatDivide(Slice(), Slice(), -1); WasmValVec params(7), result(1); auto* trap = ww(floatDivide_wrap, &import.at("float_divide"), params, result, 0, 0, 0, 0, 0, floatSize, -1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatDivide(Slice(), Slice(), 0); WasmValVec params(7), result(1); auto* trap = ww(floatDivide_wrap, &import.at("float_divide"), params, result, 0, 0, 0, 0, 0, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatDivide(makeSlice(float1), makeSlice(invalid), 0); WasmValVec params(7), result(1); vrt.setBytes(0, float1.data(), float1.size()); vrt.setBytes(floatSize, invalid.data(), invalid.size()); auto* trap = ww(floatDivide_wrap, &import.at("float_divide"), params, result, 0, floatSize, floatSize, invalid.size(), 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatDivide(makeSlice(float1), makeSlice(floatIntZero), 0); WasmValVec params(7), result(1); vrt.setBytes(0, float1.data(), float1.size()); vrt.setBytes(floatSize, floatIntZero.data(), floatIntZero.size()); auto* trap = ww(floatDivide_wrap, &import.at("float_divide"), params, result, 0, floatSize, floatSize, floatSize, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatComputationError)); } { // hfs.floatDivide(makeSlice(floatMax), makeSlice(*y), 0); auto const y = hfs.floatFromMantExp(STAmount::kMaxValue, -normalExp - 1, 0); // 0.9999999... if (BEAST_EXPECT(y)) { WasmValVec params(7), result(1); vrt.setBytes(0, floatMax.data(), floatMax.size()); vrt.setBytes(floatSize, y->data(), y->size()); auto* trap = ww(floatDivide_wrap, &import.at("float_divide"), params, result, 0, floatSize, floatSize, floatSize, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatComputationError)); } } { // hfs.floatDivide(makeSlice(floatIntZero), makeSlice(float1), 0); WasmValVec params(7), result(1); vrt.setBytes(0, floatIntZero.data(), floatIntZero.size()); vrt.setBytes(floatSize, float1.data(), float1.size()); auto* trap = ww(floatDivide_wrap, &import.at("float_divide"), params, result, 0, floatSize, floatSize, floatSize, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 4); BEAST_EXPECT(resultBytes == floatIntZero); } { // hfs.floatDivide(makeSlice(floatMaxExp), makeSlice(float10), 0); WasmValVec params(7), result(1); vrt.setBytes(0, floatMaxExp.data(), floatMaxExp.size()); vrt.setBytes(floatSize, float10.data(), float10.size()); auto* trap = ww(floatDivide_wrap, &import.at("float_divide"), params, result, 0, floatSize, floatSize, floatSize, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 4); BEAST_EXPECT(resultBytes == floatPreMaxExp); } } void testFloatRoot() { testcase("floatRoot"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.floatRoot(Slice(), 2, -1); WasmValVec params(6), result(1); auto* trap = ww(floatRoot_wrap, &import.at("float_root"), params, result, 0, 0, 2, 0, floatSize, -1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatRoot(makeSlice(invalid), 3, 0); WasmValVec params(6), result(1); vrt.setBytes(0, invalid.data(), invalid.size()); auto* trap = ww(floatRoot_wrap, &import.at("float_root"), params, result, 0, invalid.size(), 3, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatRoot(makeSlice(float1), -2, 0); WasmValVec params(6), result(1); vrt.setBytes(0, float1.data(), float1.size()); auto* trap = ww(floatRoot_wrap, &import.at("float_root"), params, result, 0, floatSize, -2, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatRoot(makeSlice(floatIntZero), 2, 0); WasmValVec params(6), result(1); vrt.setBytes(0, floatIntZero.data(), floatIntZero.size()); auto* trap = ww(floatRoot_wrap, &import.at("float_root"), params, result, 0, floatSize, 2, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 3); BEAST_EXPECT(resultBytes == floatIntZero); } { // hfs.floatRoot(makeSlice(floatMaxIOU), 1, 0); WasmValVec params(6), result(1); vrt.setBytes(0, floatMaxIOU.data(), floatMaxIOU.size()); auto* trap = ww(floatRoot_wrap, &import.at("float_root"), params, result, 0, floatSize, 1, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 3); BEAST_EXPECT(resultBytes == floatMaxIOU); } { // hfs.floatRoot(makeSlice(*x), 2, 0); auto const x = hfs.floatFromMantExp(100, 0, 0); // 100 if (BEAST_EXPECT(x)) { WasmValVec params(6), result(1); vrt.setBytes(0, x->data(), x->size()); auto* trap = ww(floatRoot_wrap, &import.at("float_root"), params, result, 0, floatSize, 2, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 3); BEAST_EXPECT(resultBytes == float10); } } { // hfs.floatRoot(makeSlice(*x), 3, 0); auto const x = hfs.floatFromMantExp(1000, 0, 0); // 1000 if (BEAST_EXPECT(x)) { WasmValVec params(6), result(1); vrt.setBytes(0, x->data(), x->size()); auto* trap = ww(floatRoot_wrap, &import.at("float_root"), params, result, 0, floatSize, 3, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 3); BEAST_EXPECT(resultBytes == float10); } } { // hfs.floatRoot(makeSlice(*x), 2, 0); auto const x = hfs.floatFromMantExp(1, -2, 0); // 0.01 auto const y = hfs.floatFromMantExp(1, -1, 0); // 0.1 if (BEAST_EXPECT(x && y)) { WasmValVec params(6), result(1); vrt.setBytes(0, x->data(), x->size()); auto* trap = ww(floatRoot_wrap, &import.at("float_root"), params, result, 0, floatSize, 2, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 3); BEAST_EXPECT(resultBytes == *y); } } } void testFloatPower() { testcase("floatPower"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.floatPower(Slice(), 2, -1); WasmValVec params(6), result(1); auto* trap = ww(floatPower_wrap, &import.at("float_pow"), params, result, 0, 0, 2, 0, floatSize, -1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatPower(makeSlice(invalid), 3, 0); WasmValVec params(6), result(1); vrt.setBytes(0, invalid.data(), invalid.size()); auto* trap = ww(floatPower_wrap, &import.at("float_pow"), params, result, 0, invalid.size(), 3, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatPower(makeSlice(float1), -2, 0); WasmValVec params(6), result(1); vrt.setBytes(0, float1.data(), float1.size()); auto* trap = ww(floatPower_wrap, &import.at("float_pow"), params, result, 0, floatSize, -2, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatPower(makeSlice(floatMax), 2, 0); WasmValVec params(6), result(1); vrt.setBytes(0, floatMax.data(), floatMax.size()); auto* trap = ww(floatPower_wrap, &import.at("float_pow"), params, result, 0, floatSize, 2, floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatComputationError)); } { // hfs.floatPower(makeSlice(floatMax), Number::kMaxExponent + 1, 0); WasmValVec params(6), result(1); vrt.setBytes(0, floatMax.data(), floatMax.size()); auto* trap = ww(floatPower_wrap, &import.at("float_pow"), params, result, 0, floatSize, Number::kMaxExponent + 1, floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatPower(makeSlice(floatMaxIOU), 0, 0); WasmValVec params(6), result(1); vrt.setBytes(0, floatMaxIOU.data(), floatMaxIOU.size()); auto* trap = ww(floatPower_wrap, &import.at("float_pow"), params, result, 0, floatSize, 0, floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 3); BEAST_EXPECT(resultBytes == float1); } { // hfs.floatPower(makeSlice(floatMaxIOU), 1, 0); WasmValVec params(6), result(1); vrt.setBytes(0, floatMaxIOU.data(), floatMaxIOU.size()); auto* trap = ww(floatPower_wrap, &import.at("float_pow"), params, result, 0, floatSize, 1, floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 3); BEAST_EXPECT(resultBytes == floatMaxIOU); } { // hfs.floatPower(makeSlice(float10), 2, 0); auto const x = hfs.floatFromMantExp(100, 0, 0); // 100 if (BEAST_EXPECT(x)) { WasmValVec params(6), result(1); vrt.setBytes(0, float10.data(), float10.size()); auto* trap = ww(floatPower_wrap, &import.at("float_pow"), params, result, 0, floatSize, 2, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 3); BEAST_EXPECT(resultBytes == *x); } } { // hfs.floatPower(makeSlice(*x), 2, 0); auto const x = hfs.floatFromMantExp(1, -1, 0); // 0.1 auto const y = hfs.floatFromMantExp(1, -2, 0); // 0.01 if (BEAST_EXPECT(x && y)) { WasmValVec params(6), result(1); vrt.setBytes(0, x->data(), x->size()); auto* trap = ww(floatPower_wrap, &import.at("float_pow"), params, result, 0, floatSize, 2, 2 * floatSize, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 3); BEAST_EXPECT(resultBytes == *y); } } } void testFloatSpecialCases() { using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); WasmHostFunctionsImpl const hfs(ac, dummyEscrow); testcase("float non-canonical"); { // non-canonical mantissa 100000e-4 Bytes const y = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x86, 0xA0, 0xFF, 0xFF, 0xFF, 0xFC}; auto const result = hfs.floatCompare(makeSlice(y), makeSlice(float10)); BEAST_EXPECT(result && *result == 0); } } void testFloatFromSTAmount() { testcase("floatFromSTAmount"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.floatFromSTAmount(amount, -1); STAmount const amount = XRP(100); Bytes amountBytes = toBytes(amount); vrt.setBytes(0, amountBytes.data(), amountBytes.size()); WasmValVec params(5), result(1); auto* trap = ww(floatFromSTAmount_wrap, &import.at("float_from_stamount"), params, result, 0, amountBytes.size(), 256, floatSize, -1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatFromSTAmount(amount, 4); STAmount const amount = XRP(100); Bytes amountBytes = toBytes(amount); vrt.setBytes(0, amountBytes.data(), amountBytes.size()); WasmValVec params(5), result(1); auto* trap = ww(floatFromSTAmount_wrap, &import.at("float_from_stamount"), params, result, 0, amountBytes.size(), 256, floatSize, 4); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatFromSTAmount(amount, 0); STAmount const amount = XRP(0); Bytes amountBytes = toBytes(amount); vrt.setBytes(0, amountBytes.data(), amountBytes.size()); WasmValVec params(5), result(1); auto* trap = ww(floatFromSTAmount_wrap, &import.at("float_from_stamount"), params, result, 0, amountBytes.size(), 256, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 2); BEAST_EXPECT(resultBytes == floatIntZero); } { // hfs.floatFromSTAmount(amount, 0); STAmount const amount = XRP(-1); auto const y = hfs.floatFromMantExp(-1 * 1'000'000, 0, 0); if (BEAST_EXPECT(y)) { Bytes amountBytes = toBytes(amount); vrt.setBytes(0, amountBytes.data(), amountBytes.size()); WasmValVec params(5), result(1); auto* trap = ww(floatFromSTAmount_wrap, &import.at("float_from_stamount"), params, result, 0, amountBytes.size(), 256, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 2); BEAST_EXPECT(resultBytes == *y); } } { // hfs.floatFromSTAmount(amount, 0); auto const y = hfs.floatFromMantExp(9223372036854776, 3, 0); STAmount const amount(noIssue(), std::numeric_limits::max()); Bytes amountBytes = toBytes(amount); vrt.setBytes(0, amountBytes.data(), amountBytes.size()); WasmValVec params(5), result(1); auto* trap = ww(floatFromSTAmount_wrap, &import.at("float_from_stamount"), params, result, 0, amountBytes.size(), 256, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 2); BEAST_EXPECT(resultBytes == *y); } { bool ex = false; try { STAmount const amount(noIssue(), -1, Number::kMaxExponent + normalExp); [[maybe_unused]] Bytes const amountBytes = toBytes(amount); } catch (...) { ex = true; } BEAST_EXPECT(ex); } auto const usd = env.master["USD"]; { // hfs.floatFromSTAmount(amount, 0); STAmount const amount( IOUAmount(STAmount::kMinValue, STAmount::kMinOffset), usd.issue()); Bytes amountBytes = toBytes(amount); vrt.setBytes(0, amountBytes.data(), amountBytes.size()); WasmValVec params(5), result(1); auto* trap = ww(floatFromSTAmount_wrap, &import.at("float_from_stamount"), params, result, 0, amountBytes.size(), 256, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 2); BEAST_EXPECT(resultBytes == floatMinIOU); } { // hfs.floatFromSTAmount(amount, 0); STAmount const amount( IOUAmount(STAmount::kMaxValue, STAmount::kMaxOffset), usd.issue()); Bytes amountBytes = toBytes(amount); vrt.setBytes(0, amountBytes.data(), amountBytes.size()); WasmValVec params(5), result(1); auto* trap = ww(floatFromSTAmount_wrap, &import.at("float_from_stamount"), params, result, 0, amountBytes.size(), 256, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 2); BEAST_EXPECT(resultBytes == floatMaxIOU); } } void testFloatFromSTNumber() { testcase("floatFromSTNumber"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); // Test with invalid rounding mode { // hfs.floatFromSTNumber(num, -1); STNumber const num(sfNumber, Number(123, 0)); Bytes numBytes = toBytes(num); vrt.setBytes(0, numBytes.data(), numBytes.size()); WasmValVec params(5), result(1); auto* trap = ww(floatFromSTNumber_wrap, &import.at("float_from_stnumber"), params, result, 0, numBytes.size(), 256, floatSize, -1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatFromSTNumber(num, 4); STNumber const num(sfNumber, Number(123, 0)); Bytes numBytes = toBytes(num); vrt.setBytes(0, numBytes.data(), numBytes.size()); WasmValVec params(5), result(1); auto* trap = ww(floatFromSTNumber_wrap, &import.at("float_from_stnumber"), params, result, 0, numBytes.size(), 256, floatSize, 4); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatFromSTNumber(num, 0); STNumber const num( sfNumber, Number(std::numeric_limits::max(), 0, Number::Normalized{})); Bytes numBytes = toBytes(num); vrt.setBytes(0, numBytes.data(), numBytes.size()); WasmValVec params(5), result(1); auto* trap = ww(floatFromSTNumber_wrap, &import.at("float_from_stnumber"), params, result, 0, numBytes.size(), 256, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 2); BEAST_EXPECT(resultBytes == floatUIntMax); } { // hfs.floatFromSTNumber(num, 0); STNumber const num(sfNumber, Number(-1, Number::kMaxExponent + normalExp)); Bytes numBytes = toBytes(num); vrt.setBytes(0, numBytes.data(), numBytes.size()); WasmValVec params(5), result(1); auto* trap = ww(floatFromSTNumber_wrap, &import.at("float_from_stnumber"), params, result, 0, numBytes.size(), 256, floatSize, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const resultBytes = vrt.getBytes(params, 2); BEAST_EXPECT(resultBytes == floatMinusMaxExp); } } void testFloatToInt() { testcase("floatToInt"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.floatToInt(makeSlice(float1), -1); vrt.setBytes(0, float1.data(), float1.size()); WasmValVec params(5), result(1); auto* trap = ww(floatToInt_wrap, &import.at("float_to_int"), params, result, 0, floatSize, 256, 8, -1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatToInt(makeSlice(float1), 4); vrt.setBytes(0, float1.data(), float1.size()); WasmValVec params(5), result(1); auto* trap = ww(floatToInt_wrap, &import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 4); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatToInt(Slice(), 0); WasmValVec params(5), result(1); auto* trap = ww(floatToInt_wrap, &import.at("float_to_int"), params, result, 0, 0, 256, 8, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatToInt(makeSlice(invalid), 0); vrt.setBytes(0, invalid.data(), invalid.size()); WasmValVec params(5), result(1); auto* trap = ww(floatToInt_wrap, &import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatToInt(makeSlice(floatIntZero), 0); vrt.setBytes(0, floatIntZero.data(), floatIntZero.size()); WasmValVec params(5), result(1); auto* trap = ww(floatToInt_wrap, &import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 8); auto const resultVal = vrt.getInt64(params, 2); BEAST_EXPECT(resultVal == 0); // roundtrip auto const result2 = hfs.floatFromInt(resultVal, 0); BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatIntZero); } { // hfs.floatToInt(makeSlice(float1), 0); vrt.setBytes(0, float1.data(), float1.size()); WasmValVec params(5), result(1); auto* trap = ww(floatToInt_wrap, &import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 8); auto const resultVal = vrt.getInt64(params, 2); BEAST_EXPECT(resultVal == 1); // roundtrip auto const result2 = hfs.floatFromInt(resultVal, 0); BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == float1); } { // hfs.floatToInt(makeSlice(floatMinus1), 0); vrt.setBytes(0, floatMinus1.data(), floatMinus1.size()); WasmValVec params(5), result(1); auto* trap = ww(floatToInt_wrap, &import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 8); auto const resultVal = vrt.getInt64(params, 2); BEAST_EXPECT(resultVal == -1); // roundtrip auto const result2 = hfs.floatFromInt(resultVal, 0); BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatMinus1); } { // hfs.floatToInt(makeSlice(floatIntMax), 0); vrt.setBytes(0, floatIntMax.data(), floatIntMax.size()); WasmValVec params(5), result(1); auto* trap = ww(floatToInt_wrap, &import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 8); auto const resultVal = vrt.getInt64(params, 2); BEAST_EXPECT(resultVal == std::numeric_limits::max()); // roundtrip auto const result2 = hfs.floatFromInt(resultVal, 0); BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatIntMax); } { // Number can't hold int64.min, it is rounded and we get int64_t.min - 3, which doesn't // fit into int64 // hfs.floatToInt(makeSlice(floatIntMin), 0); vrt.setBytes(0, floatIntMin.data(), floatIntMin.size()); WasmValVec params(5), result(1); auto* trap = ww(floatToInt_wrap, &import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatComputationError)); } { // hfs.floatToInt(makeSlice(floatUIntMax), 0); vrt.setBytes(0, floatUIntMax.data(), floatUIntMax.size()); WasmValVec params(5), result(1); auto* trap = ww(floatToInt_wrap, &import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatComputationError)); } // Test rounding modes with pi (3.141592653589793) { // to_nearest (mode 0): should round to 3 // hfs.floatToInt(makeSlice(floatPi), 0); vrt.setBytes(0, floatPi.data(), floatPi.size()); WasmValVec params(5), result(1); auto* trap = ww(floatToInt_wrap, &import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 8); auto const resultVal = vrt.getInt64(params, 2); BEAST_EXPECT(resultVal == 3); } { // towards_zero (mode 1): should truncate to 3 // hfs.floatToInt(makeSlice(floatPi), 1); vrt.setBytes(0, floatPi.data(), floatPi.size()); WasmValVec params(5), result(1); auto* trap = ww(floatToInt_wrap, &import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 8); auto const resultVal = vrt.getInt64(params, 2); BEAST_EXPECT(resultVal == 3); } { // downward (mode 2): should round down to 3 // hfs.floatToInt(makeSlice(floatPi), 2); vrt.setBytes(0, floatPi.data(), floatPi.size()); WasmValVec params(5), result(1); auto* trap = ww(floatToInt_wrap, &import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 2); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 8); auto const resultVal = vrt.getInt64(params, 2); BEAST_EXPECT(resultVal == 3); } { // upward (mode 3): should round up to 4 // hfs.floatToInt(makeSlice(floatPi), 3); vrt.setBytes(0, floatPi.data(), floatPi.size()); WasmValVec params(5), result(1); auto* trap = ww(floatToInt_wrap, &import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 3); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == 8); auto const resultVal = vrt.getInt64(params, 2); BEAST_EXPECT(resultVal == 4); } } void testFloatToMantExp() { testcase("floatToMantExp"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); { // hfs.floatToMantExp(makeSlice(invalid)); vrt.setBytes(0, invalid.data(), invalid.size()); WasmValVec params(6), result(1); auto* trap = ww(floatToMantExp_wrap, &import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT( result[0].of.i32 == static_cast(HostFunctionError::FloatInputMalformed)); } { // hfs.floatToMantExp(makeSlice(floatIntZero)); vrt.setBytes(0, floatIntZero.data(), floatIntZero.size()); WasmValVec params(6), result(1); auto* trap = ww(floatToMantExp_wrap, &import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const mantissa = vrt.getInt64(params, 2); auto const exponent = vrt.getInt32(params, 4); BEAST_EXPECT(mantissa == 0) && BEAST_EXPECT(exponent == std::numeric_limits::min()); // roundtrip auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0); BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatIntZero); } { // hfs.floatToMantExp(makeSlice(float1)); vrt.setBytes(0, float1.data(), float1.size()); WasmValVec params(6), result(1); auto* trap = ww(floatToMantExp_wrap, &import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const mantissa = vrt.getInt64(params, 2); auto const exponent = vrt.getInt32(params, 4); BEAST_EXPECT(mantissa == 1000000000000000000) && BEAST_EXPECT(exponent == -normalExp); // roundtrip auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0); BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == float1); } { // hfs.floatToMantExp(makeSlice(floatMinus1)); vrt.setBytes(0, floatMinus1.data(), floatMinus1.size()); WasmValVec params(6), result(1); auto* trap = ww(floatToMantExp_wrap, &import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const mantissa = vrt.getInt64(params, 2); auto const exponent = vrt.getInt32(params, 4); BEAST_EXPECT(mantissa == -1000000000000000000) && BEAST_EXPECT(exponent == -normalExp); // roundtrip auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0); BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatMinus1); } { // hfs.floatToMantExp(makeSlice(float10)); vrt.setBytes(0, float10.data(), float10.size()); WasmValVec params(6), result(1); auto* trap = ww(floatToMantExp_wrap, &import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const mantissa = vrt.getInt64(params, 2); auto const exponent = vrt.getInt32(params, 4); BEAST_EXPECT(mantissa == 1000000000000000000) && BEAST_EXPECT(exponent == -normalExp + 1); // roundtrip auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0); BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == float10); } { // hfs.floatToMantExp(makeSlice(floatPi)); vrt.setBytes(0, floatPi.data(), floatPi.size()); WasmValVec params(6), result(1); auto* trap = ww(floatToMantExp_wrap, &import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const mantissa = vrt.getInt64(params, 2); auto const exponent = vrt.getInt32(params, 4); BEAST_EXPECT(mantissa == 3141592653589793000) && BEAST_EXPECT(exponent == -normalExp); // roundtrip auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0); BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatPi); } { // hfs.floatToMantExp(makeSlice(floatIntMax)); vrt.setBytes(0, floatIntMax.data(), floatIntMax.size()); WasmValVec params(6), result(1); auto* trap = ww(floatToMantExp_wrap, &import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const mantissa = vrt.getInt64(params, 2); auto const exponent = vrt.getInt32(params, 4); BEAST_EXPECT(mantissa == std::numeric_limits::max()) && BEAST_EXPECT(exponent == 0); // roundtrip auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0); BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatIntMax); } { // hfs.floatToMantExp(makeSlice(floatIntMin)); vrt.setBytes(0, floatIntMin.data(), floatIntMin.size()); WasmValVec params(6), result(1); auto* trap = ww(floatToMantExp_wrap, &import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const mantissa = vrt.getInt64(params, 2); auto const exponent = vrt.getInt32(params, 4); BEAST_EXPECT(mantissa == (std::numeric_limits::min() / 10) - 1) && BEAST_EXPECT(exponent == 1); // roundtrip auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0); BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatIntMin); } { // hfs.floatToMantExp(makeSlice(floatMax)); vrt.setBytes(0, floatMax.data(), floatMax.size()); WasmValVec params(6), result(1); auto* trap = ww(floatToMantExp_wrap, &import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == floatSize); auto const mantissa = vrt.getInt64(params, 2); auto const exponent = vrt.getInt32(params, 4); BEAST_EXPECT(mantissa == Number::kMaxRep) && BEAST_EXPECT(exponent == Number::kMaxExponent); // roundtrip auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0); BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatMax); } } void testFloats() { // for checking binary formats manually // printNumbersBin(); testTraceFloat(); testFloatFromInt(); testFloatFromUint(); testFloatFromSTAmount(); testFloatFromSTNumber(); testFloatToInt(); testFloatToMantExp(); testfloatFromMantExp(); testFloatCompare(); testFloatAdd(); testFloatSubtract(); testFloatMultiply(); testFloatDivide(); testFloatRoot(); testFloatPower(); testFloatSpecialCases(); } void testVectorIndexes() { testcase("WasmValVec indicies"); using namespace test::jtx; Env env{*this}; OpenView ov{*env.current()}; ApplyContext ac = createApplyContext(env, ov); auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); VirtualRuntime vrt; WasmHostFunctionsImpl hfs(ac, dummyEscrow); auto import = xrpl::createWasmImport(hfs); hfs.setRT(&vrt); bool ex = false; try { // hfs.getLedgerSqn(); WasmValVec params(2), result(1); // 3 parameters instead of 2 auto* trap = ww(getLedgerSqn_wrap, &import.at("get_ledger_sqn"), params, result, 0, sizeof(std::uint32_t), 1); BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && BEAST_EXPECT(result[0].of.i32 == sizeof(std::uint32_t)) && BEAST_EXPECT(vrt.getUint32(params, 0) == env.current()->header().seq); } catch (std::exception const& e) { BEAST_EXPECTS(e.what() == std::string("Out of bound"), e.what()); ex = true; } // const version ex = false; try { WasmValVec params(2); [[maybe_unused]] auto const x = params[2]; } catch (std::exception const& e) { BEAST_EXPECTS(e.what() == std::string("Out of bound"), e.what()); ex = true; } BEAST_EXPECT(ex); } void run() override { testGetLedgerSqn(); testGetParentLedgerTime(); testGetParentLedgerHash(); testGetBaseFee(); testIsAmendmentEnabled(); testCacheLedgerObj(); testGetTxField(); testGetCurrentLedgerObjField(); testGetLedgerObjField(); testGetTxNestedField(); testGetCurrentLedgerObjNestedField(); testGetLedgerObjNestedField(); testGetTxArrayLen(); testGetCurrentLedgerObjArrayLen(); testGetLedgerObjArrayLen(); testGetTxNestedArrayLen(); testGetCurrentLedgerObjNestedArrayLen(); testGetLedgerObjNestedArrayLen(); testUpdateData(); testCheckSignature(); testComputeSha512HalfHash(); testKeyletFunctions(); testGetNFT(); testGetNFTIssuer(); testGetNFTTaxon(); testGetNFTFlags(); testGetNFTTransferFee(); testGetNFTSerial(); testTrace(); testTraceNum(); testTraceAccount(); testTraceAmount(); testFloats(); testVectorIndexes(); } }; BEAST_DEFINE_TESTSUITE(HostFuncImpl, app, xrpl); } // namespace xrpl::test