From c5524e4881c070d37fbd7fba191486b3a54926d5 Mon Sep 17 00:00:00 2001 From: TimothyBanks Date: Mon, 17 Aug 2026 14:24:34 -0400 Subject: [PATCH] fix: Port WASM host function tests to new WASM design --- src/test/app/HostFuncImpl_test.cpp | 5429 ----------------- src/tests/libxrpl/tx/wasm/FloatFixture.h | 56 + src/tests/libxrpl/tx/wasm/NFTFixture.h | 69 + src/tests/libxrpl/tx/wasm/RealHostFixture.h | 341 +- .../tx/wasm/host_functions/AccountKeylet.cpp | 18 +- .../tx/wasm/host_functions/AmmKeylet.cpp | 25 +- .../tx/wasm/host_functions/BaseFee.cpp | 6 +- .../tx/wasm/host_functions/CacheLedgerObj.cpp | 32 +- .../tx/wasm/host_functions/CheckKeylet.cpp | 20 +- .../tx/wasm/host_functions/CheckSignature.cpp | 80 + .../wasm/host_functions/CredentialKeylet.cpp | 39 +- .../CurrentLedgerObjArrayLen.cpp | 56 + .../host_functions/CurrentLedgerObjField.cpp | 32 +- .../CurrentLedgerObjNestedArrayLen.cpp | 61 + .../CurrentLedgerObjNestedField.cpp | 122 + .../tx/wasm/host_functions/DelegateKeylet.cpp | 39 +- .../host_functions/DepositPreauthKeylet.cpp | 41 +- .../tx/wasm/host_functions/DidKeylet.cpp | 17 +- .../tx/wasm/host_functions/EscrowKeylet.cpp | 20 +- .../tx/wasm/host_functions/FloatAdd.cpp | 46 + .../tx/wasm/host_functions/FloatCompare.cpp | 48 + .../tx/wasm/host_functions/FloatDivide.cpp | 70 + .../tx/wasm/host_functions/FloatFromInt.cpp | 34 + .../wasm/host_functions/FloatFromMantExp.cpp | 68 + .../wasm/host_functions/FloatFromStAmount.cpp | 67 + .../wasm/host_functions/FloatFromStNumber.cpp | 39 + .../tx/wasm/host_functions/FloatFromUint.cpp | 33 + .../tx/wasm/host_functions/FloatMultiply.cpp | 55 + .../tx/wasm/host_functions/FloatPower.cpp | 71 + .../tx/wasm/host_functions/FloatRoot.cpp | 63 + .../tx/wasm/host_functions/FloatSubtract.cpp | 49 + .../tx/wasm/host_functions/FloatToInt.cpp | 70 + .../tx/wasm/host_functions/FloatToMantExp.cpp | 80 + .../libxrpl/tx/wasm/host_functions/GetNFT.cpp | 54 + .../host_functions/IsAmendmentEnabled.cpp | 21 +- .../wasm/host_functions/LedgerObjArrayLen.cpp | 59 + .../tx/wasm/host_functions/LedgerObjField.cpp | 80 + .../LedgerObjNestedArrayLen.cpp | 79 + .../host_functions/LedgerObjNestedField.cpp | 155 + .../tx/wasm/host_functions/LedgerSqn.cpp | 6 +- .../host_functions/MptokenIssuanceKeylet.cpp | 20 +- .../tx/wasm/host_functions/MptokenKeylet.cpp | 32 +- .../libxrpl/tx/wasm/host_functions/NFT.cpp | 39 - .../tx/wasm/host_functions/NFTFlags.cpp | 26 + .../tx/wasm/host_functions/NFTIssuer.cpp | 28 + .../tx/wasm/host_functions/NFTSequence.cpp | 26 + .../tx/wasm/host_functions/NFTTaxon.cpp | 19 + .../tx/wasm/host_functions/NFTTransferFee.cpp | 26 + .../host_functions/NftokenOfferKeylet.cpp | 19 +- .../tx/wasm/host_functions/OfferKeylet.cpp | 19 +- .../tx/wasm/host_functions/OracleKeylet.cpp | 17 +- .../wasm/host_functions/ParentLedgerHash.cpp | 6 +- .../wasm/host_functions/ParentLedgerTime.cpp | 8 +- .../wasm/host_functions/PaychannelKeylet.cpp | 42 +- .../PermissionedDomainedKeylet.cpp | 20 +- .../tx/wasm/host_functions/Sha512Half.cpp | 21 + .../wasm/host_functions/SignerListKeylet.cpp | 17 +- .../tx/wasm/host_functions/TicketKeylet.cpp | 19 +- .../libxrpl/tx/wasm/host_functions/Trace.cpp | 30 + .../wasm/host_functions/TrustLineKeylet.cpp | 53 +- .../tx/wasm/host_functions/TxArrayLen.cpp | 62 + .../tx/wasm/host_functions/TxField.cpp | 187 +- .../wasm/host_functions/TxNestedArrayLen.cpp | 64 + .../tx/wasm/host_functions/TxNestedField.cpp | 133 + .../tx/wasm/host_functions/UpdateData.cpp | 17 +- .../tx/wasm/host_functions/VaultKeylet.cpp | 19 +- 66 files changed, 2713 insertions(+), 6006 deletions(-) delete mode 100644 src/test/app/HostFuncImpl_test.cpp create mode 100644 src/tests/libxrpl/tx/wasm/FloatFixture.h create mode 100644 src/tests/libxrpl/tx/wasm/NFTFixture.h create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/CheckSignature.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/CurrentLedgerObjArrayLen.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/CurrentLedgerObjNestedArrayLen.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/CurrentLedgerObjNestedField.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/FloatAdd.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/FloatCompare.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/FloatDivide.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/FloatFromInt.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/FloatFromMantExp.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/FloatFromStAmount.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/FloatFromStNumber.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/FloatFromUint.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/FloatMultiply.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/FloatPower.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/FloatRoot.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/FloatSubtract.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/FloatToInt.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/FloatToMantExp.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/GetNFT.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/LedgerObjArrayLen.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/LedgerObjField.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/LedgerObjNestedArrayLen.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/LedgerObjNestedField.cpp delete mode 100644 src/tests/libxrpl/tx/wasm/host_functions/NFT.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/NFTFlags.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/NFTIssuer.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/NFTSequence.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/NFTTaxon.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/NFTTransferFee.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/Sha512Half.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/Trace.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/TxArrayLen.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/TxNestedArrayLen.cpp create mode 100644 src/tests/libxrpl/tx/wasm/host_functions/TxNestedField.cpp diff --git a/src/test/app/HostFuncImpl_test.cpp b/src/test/app/HostFuncImpl_test.cpp deleted file mode 100644 index a844c136af..0000000000 --- a/src/test/app/HostFuncImpl_test.cpp +++ /dev/null @@ -1,5429 +0,0 @@ -/* -#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 -#include -#include - -namespace xrpl::test { - - - -static ApplyContext -createApplyContext( - test::jtx::Env& env, - OpenView& ov, - beast::Journal j, - STTx const& tx = STTx(ttESCROW_FINISH, [](STObject&) {})) -{ - ApplyContext ac{env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, j}; - return ac; -} - -static ApplyContext -createApplyContext( - test::jtx::Env& env, - OpenView& ov, - STTx const& tx = STTx(ttESCROW_FINISH, [](STObject&) {})) -{ - return createApplyContext(env, ov, env.journal, tx); -} - -class VirtualRuntime : public WasmRuntimeWrapper -{ - Bytes buffer_; - std::int64_t gas_ = 1'000'000; - std::int64_t transferLimit_ = kWasmTransferLimit; - -public: - static constexpr std::int64_t transferDiff = 1024; - - VirtualRuntime() : buffer_(1024 * 1024) - { - } - - Wmem - getMem() override - { - return Wmem(buffer_.data(), buffer_.size()); - } - - std::int64_t - getGas() override - { - gas_ -= 100; - return gas_; - } - - std::int64_t - setGas(std::int64_t gas) override - { - if (gas == -2) - return -1; - - if (gas < 0) - { - gas_ = std::numeric_limits::max(); - } - else - { - gas_ = gas; - } - - return gas_; - } - - std::int64_t - getTransferLimit() override - { - transferLimit_ -= transferDiff; - return transferLimit_; - } - - [[nodiscard]] std::int64_t - getTestTransferLimit() const - { - return transferLimit_; - } - - std::int64_t - setTransferLimit(std::int64_t x) override - { - if (x == -2) - return -1; - - if (x < 0) - { - transferLimit_ = std::numeric_limits::max(); - } - else - { - transferLimit_ = x; - } - - return transferLimit_; - } - - 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 = 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 = 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(E&& e, P&& params, P&& result, Args... args) -{ - size_t idx = 0; - (ww_hlp(idx, e, params, std::forward(args)), ...); // NOLINT - return HostFuncMain_wrap(std::forward(e), params.get(), result.get()); // NOLINT -} - -// ww() packs only integral args as wasm params, so the scoped enum needs widening. -constexpr int32_t -traceDataTypeToInt(TraceDataType t) -{ - return static_cast(t); -} - -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 - testGetTxField() - { - testcase("getTxField"); - using namespace test::jtx; - - std::string const credIdHex = - "0011223344556677889900112233445566778899001122334455667788990011"; - uint256 credId; - BEAST_EXPECT(credId.parseHex(credIdHex)); - - Env env{*this}; - OpenView ov{*env.current()}; - STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) { - obj.setAccountID(sfAccount, env.master.id()); - obj.setAccountID(sfOwner, env.master.id()); - obj.setFieldU32(sfOfferSequence, env.seq(env.master)); - obj.setFieldArray(sfMemos, STArray{}); - STVector256 credIds; - credIds.pushBack(credId); - obj.setFieldV256(sfCredentialIDs, credIds); - }); - ApplyContext ac = createApplyContext(env, ov, stx); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - - { - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // hfs.getTxField(sfAccount); - { - WasmValVec params(3), result(1); - auto* trap = - ww(&import.at("tx_field"), - params, - result, - sfAccount.getCode(), - 0, - AccountID::size()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == AccountID::size()); - auto const accountBytes = vrt.getBytes(params, 1); - BEAST_EXPECT(std::ranges::equal(accountBytes, env.master.id())); - } - - // hfs.getTxField(sfOwner); - { - WasmValVec params(3), result(1); - auto* trap = - ww(&import.at("tx_field"), - params, - result, - sfOwner.getCode(), - 0, - AccountID::size()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == AccountID::size()); - auto const ownerBytes = vrt.getBytes(params, 1); - BEAST_EXPECT(std::ranges::equal(ownerBytes, env.master.id())); - } - - // hfs.getTxField(sfTransactionType); - { - WasmValVec params(3), result(1); - auto* trap = - ww(&import.at("tx_field"), params, result, sfTransactionType.getCode(), 0, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 > 0); - auto txTypeBytes = vrt.getBytes(params, 1); - txTypeBytes.resize(result[0].of.i32); - BEAST_EXPECT(txTypeBytes == toBytes(ttESCROW_FINISH)); - } - - // hfs.getTxField(sfOfferSequence); - { - WasmValVec params(3), result(1); - auto* trap = - ww(&import.at("tx_field"), params, result, sfOfferSequence.getCode(), 0, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 > 0); - auto offerSeqBytes = vrt.getBytes(params, 1); - offerSeqBytes.resize(result[0].of.i32); - BEAST_EXPECT(offerSeqBytes == toBytes(env.seq(env.master))); - } - - // hfs.getTxField(sfDestination); - { - WasmValVec params(3), result(1); - auto* trap = - ww(&import.at("tx_field"), params, result, sfDestination.getCode(), 0, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == static_cast(HostFunctionError::FieldNotFound)); - } - - // hfs.getTxField(sfMemos); - { - WasmValVec params(3), result(1); - auto* trap = ww(&import.at("tx_field"), params, result, sfMemos.getCode(), 0, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == static_cast(HostFunctionError::NotLeafField)); - } - - // hfs.getTxField(sfCredentialIDs); - { - WasmValVec params(3), result(1); - auto* trap = - ww(&import.at("tx_field"), params, result, sfCredentialIDs.getCode(), 0, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECTS( - result[0].of.i32 == static_cast(HostFunctionError::NotLeafField), - std::to_string(result[0].of.i32)); - } - - // hfs.getTxField(sfInvalid); - { - WasmValVec params(3), result(1); - auto* trap = - ww(&import.at("tx_field"), params, result, sfInvalid.getCode(), 0, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == static_cast(HostFunctionError::FieldNotFound)); - } - - // hfs.getTxField(sfGeneric); - { - WasmValVec params(3), result(1); - auto* trap = - ww(&import.at("tx_field"), params, result, sfGeneric.getCode(), 0, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == static_cast(HostFunctionError::FieldNotFound)); - } - } - - { - auto const iouAsset = env.master["USD"]; - STTx const stx2 = STTx(ttAMM_DEPOSIT, [&](auto& obj) { - obj.setAccountID(sfAccount, env.master.id()); - obj.setFieldIssue(sfAsset, STIssue{sfAsset, xrpIssue()}); - obj.setFieldIssue(sfAsset2, STIssue{sfAsset2, iouAsset.issue()}); - }); - ApplyContext ac2 = createApplyContext(env, ov, stx2); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac2, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // hfs.getTxField(sfAsset); - { - WasmValVec params(3), result(1); - auto* trap = ww(&import.at("tx_field"), params, result, sfAsset.getCode(), 0, 256); - - std::vector const expectedAsset(20, 0); - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 > 0); - auto assetBytes = vrt.getBytes(params, 1); - assetBytes.resize(result[0].of.i32); - BEAST_EXPECT(assetBytes == expectedAsset); - } - - // hfs.getTxField(sfAsset2); - { - WasmValVec params(3), result(1); - auto* trap = ww(&import.at("tx_field"), params, result, sfAsset2.getCode(), 0, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 > 0); - auto asset2Bytes = vrt.getBytes(params, 1); - asset2Bytes.resize(result[0].of.i32); - BEAST_EXPECT(asset2Bytes == toBytes(Asset(iouAsset))); - } - } - - { - auto const iouAsset = env.master["GBP"]; - auto const mptId = makeMptID(1, env.master); - STTx const stx2 = STTx(ttAMM_DEPOSIT, [&](auto& obj) { - obj.setAccountID(sfAccount, env.master.id()); - obj.setFieldIssue(sfAsset, STIssue{sfAsset, iouAsset.issue()}); - obj.setFieldIssue(sfAsset2, STIssue{sfAsset2, MPTIssue{mptId}}); - }); - ApplyContext ac2 = createApplyContext(env, ov, stx2); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac2, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // hfs.getTxField(sfAsset); - { - WasmValVec params(3), result(1); - auto* trap = ww(&import.at("tx_field"), params, result, sfAsset.getCode(), 0, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECT(result[0].of.i32 > 0)) - { - auto assetBytes = vrt.getBytes(params, 1); - assetBytes.resize(result[0].of.i32); - BEAST_EXPECT(assetBytes == toBytes(Asset(iouAsset))); - } - } - - // hfs.getTxField(sfAsset2); - { - WasmValVec params(3), result(1); - auto* trap = ww(&import.at("tx_field"), params, result, sfAsset2.getCode(), 0, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECT(result[0].of.i32 > 0)) - { - auto assetBytes = vrt.getBytes(params, 1); - assetBytes.resize(result[0].of.i32); - BEAST_EXPECT(assetBytes == toBytes(Asset(mptId))); - } - } - } - - { - std::uint8_t const expectedScale = 8; - STTx const stx2 = STTx(ttMPTOKEN_ISSUANCE_CREATE, [&](auto& obj) { - obj.setAccountID(sfAccount, env.master.id()); - obj.setFieldU8(sfAssetScale, expectedScale); - }); - ApplyContext ac2 = createApplyContext(env, ov, stx2); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac2, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // hfs.getTxField(sfAssetScale); - { - WasmValVec params(3), result(1); - auto* trap = - ww(&import.at("tx_field"), params, result, sfAssetScale.getCode(), 0, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECT(result[0].of.i32 > 0)) - { - auto assetBytes = vrt.getBytes(params, 1); - assetBytes.resize(result[0].of.i32); - BEAST_EXPECT(std::ranges::equal(assetBytes, toBytes(expectedScale))); - } - } - } - } - - void - testGetLedgerObjField() - { - testcase("getLedgerObjField"); - using namespace test::jtx; - using namespace std::chrono; - - Env env{*this}; - // Fund the account and create an escrow so the ledger object exists - env(escrow::create(env.master, env.master, XRP(100)), escrow::kFinishTime(env.now() + 1s)); - env.close(); - - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - - auto const accountKeylet = keylet::account(env.master.id()); - auto const escrowKeylet = - keylet::escrow(env.master.id(), SeqProxy::rawSequence(env.seq(env.master) - 1)); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, escrowKeylet); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // hfs.cacheLedgerObj(accountKeylet.key, 1); - { - WasmValVec params(3), result(1); - vrt.setBytes(0, accountKeylet.key.data(), uint256::size()); - auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 1); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 1); - } - - // hfs.getLedgerObjField(1, sfAccount); - { - WasmValVec params(4), result(1); - auto* trap = ww(&import.at("le_field"), params, result, 1, sfAccount.getCode(), 0, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) - { - auto accountBytes = vrt.getBytes(params, 2); - accountBytes.resize(result[0].of.i32); - BEAST_EXPECT(std::ranges::equal(accountBytes, env.master.id())); - } - } - - // hfs.getLedgerObjField(1, sfBalance); - { - WasmValVec params(4), result(1); - auto* trap = ww(&import.at("le_field"), params, result, 1, sfBalance.getCode(), 0, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECT(result[0].of.i32 > 0)) - { - auto balanceBytes = vrt.getBytes(params, 2); - balanceBytes.resize(result[0].of.i32); - BEAST_EXPECT(balanceBytes == toBytes(env.balance(env.master))); - } - } - - // hfs.getLedgerObjField(0, sfAccount); - { - WasmValVec params(4), result(1); - auto* trap = ww(&import.at("le_field"), params, result, 0, sfAccount.getCode(), 0, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == static_cast(HostFunctionError::SlotOutRange)); - } - - // hfs.getLedgerObjField(257, sfAccount); - { - WasmValVec params(4), result(1); - auto* trap = - ww(&import.at("le_field"), params, result, 257, sfAccount.getCode(), 0, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == static_cast(HostFunctionError::SlotOutRange)); - } - - // hfs.getLedgerObjField(2, sfAccount); - { - WasmValVec params(4), result(1); - auto* trap = ww(&import.at("le_field"), params, result, 2, sfAccount.getCode(), 0, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == static_cast(HostFunctionError::EmptySlot)); - } - - // hfs.getLedgerObjField(1, sfOwner); - { - WasmValVec params(4), result(1); - auto* trap = ww(&import.at("le_field"), params, result, 1, sfOwner.getCode(), 0, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == static_cast(HostFunctionError::FieldNotFound)); - } - } - - void - testGetTxNestedField() - { - testcase("getTxNestedField"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - - std::string const credIdHex = - "0011223344556677889900112233445566778899001122334455667788990011"; - uint256 credId; - BEAST_EXPECT(credId.parseHex(credIdHex)); - - // Create a transaction with a nested array field - STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) { - obj.setAccountID(sfAccount, env.master.id()); - STArray memos; - STObject memoObj(sfMemo); - memoObj.setFieldVL(sfMemoData, Slice("hello", 5)); - memos.push_back(memoObj); - obj.setFieldArray(sfMemos, memos); - STVector256 credIds; - credIds.pushBack(credId); - obj.setFieldV256(sfCredentialIDs, credIds); - }); - - ApplyContext ac = createApplyContext(env, ov, stx); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // hfs.getTxNestedField(locator); - { - // Locator for sfMemos[0].sfMemo.sfMemoData - // Locator is a sequence of int32_t codes: - // [sfMemos.getCode(), 0, sfMemoData.getCode()] - std::vector const locatorVec = {sfMemos.getCode(), 0, sfMemoData.getCode()}; - vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); - - WasmValVec params(4), result(1); - auto* trap = - ww(&import.at("tx_inner"), - params, - result, - 0, - locatorVec.size() * sizeof(int32_t), - 256, - 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) - { - auto memoDataBytes = vrt.getBytes(params, 2); - memoDataBytes.resize(result[0].of.i32); - std::string const memoData(memoDataBytes.begin(), memoDataBytes.end()); - BEAST_EXPECT(memoData == "hello"); - } - } - - // hfs.getTxNestedField(locator); - { - // Locator for sfCredentialIDs[0] - std::vector locatorVec = {sfCredentialIDs.getCode(), 0}; - vrt.setBytes( - 0, - reinterpret_cast(locatorVec.data()), - locatorVec.size() * sizeof(int32_t)); - - WasmValVec params(4), result(1); - auto* trap = - ww(&import.at("tx_inner"), - params, - result, - 0, - locatorVec.size() * sizeof(int32_t), - 256, - 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) - { - auto credIdBytes = vrt.getBytes(params, 2); - credIdBytes.resize(result[0].of.i32); - std::string const credIdResult(credIdBytes.begin(), credIdBytes.end()); - BEAST_EXPECT(strHex(credIdResult) == credIdHex); - } - } - - // hfs.getTxNestedField(locator); - { - // can use the nested locator for base fields too - std::vector locatorVec = {sfAccount.getCode()}; - vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); - - WasmValVec params(4), result(1); - auto* trap = - ww(&import.at("tx_inner"), - params, - result, - 0, - locatorVec.size() * sizeof(int32_t), - 256, - 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) - { - auto accountBytes = vrt.getBytes(params, 2); - accountBytes.resize(result[0].of.i32); - BEAST_EXPECT(std::ranges::equal(accountBytes, env.master.id())); - } - } - - // hfs.getTxNestedField(locator); - { - // unaligned locator - std::vector locatorVec(sizeof(int32_t) + 1); - auto const accountFieldCode = sfAccount.getCode(); - memcpy(locatorVec.data() + 1, &accountFieldCode, sizeof(int32_t)); - vrt.setBytes(0, locatorVec.data(), sizeof(int32_t) + 1); - - WasmValVec params(4), result(1); - auto* trap = ww(&import.at("tx_inner"), params, result, 1, sizeof(int32_t), 256, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) - { - auto accountBytes = vrt.getBytes(params, 2); - accountBytes.resize(result[0].of.i32); - BEAST_EXPECT(std::ranges::equal(accountBytes, env.master.id())); - } - } - - auto expectError = [&](std::vector const& locatorVec, - HostFunctionError expectedError) { - vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); - - WasmValVec params(4), result(1); - // hfs.getTxNestedField(locator); - auto* trap = - ww(&import.at("tx_inner"), - params, - result, - 0, - locatorVec.size() * sizeof(int32_t), - 256, - 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECTS( - result[0].of.i32 == hfErrorToInt(expectedError), std::to_string(result[0].of.i32)); - }; - - // hfs.getTxNestedField(locator); - // Locator for non-existent base field - expectError( - {sfSigners.getCode(), // sfSigners does not exist - 0, - sfAccount.getCode()}, - HostFunctionError::FieldNotFound); - - // hfs.getTxNestedField(locator); - // Locator for non-existent index - expectError( - {sfMemos.getCode(), - 1, // index 1 does not exist - sfMemoData.getCode()}, - HostFunctionError::IndexOutOfBounds); - - // hfs.getTxNestedField(locator); - // Locator for non-existent index - expectError( - {sfCredentialIDs.getCode(), 1}, // index 1 does not exist - HostFunctionError::IndexOutOfBounds); - - // hfs.getTxNestedField(locator); - // Locator for negative index (STArray) - expectError( - {sfMemos.getCode(), - -1, // negative index - sfMemoData.getCode()}, - HostFunctionError::IndexOutOfBounds); - - // hfs.getTxNestedField(locator); - // Locator for negative index (STVector256) - expectError( - {sfCredentialIDs.getCode(), -1}, // negative index - HostFunctionError::IndexOutOfBounds); - - // hfs.getTxNestedField(locator); - // Locator for non-existent nested field - expectError( - {sfMemos.getCode(), 0, sfURI.getCode()}, // sfURI does not exist in the memo - HostFunctionError::FieldNotFound); - - // hfs.getTxNestedField(locator); - // Locator for non-existent base sfield - expectError( - {fieldCode(20000, 20000), // nonexistent SField code - 0, - sfAccount.getCode()}, - HostFunctionError::InvalidField); - - // hfs.getTxNestedField(locator); - // Locator for non-existent nested sfield - expectError( - {sfMemos.getCode(), // nonexistent SField code - 0, - fieldCode(20000, 20000)}, - HostFunctionError::InvalidField); - - // hfs.getTxNestedField(locator); - // Locator for negative base sfield code (-1 = sfInvalid, exists in map but not in tx) - expectError( - {-1, // sfInvalid's field code - 0, - sfAccount.getCode()}, - HostFunctionError::FieldNotFound); - - // hfs.getTxNestedField(locator); - // Locator for zero base sfield code (0 = sfGeneric, exists in map but not in tx) - expectError( - {0, // sfGeneric's field code - 0, - sfAccount.getCode()}, - HostFunctionError::FieldNotFound); - - // hfs.getTxNestedField(locator); - // Locator for very negative base sfield code (not in knownCodeToField map) - expectError( - {std::numeric_limits::min(), 0, sfAccount.getCode()}, - HostFunctionError::InvalidField); - - // hfs.getTxNestedField(locator); - // Locator for negative nested sfield code in STObject context - // (sfMemos[0] is an STObject, then -1 is looked up as SField) - expectError( - {sfMemos.getCode(), 0, -1}, // -1 = sfInvalid, exists in map but not in memo object - HostFunctionError::FieldNotFound); - - // hfs.getTxNestedField(locator); - // Locator for STArray - expectError({sfMemos.getCode()}, HostFunctionError::NotLeafField); - - // hfs.getTxNestedField(locator); - // Locator for STVector256 - expectError({sfCredentialIDs.getCode()}, HostFunctionError::NotLeafField); - - // hfs.getTxNestedField(locator); - // Locator for nesting into non-array/object field - expectError( - {sfAccount.getCode(), // sfAccount is not an array or object - 0, - sfAccount.getCode()}, - HostFunctionError::LocatorMalformed); - - // hfs.getTxNestedField(locator); - // Locator for empty locator - expectError({}, HostFunctionError::LocatorMalformed); - - // hfs.getTxNestedField(locator); - // Locator for malformed locator (not multiple of 4) - { - std::vector locatorVec = {sfMemos.getCode()}; - vrt.setBytes(0, locatorVec.data(), 3); - - WasmValVec params(4), result(1); - auto* trap = ww(&import.at("tx_inner"), params, result, 0, 3, 256, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == static_cast(HostFunctionError::LocatorMalformed)); - } - } - - void - testGetCurrentLedgerObjNestedField() - { - testcase("getCurrentLedgerObjNestedField"); - using namespace test::jtx; - - Env env{*this}; - Account const alice("alice"); - Account const becky("becky"); - // Create a SignerList for env.master - env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); - - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - - // Find the signer ledger object - auto const signerKeylet = keylet::signerList(env.master.id()); - BEAST_EXPECT(env.le(signerKeylet)); - - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, signerKeylet); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // hfs.getCurrentLedgerObjNestedField(baseLocatorSlice); - // Locator for base field - { - std::vector baseLocator = {sfSignerQuorum.getCode()}; - vrt.setBytes(0, baseLocator.data(), baseLocator.size() * sizeof(int32_t)); - - WasmValVec params(4), result(1); - auto* trap = - ww(&import.at("home_le_inner"), - params, - result, - 0, - baseLocator.size() * sizeof(int32_t), - 256, - 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) - { - auto signerQuorumBytes = vrt.getBytes(params, 2); - signerQuorumBytes.resize(result[0].of.i32); - BEAST_EXPECT(signerQuorumBytes == toBytes(static_cast(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(&import.at("home_le_inner"), - params, - result, - 0, - locatorVec.size() * sizeof(int32_t), - 256, - 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECTS( - result[0].of.i32 == hfErrorToInt(expectedError), std::to_string(result[0].of.i32)); - }; - // hfs.getCurrentLedgerObjNestedField(locator); - // Locator for non-existent base field - expectError( - {sfSigners.getCode(), // sfSigners does not exist - 0, - sfAccount.getCode()}, - HostFunctionError::FieldNotFound); - - // hfs.getCurrentLedgerObjNestedField(locator); - // Locator for nesting into non-array/object field - expectError( - {sfSignerQuorum.getCode(), // sfSignerQuorum is not an array or object - 0, - sfAccount.getCode()}, - HostFunctionError::LocatorMalformed); - - // hfs.getCurrentLedgerObjNestedField(emptyLocator); - // Locator for empty locator - { - WasmValVec params(4), result(1); - auto* trap = ww(&import.at("home_le_inner"), params, result, 0, 0, 256, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == static_cast(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(&import.at("home_le_inner"), params, result, 0, 3, 256, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == static_cast(HostFunctionError::LocatorMalformed)); - } - - // hfs.getCurrentLedgerObjNestedField(locator); - { - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master) + 5)); - VirtualRuntime vrt2; - WasmHostFunctionsImpl dummyHfs(ac, dummyEscrow); - - auto import2 = xrpl::createWasmImport(dummyHfs); - dummyHfs.setRT(vrt2); - - std::vector const locatorVec = {sfAccount.getCode()}; - vrt2.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); - - WasmValVec params(4), result(1); - auto* trap = - ww(&import2.at("home_le_inner"), - params, - result, - 0, - locatorVec.size() * sizeof(int32_t), - 256, - 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECTS( - result[0].of.i32 == static_cast(HostFunctionError::LedgerObjNotFound), - std::to_string(result[0].of.i32)); - } - } - - void - testGetLedgerObjNestedField() - { - testcase("getLedgerObjNestedField"); - using namespace test::jtx; - - Env env{*this}; - Account const alice("alice"); - Account const becky("becky"); - // Create a SignerList for env.master - env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); - env.close(); - - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // Cache the SignerList ledger object in slot 1 - auto const signerListKeylet = keylet::signerList(env.master.id()); - // hfs.cacheLedgerObj(signerListKeylet.key, 1); - { - WasmValVec params(3), result(1); - vrt.setBytes(0, signerListKeylet.key.data(), uint256::size()); - auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 1); - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 1); - } - - // Locator for sfSignerEntries[0].sfAccount - { - std::vector const locatorVec = { - sfSignerEntries.getCode(), 0, sfAccount.getCode()}; - // hfs.getLedgerObjNestedField(1, locator); - vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("le_inner"), - params, - result, - 1, - 0, - locatorVec.size() * sizeof(int32_t), - 256, - 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) - { - auto aliceIdBytes = vrt.getBytes(params, 3); - aliceIdBytes.resize(result[0].of.i32); - BEAST_EXPECT(std::ranges::equal(aliceIdBytes, alice.id())); - } - } - - // Locator for sfSignerEntries[1].sfAccount - { - std::vector const locatorVec = { - sfSignerEntries.getCode(), 1, sfAccount.getCode()}; - // hfs.getLedgerObjNestedField(1, locator); - vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("le_inner"), - params, - result, - 1, - 0, - locatorVec.size() * sizeof(int32_t), - 256, - 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) - { - auto beckyIdBytes = vrt.getBytes(params, 3); - beckyIdBytes.resize(result[0].of.i32); - BEAST_EXPECT(std::ranges::equal(beckyIdBytes, becky.id())); - } - } - - // Locator for sfSignerEntries[0].sfSignerWeight - { - std::vector const locatorVec = { - sfSignerEntries.getCode(), 0, sfSignerWeight.getCode()}; - // hfs.getLedgerObjNestedField(1, locator); - vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("le_inner"), - params, - result, - 1, - 0, - locatorVec.size() * sizeof(int32_t), - 256, - 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) - { - // Should be 1 - auto const expected = toBytes(static_cast(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(&import.at("le_inner"), - params, - result, - 1, - 0, - locatorVec.size() * sizeof(int32_t), - 256, - 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) - { - auto const expected = toBytes(static_cast(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(&import.at("le_inner"), - params, - result, - slot, - 0, - locatorVec.size() * sizeof(int32_t), - 256, - 256); - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECTS( - result[0].of.i32 == hfErrorToInt(expectedError), std::to_string(result[0].of.i32)); - }; - - // Error: base field not found - expectError( - {sfSigners.getCode(), // sfSigners does not exist - 0, - sfAccount.getCode()}, - HostFunctionError::FieldNotFound); - - // Error: index out of bounds - expectError( - {sfSignerEntries.getCode(), - 2, // index 2 does not exist - sfAccount.getCode()}, - HostFunctionError::IndexOutOfBounds); - - // Error: nested field not found - expectError( - { - sfSignerEntries.getCode(), - 0, - sfDestination.getCode() // sfDestination does not exist - }, - HostFunctionError::FieldNotFound); - - // Error: invalid field code - expectError( - {fieldCode(99999, 99999), 0, sfAccount.getCode()}, HostFunctionError::InvalidField); - - // Error: invalid nested field code - expectError( - {sfSignerEntries.getCode(), 0, fieldCode(99999, 99999)}, - HostFunctionError::InvalidField); - - // Error: slot out of range - expectError({sfSignerQuorum.getCode()}, HostFunctionError::SlotOutRange, 0); - expectError({sfSignerQuorum.getCode()}, HostFunctionError::SlotOutRange, 257); - - // Error: empty slot - expectError({sfSignerQuorum.getCode()}, HostFunctionError::EmptySlot, 2); - - // Error: locator for STArray (not leaf field) - expectError({sfSignerEntries.getCode()}, HostFunctionError::NotLeafField); - - // Error: nesting into non-array/object field - expectError( - {sfSignerQuorum.getCode(), 0, sfAccount.getCode()}, - HostFunctionError::LocatorMalformed); - - // Error: empty locator - expectError({}, HostFunctionError::LocatorMalformed); - - // Error: locator malformed (not multiple of 4) - { - std::vector const locatorVec = {sfSignerEntries.getCode()}; - // hfs.getLedgerObjNestedField(1, locator); - vrt.setBytes(0, locatorVec.data(), 3); - WasmValVec params(5), result(1); - auto* trap = ww(&import.at("le_inner"), params, result, 1, 0, 3, 256, 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == static_cast(HostFunctionError::LocatorMalformed)); - } - } - - void - testGetTxArrayLen() - { - testcase("getTxArrayLen"); - using namespace test::jtx; - - std::string const credIdHex = - "0011223344556677889900112233445566778899001122334455667788990011"; - uint256 credId; - BEAST_EXPECT(credId.parseHex(credIdHex)); - - Env env{*this}; - OpenView ov{*env.current()}; - - // Transaction with an array field - STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) { - obj.setAccountID(sfAccount, env.master.id()); - STArray memos; - { - STObject memoObj(sfMemo); - memoObj.setFieldVL(sfMemoData, Slice("hello", 5)); - memos.push_back(memoObj); - } - { - STObject memoObj(sfMemo); - memoObj.setFieldVL(sfMemoData, Slice("world", 5)); - memos.push_back(memoObj); - } - obj.setFieldArray(sfMemos, memos); - STVector256 credIds; - credIds.pushBack(credId); - obj.setFieldV256(sfCredentialIDs, credIds); - }); - - ApplyContext ac = createApplyContext(env, ov, stx); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // Should return 2 for sfMemos - // hfs.getTxArrayLen(sfMemos); - { - WasmValVec params(1), result(1); - auto* trap = ww(&import.at("tx_arr_len"), params, result, sfMemos.getCode()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECT(result[0].of.i32 > 0)) - BEAST_EXPECT(result[0].of.i32 == 2); - } - - // Should return error for non-array field - // hfs.getTxArrayLen(sfAccount); - { - WasmValVec params(1), result(1); - auto* trap = ww(&import.at("tx_arr_len"), params, result, sfAccount.getCode()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECT(result[0].of.i32 == static_cast(HostFunctionError::NoArray)); - } - - // Should return error for missing array field - // hfs.getTxArrayLen(sfSigners); - { - WasmValVec params(1), result(1); - auto* trap = ww(&import.at("tx_arr_len"), params, result, sfSigners.getCode()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECT( - result[0].of.i32 == static_cast(HostFunctionError::FieldNotFound)); - } - - // Should return 1 for sfCredentialIDs - // hfs.getTxArrayLen(sfCredentialIDs); - { - WasmValVec params(1), result(1); - auto* trap = ww(&import.at("tx_arr_len"), params, result, sfCredentialIDs.getCode()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECT(result[0].of.i32 > 0)) - BEAST_EXPECT(result[0].of.i32 == 1); - } - } - - void - testGetCurrentLedgerObjArrayLen() - { - testcase("getCurrentLedgerObjArrayLen"); - using namespace test::jtx; - - Env env{*this}; - Account const alice("alice"); - Account const becky("becky"); - // Create a SignerList for env.master - env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); - env.close(); - - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - - auto const signerKeylet = keylet::signerList(env.master.id()); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, signerKeylet); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // hfs.getCurrentLedgerObjArrayLen(sfSignerEntries); - { - WasmValVec params(1), result(1); - auto* trap = - ww(&import.at("home_le_arr_len"), params, result, sfSignerEntries.getCode()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECT(result[0].of.i32 > 0)) - BEAST_EXPECT(result[0].of.i32 == 2); - } - - // hfs.getCurrentLedgerObjArrayLen(sfMemos); - { - WasmValVec params(1), result(1); - auto* trap = ww(&import.at("home_le_arr_len"), params, result, sfMemos.getCode()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECT( - result[0].of.i32 == static_cast(HostFunctionError::FieldNotFound)); - } - - // Should return NO_ARRAY for non-array field - // hfs.getCurrentLedgerObjArrayLen(sfAccount); - { - WasmValVec params(1), result(1); - auto* trap = ww(&import.at("home_le_arr_len"), params, result, sfAccount.getCode()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECT(result[0].of.i32 == static_cast(HostFunctionError::NoArray)); - } - - { - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master) + 5)); - VirtualRuntime vrt2; - WasmHostFunctionsImpl dummyHfs(ac, dummyEscrow); - - auto import2 = xrpl::createWasmImport(dummyHfs); - dummyHfs.setRT(vrt2); - - // auto const len = dummyHfs.getCurrentLedgerObjArrayLen(sfMemos); - WasmValVec params(1), result(1); - auto* trap = ww(&import2.at("home_le_arr_len"), params, result, sfMemos.getCode()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECT( - result[0].of.i32 == static_cast(HostFunctionError::LedgerObjNotFound)); - } - } - - void - testGetLedgerObjArrayLen() - { - testcase("getLedgerObjArrayLen"); - using namespace test::jtx; - - Env env{*this}; - Account const alice("alice"); - Account const becky("becky"); - // Create a SignerList for env.master - env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); - env.close(); - - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - auto const signerListKeylet = keylet::signerList(env.master.id()); - // hfs.cacheLedgerObj(signerListKeylet.key, 1); - { - WasmValVec params(3), result(1); - vrt.setBytes(0, signerListKeylet.key.data(), uint256::size()); - auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 1); - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 1); - } - - { - // hfs.getLedgerObjArrayLen(1, sfSignerEntries); - WasmValVec params(2), result(1); - auto* trap = ww(&import.at("le_arr_len"), params, result, 1, sfSignerEntries.getCode()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECT(result[0].of.i32 > 0)) - { - // Should return 2 for sfSignerEntries - BEAST_EXPECT(result[0].of.i32 == 2); - } - } - { - // hfs.getLedgerObjArrayLen(0, sfSignerEntries); - WasmValVec params(2), result(1); - auto* trap = ww(&import.at("le_arr_len"), params, result, 0, sfSignerEntries.getCode()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECT(result[0].of.i32 == static_cast(HostFunctionError::SlotOutRange)); - } - - { - // Should return error for non-array field - // hfs.getLedgerObjArrayLen(1, sfAccount); - WasmValVec params(2), result(1); - auto* trap = ww(&import.at("le_arr_len"), params, result, 1, sfAccount.getCode()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECT(result[0].of.i32 == static_cast(HostFunctionError::NoArray)); - } - - { - // Should return error for empty slot - // hfs.getLedgerObjArrayLen(2, sfSignerEntries); - WasmValVec params(2), result(1); - auto* trap = ww(&import.at("le_arr_len"), params, result, 2, sfSignerEntries.getCode()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECT(result[0].of.i32 == static_cast(HostFunctionError::EmptySlot)); - } - - { - // Should return error for missing array field - // hfs.getLedgerObjArrayLen(1, sfMemos); - WasmValVec params(2), result(1); - auto* trap = ww(&import.at("le_arr_len"), params, result, 1, sfMemos.getCode()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECT( - result[0].of.i32 == static_cast(HostFunctionError::FieldNotFound)); - } - } - - void - testGetTxNestedArrayLen() - { - testcase("getTxNestedArrayLen"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - - STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) { - STArray memos; - STObject memoObj(sfMemo); - memoObj.setFieldVL(sfMemoData, Slice("hello", 5)); - memos.push_back(memoObj); - obj.setFieldArray(sfMemos, memos); - }); - - ApplyContext ac = createApplyContext(env, ov, stx); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // Helper for error checks - auto expectError = [&](std::vector const& locatorVec, - HostFunctionError expectedError) { - // hfs.getTxNestedArrayLen(locator); - vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); - WasmValVec params(2), result(1); - auto* trap = - ww(&import.at("tx_inner_arr_len"), - params, - result, - 0, - locatorVec.size() * sizeof(int32_t)); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECTS( - result[0].of.i32 == hfErrorToInt(expectedError), std::to_string(result[0].of.i32)); - }; - - // Locator for sfMemos - { - std::vector locatorVec = {sfMemos.getCode()}; - // hfs.getTxNestedArrayLen(locator); - vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); - WasmValVec params(2), result(1); - auto* trap = - ww(&import.at("tx_inner_arr_len"), - params, - result, - 0, - locatorVec.size() * sizeof(int32_t)); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECT(result[0].of.i32 == 1); - } - - // Error: non-array field - expectError({sfAccount.getCode()}, HostFunctionError::NoArray); - - // Error: missing field - expectError({sfSigners.getCode()}, HostFunctionError::FieldNotFound); - } - - void - testGetCurrentLedgerObjNestedArrayLen() - { - testcase("getCurrentLedgerObjNestedArrayLen"); - using namespace test::jtx; - - Env env{*this}; - Account const alice("alice"); - Account const becky("becky"); - // Create a SignerList for env.master - env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); - env.close(); - - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - - auto const signerKeylet = keylet::signerList(env.master.id()); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, signerKeylet); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // Helper for error checks - auto expectError = [&](std::vector const& locatorVec, - HostFunctionError expectedError) { - // hfs.getCurrentLedgerObjNestedArrayLen(locator); - vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); - WasmValVec params(2), result(1); - auto* trap = - ww(&import.at("home_le_inner_arr_len"), - params, - result, - 0, - locatorVec.size() * sizeof(int32_t)); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECTS( - result[0].of.i32 == hfErrorToInt(expectedError), std::to_string(result[0].of.i32)); - }; - - // Locator for sfSignerEntries - { - std::vector locatorVec = {sfSignerEntries.getCode()}; - // hfs.getCurrentLedgerObjNestedArrayLen(locator); - vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); - WasmValVec params(2), result(1); - auto* trap = - ww(&import.at("home_le_inner_arr_len"), - params, - result, - 0, - locatorVec.size() * sizeof(int32_t)); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECT(result[0].of.i32 == 2); - } - - // Error: non-array field - expectError({sfSignerQuorum.getCode()}, HostFunctionError::NoArray); - - // Error: missing field - expectError({sfSigners.getCode()}, HostFunctionError::FieldNotFound); - - { - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master) + 5)); - VirtualRuntime vrt2; - WasmHostFunctionsImpl dummyHfs(ac, dummyEscrow); - - auto import2 = xrpl::createWasmImport(dummyHfs); - dummyHfs.setRT(vrt2); - - std::vector locatorVec = {sfAccount.getCode()}; - // auto const result = dummyHfs.getCurrentLedgerObjNestedArrayLen(locator); - vrt2.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); - WasmValVec params(2), result(1); - auto* trap = - ww(&import2.at("home_le_inner_arr_len"), - params, - result, - 0, - locatorVec.size() * sizeof(int32_t)); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECTS( - result[0].of.i32 == static_cast(HostFunctionError::LedgerObjNotFound), - std::to_string(result[0].of.i32)); - } - } - - void - testGetLedgerObjNestedArrayLen() - { - testcase("getLedgerObjNestedArrayLen"); - using namespace test::jtx; - - Env env{*this}; - Account const alice("alice"); - Account const becky("becky"); - env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); - env.close(); - - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - auto const signerListKeylet = keylet::signerList(env.master.id()); - // hfs.cacheLedgerObj(signerListKeylet.key, 1); - { - WasmValVec params(3), result(1); - vrt.setBytes(0, signerListKeylet.key.data(), uint256::size()); - auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 1); - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 1); - } - - // Locator for sfSignerEntries - std::vector locatorVec = {sfSignerEntries.getCode()}; - // hfs.getLedgerObjNestedArrayLen(1, locator); - { - vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); - WasmValVec params(3), result(1); - auto* trap = - ww(&import.at("le_inner_arr_len"), - params, - result, - 1, - 0, - locatorVec.size() * sizeof(int32_t)); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - if (BEAST_EXPECT(result[0].of.i32 > 0)) - BEAST_EXPECT(result[0].of.i32 == 2); - } - - // Helper for error checks - auto expectError = [&](std::vector const& locatorVec, - HostFunctionError expectedError, - int slot = 1) { - // hfs.getLedgerObjNestedArrayLen(slot, locator); - vrt.setBytes(0, locatorVec.data(), locatorVec.size() * sizeof(int32_t)); - WasmValVec params(3), result(1); - auto* trap = - ww(&import.at("le_inner_arr_len"), - params, - result, - slot, - 0, - locatorVec.size() * sizeof(int32_t)); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); - BEAST_EXPECTS( - result[0].of.i32 == hfErrorToInt(expectedError), std::to_string(result[0].of.i32)); - }; - - // Error: non-array field - expectError({sfSignerQuorum.getCode()}, HostFunctionError::NoArray); - - // Error: missing field - expectError({sfSigners.getCode()}, HostFunctionError::FieldNotFound); - - // Slot out of range - expectError(locatorVec, HostFunctionError::SlotOutRange, 0); - expectError(locatorVec, HostFunctionError::SlotOutRange, 257); - - // Empty slot - expectError(locatorVec, HostFunctionError::EmptySlot, 2); - - // Error: empty locator - expectError({}, HostFunctionError::LocatorMalformed); - - // Error: locator malformed (not multiple of 4) - { - // hfs.getLedgerObjNestedArrayLen(1, malformedLocator); - vrt.setBytes(0, locatorVec.data(), 3); - WasmValVec params(3), result(1); - auto* trap = ww(&import.at("le_inner_arr_len"), params, result, 1, 0, 3); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == static_cast(HostFunctionError::LocatorMalformed)); - } - - // Error: locator for non-STArray field - expectError( - {sfSignerQuorum.getCode(), 0, sfAccount.getCode()}, - HostFunctionError::LocatorMalformed); - } - - void - testCheckSignature() - { - testcase("checkSignature"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // Generate a keypair and sign a message - auto const kp = generateKeyPair(KeyType::Secp256k1, randomSeed()); - PublicKey const& pk = kp.first; - SecretKey const& sk = kp.second; - std::string const& message = "hello signature"; - auto const sig = sign(pk, sk, Slice(message.data(), message.size())); - - // Should succeed for valid signature - { - // hfs.checkSignature( - // Slice(message.data(), message.size()), - // Slice(sig.data(), sig.size()), - // Slice(pk.data(), pk.size())); - vrt.setBytes(0, message.data(), message.size()); - vrt.setBytes(256, sig.data(), sig.size()); - vrt.setBytes(512, pk.data(), pk.size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("check_sig"), - params, - result, - 0, - message.size(), - 256, - sig.size(), - 512, - pk.size()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 1); - } - - // Should fail for invalid signature - { - std::string badSig(sig.size(), 0xFF); - // hfs.checkSignature( - // Slice(message.data(), message.size()), - // Slice(badSig.data(), badSig.size()), - // Slice(pk.data(), pk.size())); - vrt.setBytes(0, message.data(), message.size()); - vrt.setBytes(256, badSig.data(), badSig.size()); - vrt.setBytes(512, pk.data(), pk.size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("check_sig"), - params, - result, - 0, - message.size(), - 256, - badSig.size(), - 512, - pk.size()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 0); - } - - // Should fail for invalid public key - { - std::string badPk(pk.size(), 0x00); - // hfs.checkSignature( - // Slice(message.data(), message.size()), - // Slice(sig.data(), sig.size()), - // Slice(badPk.data(), badPk.size())); - vrt.setBytes(0, message.data(), message.size()); - vrt.setBytes(256, sig.data(), sig.size()); - vrt.setBytes(512, badPk.data(), badPk.size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("check_sig"), - params, - result, - 0, - message.size(), - 256, - sig.size(), - 512, - badPk.size()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == hfErrorToInt(HostFunctionError::InvalidParams)); - } - - // Should fail for empty public key - { - // hfs.checkSignature( - // Slice(message.data(), message.size()), - // Slice(sig.data(), sig.size()), - // Slice(nullptr, 0)); - vrt.setBytes(0, message.data(), message.size()); - vrt.setBytes(256, sig.data(), sig.size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("check_sig"), - params, - result, - 0, - message.size(), - 256, - sig.size(), - 512, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == hfErrorToInt(HostFunctionError::InvalidParams)); - } - - // Should fail for empty signature - { - // hfs.checkSignature( - // Slice(message.data(), message.size()), - // Slice(nullptr, 0), - // Slice(pk.data(), pk.size())); - vrt.setBytes(0, message.data(), message.size()); - vrt.setBytes(512, pk.data(), pk.size()); - WasmValVec params(6), result(1); - auto* trap = ww( - &import.at("check_sig"), params, result, 0, message.size(), 256, 0, 512, pk.size()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 0); - } - - // Should fail for empty message - { - // hfs.checkSignature( - // Slice(nullptr, 0), Slice(sig.data(), sig.size()), Slice(pk.data(), pk.size())); - vrt.setBytes(256, sig.data(), sig.size()); - vrt.setBytes(512, pk.data(), pk.size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("check_sig"), params, result, 0, 0, 256, sig.size(), 512, pk.size()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 0); - } - } - - void - testComputeSha512HalfHash() - { - testcase("computeSha512HalfHash"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - std::string data = "hello world"; - // hfs.computeSha512HalfHash(Slice(data.data(), data.size())); - { - vrt.setBytes(0, data.data(), data.size()); - WasmValVec params(4), result(1); - auto* trap = - ww(&import.at("sha512_half"), params, result, 0, data.size(), 256, uint256::size()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == uint256::size()); - - // Should match direct call to sha512Half - auto expected = sha512Half(Slice(data.data(), data.size())); - auto hashBytes = vrt.getBytes(params, 2); - BEAST_EXPECT(std::ranges::equal(hashBytes, expected)); - } - } - - void - testGetNFT() - { - testcase("getNFT"); - using namespace test::jtx; - - Env env{*this}; - Account const alice("alice"); - env.fund(XRP(1000), alice); - env.close(); - - // Mint NFT for alice - uint256 const nftId = token::getNextID(env, alice, 0u, 0u); - std::string const uri = "https://example.com/nft"; - env(token::mint(alice), token::Uri(uri)); - env.close(); - uint256 const nftId2 = token::getNextID(env, alice, 0u, 0u); - env(token::mint(alice)); - env.close(); - - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - - auto const dummyEscrow = keylet::escrow(alice, SeqProxy::rawSequence(env.seq(alice))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // Should succeed for valid NFT - { - // hfs.getNFT(alice.id(), nftId); - vrt.setBytes(0, alice.id().data(), AccountID::size()); - vrt.setBytes(256, nftId.data(), uint256::size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("nft_uri"), - params, - result, - 0, - AccountID::size(), - 256, - uint256::size(), - 512, - 256); - - if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 > 0)) - { - auto uriBytes = vrt.getBytes(params, 4); - uriBytes.resize(result[0].of.i32); - BEAST_EXPECT(std::ranges::equal(uriBytes, uri)); - } - } - - // Should fail for invalid account - { - // hfs.getNFT(xrpAccount(), nftId); - vrt.setBytes(0, xrpAccount().data(), AccountID::size()); - vrt.setBytes(256, nftId.data(), uint256::size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("nft_uri"), - params, - result, - 0, - AccountID::size(), - 256, - uint256::size(), - 512, - 256); - - if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32)) - BEAST_EXPECT(result[0].of.i32 == hfErrorToInt(HostFunctionError::InvalidAccount)); - } - - // Should fail for invalid nftId - { - // hfs.getNFT(alice.id(), uint256()); - uint256 zeroId; - vrt.setBytes(0, alice.id().data(), AccountID::size()); - vrt.setBytes(256, zeroId.data(), uint256::size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("nft_uri"), - params, - result, - 0, - AccountID::size(), - 256, - uint256::size(), - 512, - 256); - - if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32)) - BEAST_EXPECT(result[0].of.i32 == hfErrorToInt(HostFunctionError::InvalidParams)); - } - - // Should fail for invalid nftId - { - auto const badId = token::getNextID(env, alice, 0u, 1u); - // hfs.getNFT(alice.id(), badId); - vrt.setBytes(0, alice.id().data(), AccountID::size()); - vrt.setBytes(256, badId.data(), uint256::size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("nft_uri"), - params, - result, - 0, - AccountID::size(), - 256, - uint256::size(), - 512, - 256); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == hfErrorToInt(HostFunctionError::LedgerObjNotFound)); - } - - { - // hfs.getNFT(alice.id(), nftId2); - vrt.setBytes(0, alice.id().data(), AccountID::size()); - vrt.setBytes(256, nftId2.data(), uint256::size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("nft_uri"), - params, - result, - 0, - AccountID::size(), - 256, - uint256::size(), - 512, - 256); - - if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32)) - BEAST_EXPECT(result[0].of.i32 == hfErrorToInt(HostFunctionError::FieldNotFound)); - } - } - - void - testGetNFTIssuer() - { - testcase("getNFTIssuer"); - using namespace test::jtx; - - Env env{*this}; - // Mint NFT for env.master - uint32_t const taxon = 12345; - uint256 const nftId = token::getNextID(env, env.master, taxon); - env(token::mint(env.master, taxon)); - env.close(); - - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // Should succeed for valid NFT id - { - // hfs.getNFTIssuer(nftId); - vrt.setBytes(0, nftId.data(), uint256::size()); - WasmValVec params(4), result(1); - auto* trap = - ww(&import.at("nft_issuer"), - params, - result, - 0, - uint256::size(), - 256, - AccountID::size()); - - if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == AccountID::size())) - { - auto issuerBytes = vrt.getBytes(params, 2); - BEAST_EXPECT(std::ranges::equal(issuerBytes, env.master.id())); - } - } - - // Should fail for zero NFT id - { - // hfs.getNFTIssuer(uint256()); - uint256 zeroId; - vrt.setBytes(0, zeroId.data(), uint256::size()); - WasmValVec params(4), result(1); - auto* trap = - ww(&import.at("nft_issuer"), - params, - result, - 0, - uint256::size(), - 256, - AccountID::size()); - - if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32)) - BEAST_EXPECT(result[0].of.i32 == hfErrorToInt(HostFunctionError::InvalidParams)); - } - } - - void - testGetNFTTaxon() - { - testcase("getNFTTaxon"); - using namespace test::jtx; - - Env env{*this}; - - uint32_t const taxon = 54321; - uint256 const nftId = token::getNextID(env, env.master, taxon); - env(token::mint(env.master, taxon)); - env.close(); - - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // hfs.getNFTTaxon(nftId); - vrt.setBytes(0, nftId.data(), uint256::size()); - WasmValVec params(4), result(1); - auto* trap = - ww(&import.at("nft_taxon"), params, result, 0, uint256::size(), 256, sizeof(uint32_t)); - - if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == sizeof(uint32_t))) - { - BEAST_EXPECT(vrt.getUint32(params, 2) == taxon); - } - } - - void - testGetNFTFlags() - { - testcase("getNFTFlags"); - using namespace test::jtx; - - Env env{*this}; - - // Mint NFT with default flags - uint256 const nftId = token::getNextID(env, env.master, 0u, tfTransferable); - env(token::mint(env.master, 0), Txflags(tfTransferable)); - env.close(); - - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - { - // hfs.getNFTFlags(nftId); - vrt.setBytes(0, nftId.data(), uint256::size()); - WasmValVec params(2), result(1); - auto* trap = ww(&import.at("nft_flags"), params, result, 0, uint256::size()); - - if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32)) - BEAST_EXPECT(result[0].of.i32 == tfTransferable); - } - - // Should return 0 for zero NFT id - { - // hfs.getNFTFlags(uint256()); - uint256 zeroId; - vrt.setBytes(0, zeroId.data(), uint256::size()); - WasmValVec params(2), result(1); - auto* trap = ww(&import.at("nft_flags"), params, result, 0, uint256::size()); - - if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32)) - BEAST_EXPECT(result[0].of.i32 == 0); - } - } - - void - testGetNFTTransferFee() - { - testcase("getNFTTransferFee"); - using namespace test::jtx; - - Env env{*this}; - - uint16_t const transferFee = 250; - uint256 const nftId = token::getNextID(env, env.master, 0u, tfTransferable, transferFee); - env(token::mint(env.master, 0), token::XferFee(transferFee), Txflags(tfTransferable)); - env.close(); - - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - { - // hfs.getNFTTransferFee(nftId); - vrt.setBytes(0, nftId.data(), uint256::size()); - WasmValVec params(2), result(1); - auto* trap = ww(&import.at("nft_xfer_fee"), params, result, 0, uint256::size()); - - if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32)) - BEAST_EXPECT(result[0].of.i32 == transferFee); - } - - // Should return 0 for zero NFT id - { - // hfs.getNFTTransferFee(uint256()); - uint256 zeroId; - vrt.setBytes(0, zeroId.data(), uint256::size()); - WasmValVec params(2), result(1); - auto* trap = ww(&import.at("nft_xfer_fee"), params, result, 0, uint256::size()); - - if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32)) - BEAST_EXPECT(result[0].of.i32 == 0); - } - } - - void - testGetNFTSerial() - { - testcase("getNFTSequence"); - using namespace test::jtx; - - Env env{*this}; - - // Mint NFT with serial 0 - uint256 const nftId = token::getNextID(env, env.master, 0u); - auto const serial = env.seq(env.master); - env(token::mint(env.master)); - env.close(); - - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - { - // hfs.getNFTSequence(nftId); - vrt.setBytes(0, nftId.data(), uint256::size()); - WasmValVec params(4), result(1); - auto* trap = - ww(&import.at("nft_serial"), - params, - result, - 0, - uint256::size(), - 256, - sizeof(uint32_t)); - - if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == sizeof(uint32_t))) - { - BEAST_EXPECT(vrt.getUint32(params, 2) == serial); - } - } - - // Should return 0 for zero NFT id - { - // hfs.getNFTSequence(uint256()); - uint256 zeroId; - vrt.setBytes(0, zeroId.data(), uint256::size()); - WasmValVec params(4), result(1); - auto* trap = - ww(&import.at("nft_serial"), - params, - result, - 0, - uint256::size(), - 256, - sizeof(uint32_t)); - - if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == sizeof(uint32_t))) - { - BEAST_EXPECT(vrt.getUint32(params, 2) == 0); - } - } - } - - void - testTrace() - { - testcase("trace"); - using namespace test::jtx; - - { - Env env(*this); - OpenView ov{*env.current()}; - test::StreamSink sink{beast::Severity::Trace}; - beast::Journal const jlog{sink}; - ApplyContext ac = createApplyContext(env, ov, jlog); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - VirtualRuntime vrt; - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - std::string const msg = "test trace"; - std::string data = "abc"; - auto const slice = Slice(data.data(), data.size()); - - // AsText: data printed verbatim (was trace with as_hex = 0) - { - vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); - vrt.setBytes(256, slice.data(), slice.size()); - WasmValVec params(5), result(0); - auto* trap = - ww(&import.at("trace"), - params, - result, - 0, - msg.size(), - traceDataTypeToInt(TraceDataType::AsText), - 256, - slice.size()); - - if (BEAST_EXPECT(!trap)) - { - auto const messages = sink.messages().str(); - BEAST_EXPECT(messages.contains(msg)); - BEAST_EXPECT(messages.contains(data)); - } - } - - // AsHex: host hex-encodes data (was trace with as_hex = 1) - { - vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); - vrt.setBytes(256, slice.data(), slice.size()); - WasmValVec params(5), result(0); - auto* trap = - ww(&import.at("trace"), - params, - result, - 0, - msg.size(), - traceDataTypeToInt(TraceDataType::AsHex), - 256, - slice.size()); - - if (BEAST_EXPECT(!trap)) - { - auto const messages = sink.messages().str(); - std::string hex; - hex.reserve(data.size() * 2); - boost::algorithm::hex(data.begin(), data.end(), std::back_inserter(hex)); - BEAST_EXPECT(messages.contains(msg)); - BEAST_EXPECT(messages.contains(hex)); - } - } - - // Unknown data_type: logged as invalid, never a trap - { - vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); - vrt.setBytes(256, slice.data(), slice.size()); - WasmValVec params(5), result(0); - auto* trap = - ww(&import.at("trace"), params, result, 0, msg.size(), 9999, 256, slice.size()); - BEAST_EXPECT(!trap); - } - - // msg and data each fit, but their combined size exceeds - // kMaxWasmDataLength, so nothing is logged - { - std::string const longMsg(kMaxWasmDataLength, 'x'); - vrt.setBytes(0, reinterpret_cast(longMsg.data()), longMsg.size()); - vrt.setBytes(2048, slice.data(), slice.size()); - WasmValVec params(5), result(0); - auto* trap = - ww(&import.at("trace"), - params, - result, - 0, - longMsg.size(), - traceDataTypeToInt(TraceDataType::AsText), - 2048, - slice.size()); - - if (BEAST_EXPECT(!trap)) - { - auto const messages = sink.messages().str(); - BEAST_EXPECT(messages.contains("message and data too long")); - BEAST_EXPECT(!messages.contains(longMsg)); - } - } - } - - { - // logs disabled (trace < error) - Env env(*this); - OpenView ov{*env.current()}; - test::StreamSink sink{beast::Severity::Error}; - beast::Journal const jlog{sink}; - ApplyContext ac = createApplyContext(env, ov, jlog); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - VirtualRuntime vrt; - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - std::string const msg = "test trace"; - std::string data = "abc"; - auto const slice = Slice(data.data(), data.size()); - - vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); - vrt.setBytes(256, slice.data(), slice.size()); - WasmValVec params(5), result(0); - auto* trap = - ww(&import.at("trace"), - params, - result, - 0, - msg.size(), - traceDataTypeToInt(TraceDataType::AsText), - 256, - slice.size()); - - BEAST_EXPECT(!trap); - auto const messages = sink.messages().str(); - BEAST_EXPECT(messages.empty()); - } - } - - void - testTraceNum() - { - testcase("traceNum"); - using namespace test::jtx; - - { - Env env(*this); - OpenView ov{*env.current()}; - test::StreamSink sink{beast::Severity::Trace}; - beast::Journal const jlog{sink}; - ApplyContext ac = createApplyContext(env, ov, jlog); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - VirtualRuntime vrt; - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - std::string const msg = "trace number"; - - // adjustWasmEndianess is its own inverse, so writing the adjusted value - // lets the wrapper's adjustment recover it on either endianness. - auto const traceNum = [&](TraceDataType type, auto value) { - auto const wire = adjustWasmEndianess(value); - vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); - vrt.setBytes(256, reinterpret_cast(&wire), sizeof(wire)); - WasmValVec params(5), result(0); - auto* trap = - ww(&import.at("trace"), - params, - result, - 0, - msg.size(), - traceDataTypeToInt(type), - 256, - sizeof(wire)); - - if (BEAST_EXPECT(!trap)) - { - auto const messages = sink.messages().str(); - BEAST_EXPECT(messages.contains(msg)); - BEAST_EXPECT(messages.contains(std::to_string(value))); - } - }; - - traceNum(TraceDataType::Int64, int64_t{123456789}); - traceNum(TraceDataType::Int64, int64_t{-42}); - // Above int64 max -- unreachable through the old trace_num - traceNum(TraceDataType::Uint64, std::numeric_limits::max()); - - // Wrong buffer length for the type: logged as invalid, no trap - { - std::int32_t const tooShort = 7; - vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); - vrt.setBytes(256, reinterpret_cast(&tooShort), sizeof(tooShort)); - WasmValVec params(5), result(0); - auto* trap = - ww(&import.at("trace"), - params, - result, - 0, - msg.size(), - traceDataTypeToInt(TraceDataType::Int64), - 256, - sizeof(tooShort)); - BEAST_EXPECT(!trap); - } - } - - { - // logs disabled - Env env(*this); - OpenView ov{*env.current()}; - test::StreamSink sink{beast::Severity::Error}; - beast::Journal const jlog{sink}; - ApplyContext ac = createApplyContext(env, ov, jlog); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - VirtualRuntime vrt; - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - std::string const msg = "trace number"; - auto const wire = adjustWasmEndianess(int64_t{123456789}); - - vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); - vrt.setBytes(256, reinterpret_cast(&wire), sizeof(wire)); - WasmValVec params(5), result(0); - auto* trap = - ww(&import.at("trace"), - params, - result, - 0, - msg.size(), - traceDataTypeToInt(TraceDataType::Int64), - 256, - sizeof(wire)); - - BEAST_EXPECT(!trap); - auto const messages = sink.messages().str(); - BEAST_EXPECT(messages.empty()); - } - } - - void - testTraceAccount() - { - testcase("traceAccount"); - using namespace test::jtx; - - { - Env env(*this); - OpenView ov{*env.current()}; - test::StreamSink sink{beast::Severity::Trace}; - beast::Journal const jlog{sink}; - ApplyContext ac = createApplyContext(env, ov, jlog); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - VirtualRuntime vrt; - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - std::string const msg = "trace account"; - auto const& accountId = env.master.id(); - - vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); - vrt.setBytes(256, accountId.data(), accountId.size()); - WasmValVec params(5), result(0); - auto* trap = - ww(&import.at("trace"), - params, - result, - 0, - msg.size(), - traceDataTypeToInt(TraceDataType::Account), - 256, - accountId.size()); - - if (BEAST_EXPECT(!trap)) - { - auto const messages = sink.messages().str(); - BEAST_EXPECT(messages.contains(msg)); - BEAST_EXPECT(messages.contains(env.master.human())); - } - } - - { - // logs disabled - Env env(*this); - OpenView ov{*env.current()}; - test::StreamSink sink{beast::Severity::Error}; - beast::Journal const jlog{sink}; - ApplyContext ac = createApplyContext(env, ov, jlog); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - VirtualRuntime vrt; - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - std::string msg = "trace account"; - auto const& accountId = env.master.id(); - - vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); - vrt.setBytes(256, accountId.data(), accountId.size()); - WasmValVec params(5), result(0); - auto* trap = - ww(&import.at("trace"), - params, - result, - 0, - msg.size(), - traceDataTypeToInt(TraceDataType::Account), - 256, - accountId.size()); - - BEAST_EXPECT(!trap); - auto const messages = sink.messages().str(); - BEAST_EXPECT(messages.empty()); - } - } - - void - testTraceAmount() - { - testcase("traceAmount"); - using namespace test::jtx; - - { - Env env(*this); - OpenView ov{*env.current()}; - test::StreamSink sink{beast::Severity::Trace}; - beast::Journal const jlog{sink}; - ApplyContext ac = createApplyContext(env, ov, jlog); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - VirtualRuntime vrt; - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - std::string const msg = "trace amount"; - STAmount const amount = XRP(12345); - { - Bytes amountBytes = toBytes(amount); - vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); - vrt.setBytes(256, amountBytes.data(), amountBytes.size()); - WasmValVec params(5), result(0); - auto* trap = - ww(&import.at("trace"), - params, - result, - 0, - msg.size(), - traceDataTypeToInt(TraceDataType::Amount), - 256, - amountBytes.size()); - - if (BEAST_EXPECT(!trap)) - { - auto const messages = sink.messages().str(); - BEAST_EXPECT(messages.contains(msg)); - BEAST_EXPECT(messages.contains(amount.getFullText())); - } - } - - // IOU amount - Account const alice("alice"); - env.fund(XRP(1000), alice); - env.close(); - STAmount const iouAmount = env.master["USD"](100); - { - Bytes amountBytes = toBytes(iouAmount); - vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); - vrt.setBytes(256, amountBytes.data(), amountBytes.size()); - WasmValVec params(5), result(0); - auto* trap = - ww(&import.at("trace"), - params, - result, - 0, - msg.size(), - traceDataTypeToInt(TraceDataType::Amount), - 256, - amountBytes.size()); - - BEAST_EXPECT(!trap); - } - - // MPT amount - { - auto const mptId = makeMptID(42, env.master.id()); - Asset const mptAsset = Asset(mptId); - STAmount const mptAmount(mptAsset, 123456); - - Bytes amountBytes = toBytes(mptAmount); - vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); - vrt.setBytes(256, amountBytes.data(), amountBytes.size()); - WasmValVec params(5), result(0); - auto* trap = - ww(&import.at("trace"), - params, - result, - 0, - msg.size(), - traceDataTypeToInt(TraceDataType::Amount), - 256, - amountBytes.size()); - - BEAST_EXPECT(!trap); - } - } - - { - // logs disabled - Env env(*this); - OpenView ov{*env.current()}; - test::StreamSink sink{beast::Severity::Error}; - beast::Journal const jlog{sink}; - ApplyContext ac = createApplyContext(env, ov, jlog); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - VirtualRuntime vrt; - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - std::string const msg = "trace amount"; - STAmount const amount = XRP(12345); - - Bytes amountBytes = toBytes(amount); - vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); - vrt.setBytes(256, amountBytes.data(), amountBytes.size()); - WasmValVec params(5), result(0); - auto* trap = - ww(&import.at("trace"), - params, - result, - 0, - msg.size(), - traceDataTypeToInt(TraceDataType::Amount), - 256, - amountBytes.size()); - - BEAST_EXPECT(!trap); - auto const messages = sink.messages().str(); - BEAST_EXPECT(messages.empty()); - } - } - - // clang-format off - - int const normalExp = 18; - - Bytes const floatIntMin = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, -0x00, 0x00}; // -2^63 (rounds to nearest: -(2^63-1)) Bytes const floatIntZero = {0x00, 0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00}; // 0 Bytes const floatIntMax = -{0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}; // 2^63-1 Bytes const -floatUIntMax = {0x19, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, 0x00, 0x00, 0x00, 0x01}; // -2^64-1 - - Bytes const floatMaxExp = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0x00, 0x00, -0x80, 0x00}; // 1e(Number::kMaxExponent + normalExp) Bytes const floatPreMaxExp = {0x0D, 0xE0, -0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF}; // 1e(Number::kMaxExponent + normalExp -- 1) Bytes const floatMinusMaxExp = {0xF2, 0x1F, 0x49, 0x4C, 0x58, 0x9C, 0x00, 0x00, 0x00, 0x00, -0x80, 0x00}; // -1e(Number::kMaxExponent + normalExp) Bytes const floatMinExp = {0x00, -0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00}; // 1e(Number::kMinExponent - -normalExp) Bytes const floatMax = {0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, -0x00, 0x80, 0x00}; // Number::kMaxRep e(Number::kMaxExponent - normalExp) - - Bytes const floatMaxIOU = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x63, 0xFF, 0x9C, 0x00, 0x00, -0x00, 0x4E}; // 9999999999999999e(96) Bytes const floatMinIOU = {0x0D, 0xE0, 0xB6, 0xB3, -0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x9D}; // 1e(-96 - 3 + normalExp = -81) - - Bytes const float1 = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF, -0xFF, 0xEE}; // 1 Bytes const floatMinus1 = {0xF2, 0x1F, 0x49, 0x4C, 0x58, 0x9C, 0x00, -0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // -1 Bytes const float1More = {0x0D, 0xE0, 0xB6, 0xB3, -0xA7, 0x64, 0x03, 0xE8, 0xFF, 0xFF, 0xFF, 0xEE}; // 1.000 000 000 000 001 Bytes const float2 = -{0x1B, 0xC1, 0x6D, 0x67, 0x4E, 0xC8, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // 2 Bytes const float10 -= {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEF}; // 10 Bytes const -floatPi = {0x2B, 0x99, 0x2D, 0xDF, 0xA2, 0x32, 0x48, 0xE8, 0xFF, 0xFF, 0xFF, 0xEE}; -// 3.141592653589793 Bytes const floatInvalidZero = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, -0x00, 0x81, 0x00, 0x00, 0x00}; // INVALID Bytes const floatMinus3 = {0xD6, 0x5D, 0xDB, -0xE5, 0x09, 0xD4, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // -3 - - std::string const invalid = "invalid_data"; - - // clang-format on - - template - 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); - } - - void - testTraceFloat() - { - testcase("traceFloat"); - using namespace test::jtx; - - { - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - VirtualRuntime vrt; - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - std::string const msg = "trace float"; - - { - vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); - vrt.setBytes(256, reinterpret_cast(invalid.data()), invalid.size()); - WasmValVec params(5), result(0); - auto* trap = - ww(&import.at("trace"), - params, - result, - 0, - msg.size(), - traceDataTypeToInt(TraceDataType::Xfloat), - 256, - invalid.size()); - - BEAST_EXPECT(!trap); - } - - { - vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); - vrt.setBytes(256, floatMaxExp.data(), floatMaxExp.size()); - WasmValVec params(5), result(0); - auto* trap = - ww(&import.at("trace"), - params, - result, - 0, - msg.size(), - traceDataTypeToInt(TraceDataType::Xfloat), - 256, - floatMaxExp.size()); - - BEAST_EXPECT(!trap); - } - } - - { - // logs disabled - Env env(*this); - OpenView ov{*env.current()}; - test::StreamSink sink{beast::Severity::Error}; - beast::Journal const jlog{sink}; - ApplyContext ac = createApplyContext(env, ov, jlog); - - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - VirtualRuntime vrt; - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - std::string const msg = "trace float"; - - vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); - vrt.setBytes(256, reinterpret_cast(invalid.data()), invalid.size()); - WasmValVec params(5), result(0); - auto* trap = - ww(&import.at("trace"), - params, - result, - 0, - msg.size(), - traceDataTypeToInt(TraceDataType::Xfloat), - 256, - invalid.size()); - - BEAST_EXPECT(!trap); - auto const messages = sink.messages().str(); - BEAST_EXPECT(messages.empty()); - } - } - - void - testFloatFromInt() - { - testcase("floatFromInt"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - { - // hfs.floatFromInt(min64, -1); - WasmValVec params(4), result(1); - auto* trap = ww(&import.at("float_from_int"), params, result, min64, 0, floatSize, -1); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatFromInt(min64, 4); - WasmValVec params(4), result(1); - auto* trap = ww(&import.at("float_from_int"), params, result, min64, 0, floatSize, 4); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatFromInt(min64, 0); - WasmValVec params(4), result(1); - auto* trap = ww(&import.at("float_from_int"), params, result, min64, 0, floatSize, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 1); - BEAST_EXPECT(resultBytes == floatIntMin); - } - - { - // hfs.floatFromInt(0, 0); - WasmValVec params(4), result(1); - auto* trap = ww(&import.at("float_from_int"), params, result, 0ll, 0, floatSize, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 1); - BEAST_EXPECT(resultBytes == floatIntZero); - } - - { - // hfs.floatFromInt(max64, 0); - WasmValVec params(4), result(1); - auto* trap = ww(&import.at("float_from_int"), params, result, max64, 0, floatSize, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 1); - BEAST_EXPECT(resultBytes == floatIntMax); - } - } - - void - testFloatFromUint() - { - testcase("floatFromUint"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - { - // hfs.floatFromUint(std::numeric_limits::min(), -1); - WasmValVec params(5), result(1); - uint64_t val = std::numeric_limits::min(); - vrt.setBytes(0, &val, sizeof(val)); - auto* trap = ww(&import.at("float_from_uint"), params, result, 0, 8, 16, floatSize, -1); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(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(&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(&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(&import.at("float_from_uint"), params, result, 0, 8, 16, floatSize, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 2); - BEAST_EXPECT(resultBytes == floatUIntMax); - } - } - - void - testfloatFromMantExp() - { - testcase("floatFromMantExp"); - using namespace test::jtx; - using namespace wasm_float; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - { - // hfs.floatFromMantExp(1, 0, -1); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_mant_exp"), params, result, 1ll, 0, 0, floatSize, -1); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatFromMantExp(1, 0, 4); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_mant_exp"), params, result, 1ll, 0, 0, floatSize, 4); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatFromMantExp(1, Number::kMaxExponent + normalExp + 1, 0); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_mant_exp"), - params, - result, - 1ll, - Number::kMaxExponent + normalExp + 1, - 0, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatFromMantExp(1, Number::kMinExponent + normalExp - 1, 0); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_mant_exp"), - params, - result, - 1ll, - Number::kMinExponent + normalExp - 1, - 0, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 2); - BEAST_EXPECT(resultBytes == floatIntZero); - } - - { - // hfs.floatFromMantExp(1, Number::kMaxExponent + normalExp, 0); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_mant_exp"), - params, - result, - 1ll, - Number::kMaxExponent + normalExp, - 0, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 2); - BEAST_EXPECT(resultBytes == floatMaxExp); - } - - { - // hfs.floatFromMantExp(-1, Number::kMaxExponent + normalExp, 0); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_mant_exp"), - params, - result, - -1ll, - Number::kMaxExponent + normalExp, - 0, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 2); - BEAST_EXPECT(resultBytes == floatMinusMaxExp); - } - - { - // hfs.floatFromMantExp(1, Number::kMaxExponent + normalExp - 1, 0); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_mant_exp"), - params, - result, - 1ll, - Number::kMaxExponent + normalExp - 1, - 0, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 2); - BEAST_EXPECT(resultBytes == floatPreMaxExp); - } - - { - // hfs.floatFromMantExp(STAmount::kMaxValue, STAmount::kMaxOffset, 0); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_mant_exp"), - params, - result, - static_cast(STAmount::kMaxValue), - STAmount::kMaxOffset, - 0, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 2); - BEAST_EXPECT(resultBytes == floatMaxIOU); - } - - { - // hfs.floatFromMantExp(1, Number::kMinExponent + normalExp, 0); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_mant_exp"), - params, - result, - 1ll, - Number::kMinExponent - normalExp, - 0, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 2); - BEAST_EXPECT(resultBytes == floatMinExp); - } - - { - // hfs.floatFromMantExp(10, -1, 0); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_mant_exp"), params, result, 10ll, -1, 0, floatSize, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 2); - BEAST_EXPECT(resultBytes == float1); - } - - { - // hfs.floatFromMantExp(1, Number::kMaxExponent + normalExp + 1, 0); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_mant_exp"), - params, - result, - 1ll, - Number::kMaxExponent + normalExp + 1, - 0, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - } - - void - testFloatCompare() - { - testcase("floatCompare"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - { - // hfs.floatCompare(Slice(), Slice()); - WasmValVec params(4), result(1); - auto* trap = ww(&import.at("float_cmp"), params, result, 0, 0, 0, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatCompare(makeSlice(floatInvalidZero), Slice()); - WasmValVec params(4), result(1); - vrt.setBytes(0, floatInvalidZero.data(), floatInvalidZero.size()); - auto* trap = ww(&import.at("float_cmp"), params, result, 0, floatSize, 0, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatCompare(makeSlice(float1), makeSlice(invalid)); - WasmValVec params(4), result(1); - vrt.setBytes(0, float1.data(), float1.size()); - vrt.setBytes(floatSize, invalid.data(), invalid.size()); - auto* trap = ww( - &import.at("float_cmp"), params, result, 0, floatSize, floatSize, invalid.size()); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatCompare(makeSlice(floatIntMin), makeSlice(floatIntZero)); - WasmValVec params(4), result(1); - vrt.setBytes(0, floatIntMin.data(), floatIntMin.size()); - vrt.setBytes(floatSize, floatIntZero.data(), floatIntZero.size()); - auto* trap = - ww(&import.at("float_cmp"), params, result, 0, floatSize, floatSize, floatSize); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 2); - } - - { - // hfs.floatCompare(makeSlice(floatIntMax), makeSlice(floatIntZero)); - WasmValVec params(4), result(1); - vrt.setBytes(0, floatIntMax.data(), floatIntMax.size()); - vrt.setBytes(floatSize, floatIntZero.data(), floatIntZero.size()); - auto* trap = - ww(&import.at("float_cmp"), params, result, 0, floatSize, floatSize, floatSize); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 1); - } - - { - // hfs.floatCompare(makeSlice(float1), makeSlice(float1)); - WasmValVec params(4), result(1); - vrt.setBytes(0, float1.data(), float1.size()); - vrt.setBytes(floatSize, float1.data(), float1.size()); - auto* trap = - ww(&import.at("float_cmp"), params, result, 0, floatSize, floatSize, floatSize); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 0); - } - } - - void - testFloatAdd() - { - testcase("floatAdd"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - { - // hfs.floatAdd(Slice(), Slice(), -1); - WasmValVec params(7), result(1); - auto* trap = ww(&import.at("float_add"), params, result, 0, 0, 0, 0, 0, floatSize, -1); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatAdd(Slice(), Slice(), 0); - WasmValVec params(7), result(1); - auto* trap = ww(&import.at("float_add"), params, result, 0, 0, 0, 0, 0, floatSize, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatAdd(makeSlice(float1), makeSlice(invalid), 0); - WasmValVec params(7), result(1); - vrt.setBytes(0, float1.data(), float1.size()); - vrt.setBytes(floatSize, invalid.data(), invalid.size()); - auto* trap = - ww(&import.at("float_add"), - params, - result, - 0, - floatSize, - floatSize, - invalid.size(), - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatAdd(makeSlice(floatMaxIOU), makeSlice(floatMaxExp), 0); - // max IOU is too small to make any change - WasmValVec params(7), result(1); - vrt.setBytes(0, floatMaxIOU.data(), floatMaxIOU.size()); - vrt.setBytes(floatSize, floatMaxExp.data(), floatMaxExp.size()); - auto* trap = - ww(&import.at("float_add"), - params, - result, - 0, - floatSize, - floatSize, - floatSize, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 4); - BEAST_EXPECT(resultBytes == floatMaxExp); - } - - { - // hfs.floatAdd(makeSlice(floatIntMin), makeSlice(floatIntZero), 0); - WasmValVec params(7), result(1); - vrt.setBytes(0, floatIntMin.data(), floatIntMin.size()); - vrt.setBytes(floatSize, floatIntZero.data(), floatIntZero.size()); - auto* trap = - ww(&import.at("float_add"), - params, - result, - 0, - floatSize, - floatSize, - floatSize, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 4); - BEAST_EXPECT(resultBytes == floatIntMin); - } - - { - // hfs.floatAdd(makeSlice(floatIntMax), makeSlice(floatIntMin), 0);// - // int64.min is rounded to nearest: -(2^63-1), so max + min == 0 - WasmValVec params(7), result(1); - vrt.setBytes(0, floatIntMax.data(), floatIntMax.size()); - vrt.setBytes(floatSize, floatIntMin.data(), floatIntMin.size()); - auto* trap = - ww(&import.at("float_add"), - params, - result, - 0, - floatSize, - floatSize, - floatSize, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 4); - BEAST_EXPECT(resultBytes == floatIntZero); - } - } - - void - testFloatSubtract() - { - testcase("floatSubtract"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - { - // hfs.floatSubtract(Slice(), Slice(), -1); - WasmValVec params(7), result(1); - auto* trap = ww(&import.at("float_sub"), params, result, 0, 0, 0, 0, 0, floatSize, -1); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatSubtract(Slice(), Slice(), 0); - WasmValVec params(7), result(1); - auto* trap = ww(&import.at("float_sub"), params, result, 0, 0, 0, 0, 0, floatSize, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatSubtract(makeSlice(float1), makeSlice(invalid), 0); - WasmValVec params(7), result(1); - vrt.setBytes(0, float1.data(), float1.size()); - vrt.setBytes(floatSize, invalid.data(), invalid.size()); - auto* trap = - ww(&import.at("float_sub"), - params, - result, - 0, - floatSize, - floatSize, - invalid.size(), - floatSize * 2, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatSubtract(makeSlice(floatMinusMaxExp), makeSlice(floatMaxIOU), 0); - WasmValVec params(7), result(1); - vrt.setBytes(0, floatMinusMaxExp.data(), floatMinusMaxExp.size()); - vrt.setBytes(floatSize, floatMaxIOU.data(), floatMaxIOU.size()); - auto* trap = - ww(&import.at("float_sub"), - params, - result, - 0, - floatSize, - floatSize, - floatSize, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 4); - BEAST_EXPECT(resultBytes == floatMinusMaxExp); - } - - { - // hfs.floatSubtract(makeSlice(floatIntMin), makeSlice(floatIntZero), 0); - WasmValVec params(7), result(1); - vrt.setBytes(0, floatIntMin.data(), floatIntMin.size()); - vrt.setBytes(floatSize, floatIntZero.data(), floatIntZero.size()); - auto* trap = - ww(&import.at("float_sub"), - params, - result, - 0, - floatSize, - floatSize, - floatSize, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 4); - BEAST_EXPECT(resultBytes == floatIntMin); - } - - { - // hfs.floatSubtract(makeSlice(floatIntZero), makeSlice(float1), 0); - WasmValVec params(7), result(1); - vrt.setBytes(0, floatIntZero.data(), floatIntZero.size()); - vrt.setBytes(floatSize, float1.data(), float1.size()); - auto* trap = - ww(&import.at("float_sub"), - params, - result, - 0, - floatSize, - floatSize, - floatSize, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 4); - BEAST_EXPECT(resultBytes == floatMinus1); - } - } - - void - testFloatMultiply() - { - testcase("floatMultiply"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - { - // hfs.floatMultiply(Slice(), Slice(), -1); - WasmValVec params(7), result(1); - auto* trap = ww(&import.at("float_mult"), params, result, 0, 0, 0, 0, 0, floatSize, -1); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatMultiply(Slice(), Slice(), 0); - WasmValVec params(7), result(1); - auto* trap = ww(&import.at("float_mult"), params, result, 0, 0, 0, 0, 0, floatSize, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatMultiply(makeSlice(float1), makeSlice(invalid), 0); - WasmValVec params(7), result(1); - vrt.setBytes(0, float1.data(), float1.size()); - vrt.setBytes(floatSize, invalid.data(), invalid.size()); - auto* trap = - ww(&import.at("float_mult"), - params, - result, - 0, - floatSize, - floatSize, - invalid.size(), - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatMultiply(makeSlice(floatMax), makeSlice(float1More), 0); - WasmValVec params(7), result(1); - vrt.setBytes(0, floatMax.data(), floatMax.size()); - vrt.setBytes(floatSize, float1More.data(), float1More.size()); - auto* trap = - ww(&import.at("float_mult"), - params, - result, - 0, - floatSize, - floatSize, - floatSize, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatComputationError)); - } - - { - // hfs.floatMultiply(makeSlice(float1), makeSlice(float1), 0); - WasmValVec params(7), result(1); - vrt.setBytes(0, float1.data(), float1.size()); - vrt.setBytes(floatSize, float1.data(), float1.size()); - auto* trap = - ww(&import.at("float_mult"), - params, - result, - 0, - floatSize, - floatSize, - floatSize, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 4); - BEAST_EXPECT(resultBytes == float1); - } - - { - // hfs.floatMultiply(makeSlice(floatIntZero), makeSlice(floatMaxIOU), 0); - WasmValVec params(7), result(1); - vrt.setBytes(0, floatIntZero.data(), floatIntZero.size()); - vrt.setBytes(floatSize, floatMaxIOU.data(), floatMaxIOU.size()); - auto* trap = - ww(&import.at("float_mult"), - params, - result, - 0, - floatSize, - floatSize, - floatSize, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 4); - BEAST_EXPECT(resultBytes == floatIntZero); - } - - { - // hfs.floatMultiply(makeSlice(float10), makeSlice(floatPreMaxExp), 0); - WasmValVec params(7), result(1); - vrt.setBytes(0, float10.data(), float10.size()); - vrt.setBytes(floatSize, floatPreMaxExp.data(), floatPreMaxExp.size()); - auto* trap = - ww(&import.at("float_mult"), - params, - result, - 0, - floatSize, - floatSize, - floatSize, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 4); - BEAST_EXPECT(resultBytes == floatMaxExp); - } - } - - void - testFloatDivide() - { - testcase("floatDivide"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - { - // hfs.floatDivide(Slice(), Slice(), -1); - WasmValVec params(7), result(1); - auto* trap = ww(&import.at("float_div"), params, result, 0, 0, 0, 0, 0, floatSize, -1); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatDivide(Slice(), Slice(), 0); - WasmValVec params(7), result(1); - auto* trap = ww(&import.at("float_div"), params, result, 0, 0, 0, 0, 0, floatSize, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { // hfs.floatDivide(makeSlice(float1), makeSlice(invalid), 0); - WasmValVec params(7), result(1); - vrt.setBytes(0, float1.data(), float1.size()); - vrt.setBytes(floatSize, invalid.data(), invalid.size()); - auto* trap = - ww(&import.at("float_div"), - params, - result, - 0, - floatSize, - floatSize, - invalid.size(), - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { // hfs.floatDivide(makeSlice(float1), makeSlice(floatIntZero), 0); - WasmValVec params(7), result(1); - vrt.setBytes(0, float1.data(), float1.size()); - vrt.setBytes(floatSize, floatIntZero.data(), floatIntZero.size()); - auto* trap = - ww(&import.at("float_div"), - params, - result, - 0, - floatSize, - floatSize, - floatSize, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatComputationError)); - } - - { // hfs.floatDivide(makeSlice(floatMax), makeSlice(*y), 0); - auto const y = - hfs.floatFromMantExp(STAmount::kMaxValue, -normalExp - 1, 0); // 0.9999999... - if (BEAST_EXPECT(y)) - { - WasmValVec params(7), result(1); - vrt.setBytes(0, floatMax.data(), floatMax.size()); - vrt.setBytes(floatSize, y->data(), y->size()); - auto* trap = - ww(&import.at("float_div"), - params, - result, - 0, - floatSize, - floatSize, - floatSize, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatComputationError)); - } - } - - { // hfs.floatDivide(makeSlice(floatIntZero), makeSlice(float1), 0); - WasmValVec params(7), result(1); - vrt.setBytes(0, floatIntZero.data(), floatIntZero.size()); - vrt.setBytes(floatSize, float1.data(), float1.size()); - auto* trap = - ww(&import.at("float_div"), - params, - result, - 0, - floatSize, - floatSize, - floatSize, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 4); - BEAST_EXPECT(resultBytes == floatIntZero); - } - - { // hfs.floatDivide(makeSlice(floatMaxExp), makeSlice(float10), 0); - WasmValVec params(7), result(1); - vrt.setBytes(0, floatMaxExp.data(), floatMaxExp.size()); - vrt.setBytes(floatSize, float10.data(), float10.size()); - auto* trap = - ww(&import.at("float_div"), - params, - result, - 0, - floatSize, - floatSize, - floatSize, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 4); - BEAST_EXPECT(resultBytes == floatPreMaxExp); - } - } - - void - testFloatRoot() - { - testcase("floatRoot"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - { // hfs.floatRoot(Slice(), 2, -1); - WasmValVec params(6), result(1); - auto* trap = ww(&import.at("float_root"), params, result, 0, 0, 2, 0, floatSize, -1); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { // hfs.floatRoot(makeSlice(invalid), 3, 0); - WasmValVec params(6), result(1); - vrt.setBytes(0, invalid.data(), invalid.size()); - auto* trap = - ww(&import.at("float_root"), - params, - result, - 0, - invalid.size(), - 3, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { // hfs.floatRoot(makeSlice(float1), -2, 0); - WasmValVec params(6), result(1); - vrt.setBytes(0, float1.data(), float1.size()); - auto* trap = - ww(&import.at("float_root"), - params, - result, - 0, - floatSize, - -2, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { // hfs.floatRoot(makeSlice(floatIntZero), 2, 0); - WasmValVec params(6), result(1); - vrt.setBytes(0, floatIntZero.data(), floatIntZero.size()); - auto* trap = - ww(&import.at("float_root"), - params, - result, - 0, - floatSize, - 2, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 3); - BEAST_EXPECT(resultBytes == floatIntZero); - } - - { // hfs.floatRoot(makeSlice(floatMaxIOU), 1, 0); - WasmValVec params(6), result(1); - vrt.setBytes(0, floatMaxIOU.data(), floatMaxIOU.size()); - auto* trap = - ww(&import.at("float_root"), - params, - result, - 0, - floatSize, - 1, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 3); - BEAST_EXPECT(resultBytes == floatMaxIOU); - } - - { - // hfs.floatRoot(makeSlice(*x), 2, 0); - auto const x = hfs.floatFromMantExp(100, 0, 0); // 100 - if (BEAST_EXPECT(x)) - { - WasmValVec params(6), result(1); - vrt.setBytes(0, x->data(), x->size()); - auto* trap = - ww(&import.at("float_root"), - params, - result, - 0, - floatSize, - 2, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 3); - BEAST_EXPECT(resultBytes == float10); - } - } - - { - // hfs.floatRoot(makeSlice(*x), 3, 0); - auto const x = hfs.floatFromMantExp(1000, 0, 0); // 1000 - if (BEAST_EXPECT(x)) - { - WasmValVec params(6), result(1); - vrt.setBytes(0, x->data(), x->size()); - auto* trap = - ww(&import.at("float_root"), - params, - result, - 0, - floatSize, - 3, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 3); - BEAST_EXPECT(resultBytes == float10); - } - } - - { - // hfs.floatRoot(makeSlice(*x), 2, 0); - auto const x = hfs.floatFromMantExp(1, -2, 0); // 0.01 - auto const y = hfs.floatFromMantExp(1, -1, 0); // 0.1 - if (BEAST_EXPECT(x && y)) - { - WasmValVec params(6), result(1); - vrt.setBytes(0, x->data(), x->size()); - auto* trap = - ww(&import.at("float_root"), - params, - result, - 0, - floatSize, - 2, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 3); - BEAST_EXPECT(resultBytes == *y); - } - } - } - - void - testFloatPower() - { - testcase("floatPower"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - { // hfs.floatPower(Slice(), 2, -1); - WasmValVec params(6), result(1); - auto* trap = ww(&import.at("float_pow"), params, result, 0, 0, 2, 0, floatSize, -1); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { // hfs.floatPower(makeSlice(invalid), 3, 0); - WasmValVec params(6), result(1); - vrt.setBytes(0, invalid.data(), invalid.size()); - auto* trap = - ww(&import.at("float_pow"), - params, - result, - 0, - invalid.size(), - 3, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { // hfs.floatPower(makeSlice(float1), -2, 0); - WasmValVec params(6), result(1); - vrt.setBytes(0, float1.data(), float1.size()); - auto* trap = - ww(&import.at("float_pow"), - params, - result, - 0, - floatSize, - -2, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatPower(makeSlice(floatMax), 2, 0); - WasmValVec params(6), result(1); - vrt.setBytes(0, floatMax.data(), floatMax.size()); - auto* trap = ww( - &import.at("float_pow"), params, result, 0, floatSize, 2, floatSize, floatSize, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatComputationError)); - } - - { - // hfs.floatPower(makeSlice(floatMax), Number::kMaxExponent + 1, 0); - WasmValVec params(6), result(1); - vrt.setBytes(0, floatMax.data(), floatMax.size()); - auto* trap = - ww(&import.at("float_pow"), - params, - result, - 0, - floatSize, - Number::kMaxExponent + 1, - floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatPower(makeSlice(floatMaxIOU), 0, 0); - WasmValVec params(6), result(1); - vrt.setBytes(0, floatMaxIOU.data(), floatMaxIOU.size()); - auto* trap = ww( - &import.at("float_pow"), params, result, 0, floatSize, 0, floatSize, floatSize, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 3); - BEAST_EXPECT(resultBytes == float1); - } - - { // hfs.floatPower(makeSlice(floatMaxIOU), 1, 0); - WasmValVec params(6), result(1); - vrt.setBytes(0, floatMaxIOU.data(), floatMaxIOU.size()); - auto* trap = ww( - &import.at("float_pow"), params, result, 0, floatSize, 1, floatSize, floatSize, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 3); - BEAST_EXPECT(resultBytes == floatMaxIOU); - } - - { - // hfs.floatPower(makeSlice(float10), 2, 0); - auto const x = hfs.floatFromMantExp(100, 0, 0); // 100 - if (BEAST_EXPECT(x)) - { - WasmValVec params(6), result(1); - vrt.setBytes(0, float10.data(), float10.size()); - auto* trap = - ww(&import.at("float_pow"), - params, - result, - 0, - floatSize, - 2, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 3); - BEAST_EXPECT(resultBytes == *x); - } - } - - { - // hfs.floatPower(makeSlice(*x), 2, 0); - auto const x = hfs.floatFromMantExp(1, -1, 0); // 0.1 - auto const y = hfs.floatFromMantExp(1, -2, 0); // 0.01 - if (BEAST_EXPECT(x && y)) - { - WasmValVec params(6), result(1); - vrt.setBytes(0, x->data(), x->size()); - auto* trap = - ww(&import.at("float_pow"), - params, - result, - 0, - floatSize, - 2, - 2 * floatSize, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 3); - BEAST_EXPECT(resultBytes == *y); - } - } - } - - void - testFloatSpecialCases() - { - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - WasmHostFunctionsImpl const hfs(ac, dummyEscrow); - - testcase("float non-canonical"); - - { // non-canonical mantissa 100000e-4 - Bytes const y = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x86, 0xA0, 0xFF, 0xFF, 0xFF, 0xFC}; - auto const result = hfs.floatCompare(makeSlice(y), makeSlice(float10)); - BEAST_EXPECT(result && *result == 0); - } - } - - void - testFloatFromSTAmount() - { - testcase("floatFromSTAmount"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - { - // hfs.floatFromSTAmount(amount, -1); - STAmount const amount = XRP(100); - Bytes amountBytes = toBytes(amount); - vrt.setBytes(0, amountBytes.data(), amountBytes.size()); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_stamount"), - params, - result, - 0, - amountBytes.size(), - 256, - floatSize, - -1); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatFromSTAmount(amount, 4); - STAmount const amount = XRP(100); - Bytes amountBytes = toBytes(amount); - vrt.setBytes(0, amountBytes.data(), amountBytes.size()); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_stamount"), - params, - result, - 0, - amountBytes.size(), - 256, - floatSize, - 4); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatFromSTAmount(amount, 0); - STAmount const amount = XRP(0); - Bytes amountBytes = toBytes(amount); - vrt.setBytes(0, amountBytes.data(), amountBytes.size()); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_stamount"), - params, - result, - 0, - amountBytes.size(), - 256, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 2); - BEAST_EXPECT(resultBytes == floatIntZero); - } - - { - // hfs.floatFromSTAmount(amount, 0); - STAmount const amount = XRP(-1); - auto const y = hfs.floatFromMantExp(-1 * 1'000'000, 0, 0); - if (BEAST_EXPECT(y)) - { - Bytes amountBytes = toBytes(amount); - vrt.setBytes(0, amountBytes.data(), amountBytes.size()); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_stamount"), - params, - result, - 0, - amountBytes.size(), - 256, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 2); - BEAST_EXPECT(resultBytes == *y); - } - } - - { - // hfs.floatFromSTAmount(amount, 0); - auto const y = hfs.floatFromMantExp(9223372036854776, 3, 0); - STAmount const amount(noIssue(), std::numeric_limits::max()); - Bytes amountBytes = toBytes(amount); - vrt.setBytes(0, amountBytes.data(), amountBytes.size()); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_stamount"), - params, - result, - 0, - amountBytes.size(), - 256, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 2); - BEAST_EXPECT(resultBytes == *y); - } - - { - bool ex = false; - try - { - STAmount const amount(noIssue(), -1, Number::kMaxExponent + normalExp); - [[maybe_unused]] Bytes const amountBytes = toBytes(amount); - } - catch (...) - { - ex = true; - } - - BEAST_EXPECT(ex); - } - - auto const usd = env.master["USD"]; - { - // hfs.floatFromSTAmount(amount, 0); - STAmount const amount( - IOUAmount(STAmount::kMinValue, STAmount::kMinOffset), usd.issue()); - Bytes amountBytes = toBytes(amount); - vrt.setBytes(0, amountBytes.data(), amountBytes.size()); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_stamount"), - params, - result, - 0, - amountBytes.size(), - 256, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 2); - BEAST_EXPECT(resultBytes == floatMinIOU); - } - - { - // hfs.floatFromSTAmount(amount, 0); - STAmount const amount( - IOUAmount(STAmount::kMaxValue, STAmount::kMaxOffset), usd.issue()); - Bytes amountBytes = toBytes(amount); - vrt.setBytes(0, amountBytes.data(), amountBytes.size()); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_stamount"), - params, - result, - 0, - amountBytes.size(), - 256, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 2); - BEAST_EXPECT(resultBytes == floatMaxIOU); - } - } - - void - testFloatFromSTNumber() - { - testcase("floatFromSTNumber"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // Test with invalid rounding mode - { - // hfs.floatFromSTNumber(num, -1); - STNumber const num(sfNumber, Number(123, 0)); - Bytes numBytes = toBytes(num); - vrt.setBytes(0, numBytes.data(), numBytes.size()); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_stnumber"), - params, - result, - 0, - numBytes.size(), - 256, - floatSize, - -1); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatFromSTNumber(num, 4); - STNumber const num(sfNumber, Number(123, 0)); - Bytes numBytes = toBytes(num); - vrt.setBytes(0, numBytes.data(), numBytes.size()); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_stnumber"), - params, - result, - 0, - numBytes.size(), - 256, - floatSize, - 4); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(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(&import.at("float_from_stnumber"), - params, - result, - 0, - numBytes.size(), - 256, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 2); - BEAST_EXPECT(resultBytes == floatUIntMax); - } - - { - // hfs.floatFromSTNumber(num, 0); - STNumber const num(sfNumber, Number(-1, Number::kMaxExponent + normalExp)); - Bytes numBytes = toBytes(num); - vrt.setBytes(0, numBytes.data(), numBytes.size()); - WasmValVec params(5), result(1); - auto* trap = - ww(&import.at("float_from_stnumber"), - params, - result, - 0, - numBytes.size(), - 256, - floatSize, - 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const resultBytes = vrt.getBytes(params, 2); - BEAST_EXPECT(resultBytes == floatMinusMaxExp); - } - } - - void - testFloatToInt() - { - testcase("floatToInt"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - { - // hfs.floatToInt(makeSlice(float1), -1); - vrt.setBytes(0, float1.data(), float1.size()); - WasmValVec params(5), result(1); - auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, -1); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatToInt(makeSlice(float1), 4); - vrt.setBytes(0, float1.data(), float1.size()); - WasmValVec params(5), result(1); - auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 4); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatToInt(Slice(), 0); - WasmValVec params(5), result(1); - auto* trap = ww(&import.at("float_to_int"), params, result, 0, 0, 256, 8, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatToInt(makeSlice(invalid), 0); - vrt.setBytes(0, invalid.data(), invalid.size()); - WasmValVec params(5), result(1); - auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatToInt(makeSlice(floatIntZero), 0); - vrt.setBytes(0, floatIntZero.data(), floatIntZero.size()); - WasmValVec params(5), result(1); - auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 8); - auto const resultVal = vrt.getInt64(params, 2); - BEAST_EXPECT(resultVal == 0); - - // roundtrip - auto const result2 = hfs.floatFromInt(resultVal, 0); - BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatIntZero); - } - - { - // hfs.floatToInt(makeSlice(float1), 0); - vrt.setBytes(0, float1.data(), float1.size()); - WasmValVec params(5), result(1); - auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 8); - auto const resultVal = vrt.getInt64(params, 2); - BEAST_EXPECT(resultVal == 1); - - // roundtrip - auto const result2 = hfs.floatFromInt(resultVal, 0); - BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == float1); - } - - { - // hfs.floatToInt(makeSlice(floatMinus1), 0); - vrt.setBytes(0, floatMinus1.data(), floatMinus1.size()); - WasmValVec params(5), result(1); - auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 8); - auto const resultVal = vrt.getInt64(params, 2); - BEAST_EXPECT(resultVal == -1); - - // roundtrip - auto const result2 = hfs.floatFromInt(resultVal, 0); - BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatMinus1); - } - - { - // hfs.floatToInt(makeSlice(floatIntMax), 0); - vrt.setBytes(0, floatIntMax.data(), floatIntMax.size()); - WasmValVec params(5), result(1); - auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 8); - auto const resultVal = vrt.getInt64(params, 2); - BEAST_EXPECT(resultVal == std::numeric_limits::max()); - - // roundtrip - auto const result2 = hfs.floatFromInt(resultVal, 0); - BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatIntMax); - } - - { - // int64.min is rounded to nearest: -(2^63-1), which fits into int64 - // hfs.floatToInt(makeSlice(floatIntMin), 0); - vrt.setBytes(0, floatIntMin.data(), floatIntMin.size()); - WasmValVec params(5), result(1); - auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 8); - auto const resultVal = vrt.getInt64(params, 2); - BEAST_EXPECT(resultVal == -std::numeric_limits::max()); - - // roundtrip - auto const result2 = hfs.floatFromInt(resultVal, 0); - BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatIntMin); - } - - { - // hfs.floatToInt(makeSlice(floatUIntMax), 0); - vrt.setBytes(0, floatUIntMax.data(), floatUIntMax.size()); - WasmValVec params(5), result(1); - auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatComputationError)); - } - - // Test rounding modes with pi (3.141592653589793) - { - // to_nearest (mode 0): should round to 3 - // hfs.floatToInt(makeSlice(floatPi), 0); - vrt.setBytes(0, floatPi.data(), floatPi.size()); - WasmValVec params(5), result(1); - auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 0); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 8); - auto const resultVal = vrt.getInt64(params, 2); - BEAST_EXPECT(resultVal == 3); - } - - { - // towards_zero (mode 1): should truncate to 3 - // hfs.floatToInt(makeSlice(floatPi), 1); - vrt.setBytes(0, floatPi.data(), floatPi.size()); - WasmValVec params(5), result(1); - auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 1); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 8); - auto const resultVal = vrt.getInt64(params, 2); - BEAST_EXPECT(resultVal == 3); - } - - { - // downward (mode 2): should round down to 3 - // hfs.floatToInt(makeSlice(floatPi), 2); - vrt.setBytes(0, floatPi.data(), floatPi.size()); - WasmValVec params(5), result(1); - auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 2); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 8); - auto const resultVal = vrt.getInt64(params, 2); - BEAST_EXPECT(resultVal == 3); - } - - { - // upward (mode 3): should round up to 4 - // hfs.floatToInt(makeSlice(floatPi), 3); - vrt.setBytes(0, floatPi.data(), floatPi.size()); - WasmValVec params(5), result(1); - auto* trap = ww(&import.at("float_to_int"), params, result, 0, floatSize, 256, 8, 3); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == 8); - auto const resultVal = vrt.getInt64(params, 2); - BEAST_EXPECT(resultVal == 4); - } - } - - void - testFloatToMantExp() - { - testcase("floatToMantExp"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - { - // hfs.floatToMantExp(makeSlice(invalid)); - vrt.setBytes(0, invalid.data(), invalid.size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == - static_cast(HostFunctionError::FloatInputMalformed)); - } - - { - // hfs.floatToMantExp(makeSlice(floatIntZero)); - vrt.setBytes(0, floatIntZero.data(), floatIntZero.size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const mantissa = vrt.getInt64(params, 2); - auto const exponent = vrt.getInt32(params, 4); - BEAST_EXPECT(mantissa == 0) && - BEAST_EXPECT(exponent == std::numeric_limits::min()); - - // roundtrip - auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0); - BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatIntZero); - } - - { - // hfs.floatToMantExp(makeSlice(float1)); - vrt.setBytes(0, float1.data(), float1.size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const mantissa = vrt.getInt64(params, 2); - auto const exponent = vrt.getInt32(params, 4); - BEAST_EXPECT(mantissa == 1000000000000000000) && BEAST_EXPECT(exponent == -normalExp); - - // roundtrip - auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0); - BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == float1); - } - - { - // hfs.floatToMantExp(makeSlice(floatMinus1)); - vrt.setBytes(0, floatMinus1.data(), floatMinus1.size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const mantissa = vrt.getInt64(params, 2); - auto const exponent = vrt.getInt32(params, 4); - BEAST_EXPECT(mantissa == -1000000000000000000) && BEAST_EXPECT(exponent == -normalExp); - - // roundtrip - auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0); - BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatMinus1); - } - - { - // hfs.floatToMantExp(makeSlice(float10)); - vrt.setBytes(0, float10.data(), float10.size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const mantissa = vrt.getInt64(params, 2); - auto const exponent = vrt.getInt32(params, 4); - BEAST_EXPECT(mantissa == 1000000000000000000) && - BEAST_EXPECT(exponent == -normalExp + 1); - - // roundtrip - auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0); - BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == float10); - } - - { - // hfs.floatToMantExp(makeSlice(floatPi)); - vrt.setBytes(0, floatPi.data(), floatPi.size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const mantissa = vrt.getInt64(params, 2); - auto const exponent = vrt.getInt32(params, 4); - BEAST_EXPECT(mantissa == 3141592653589793000) && BEAST_EXPECT(exponent == -normalExp); - - // roundtrip - auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0); - BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatPi); - } - - { - // hfs.floatToMantExp(makeSlice(floatIntMax)); - vrt.setBytes(0, floatIntMax.data(), floatIntMax.size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const mantissa = vrt.getInt64(params, 2); - auto const exponent = vrt.getInt32(params, 4); - BEAST_EXPECT(mantissa == std::numeric_limits::max()) && - BEAST_EXPECT(exponent == 0); - - // roundtrip - auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0); - BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatIntMax); - } - - { - // hfs.floatToMantExp(makeSlice(floatIntMin)); - vrt.setBytes(0, floatIntMin.data(), floatIntMin.size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const mantissa = vrt.getInt64(params, 2); - auto const exponent = vrt.getInt32(params, 4); - BEAST_EXPECT(mantissa == -std::numeric_limits::max()) && - BEAST_EXPECT(exponent == 0); - - // roundtrip - auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0); - BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatIntMin); - } - - { - // hfs.floatToMantExp(makeSlice(floatMax)); - vrt.setBytes(0, floatMax.data(), floatMax.size()); - WasmValVec params(6), result(1); - auto* trap = - ww(&import.at("float_to_mant_exp"), params, result, 0, floatSize, 256, 8, 512, 4); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == floatSize); - auto const mantissa = vrt.getInt64(params, 2); - auto const exponent = vrt.getInt32(params, 4); - BEAST_EXPECT(mantissa == Number::kMaxRep) && - BEAST_EXPECT(exponent == Number::kMaxExponent); - - // roundtrip - auto const result2 = hfs.floatFromMantExp(mantissa, exponent, 0); - BEAST_EXPECT(result2) && BEAST_EXPECT(*result2 == floatMax); - } - } - - void - testFloats() - { - // for checking binary formats manually - // printNumbersBin(); - - testTraceFloat(); - testFloatFromInt(); - testFloatFromUint(); - testFloatFromSTAmount(); - testFloatFromSTNumber(); - testFloatToInt(); - testFloatToMantExp(); - testfloatFromMantExp(); - testFloatCompare(); - testFloatAdd(); - testFloatSubtract(); - testFloatMultiply(); - testFloatDivide(); - testFloatRoot(); - testFloatPower(); - testFloatSpecialCases(); - } - - void - testVectorIndexes() - { - testcase("WasmValVec indicies"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - bool ex = false; - try - { - // hfs.getLedgerSqn(); - WasmValVec params(2), result(1); - // 3 parameters instead of 2 - auto* trap = ww(&import.at("ldgr_index"), params, result, 0, sizeof(std::uint32_t), 1); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == sizeof(std::uint32_t)) && - BEAST_EXPECT(vrt.getUint32(params, 0) == env.current()->header().seq); - } - catch (std::exception const& e) - { - BEAST_EXPECTS(e.what() == std::string("Out of bound"), e.what()); - ex = true; - } - - // const version - ex = false; - try - { - WasmValVec params(2); - [[maybe_unused]] auto const x = params[2]; - } - catch (std::exception const& e) - { - BEAST_EXPECTS(e.what() == std::string("Out of bound"), e.what()); - ex = true; - } - - BEAST_EXPECT(ex); - } - - void - testTransferLimit() - { - testcase("transferLimit"); - using namespace test::jtx; - - Env env{*this}; - OpenView ov{*env.current()}; - ApplyContext ac = createApplyContext(env, ov); - auto const dummyEscrow = - keylet::escrow(env.master, SeqProxy::rawSequence(env.seq(env.master))); - VirtualRuntime vrt; - WasmHostFunctionsImpl hfs(ac, dummyEscrow); - - auto import = xrpl::createWasmImport(hfs); - hfs.setRT(vrt); - - // Test 1: Test setData() - copying FROM host TO wasm - // Multiple calls to getLedgerSqn() which uses setData() to write result to WASM memory - vrt.setTransferLimit(kWasmTransferLimit + 1024); - - // hfs.getLedgerSqn(); - for (int i = 0; i < (kWasmTransferLimit / vrt.transferDiff) - 3; ++i) - { - WasmValVec params(2), result(1); - - auto* trap = ww(&import.at("ldgr_index"), params, result, 0, sizeof(std::uint32_t)); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT(result[0].of.i32 == sizeof(std::uint32_t)) && - BEAST_EXPECT(vrt.getUint32(params, 0) == env.current()->header().seq); - } - - BEAST_EXPECT((vrt.getTestTransferLimit() >= 0) && (vrt.getTestTransferLimit() < 1024)); - - // Next call should hit OutOfTransferLimit - { - WasmValVec params(2), result(1); - auto* trap = ww(&import.at("ldgr_index"), params, result, 0, sizeof(std::uint32_t)); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == hfErrorToInt(HostFunctionError::OutOfTransferLimit)); - } - - // After limit exhausted, all next call return OutOfTransferLimit - { - WasmValVec params(2), result(1); - auto* trap = ww(&import.at("ldgr_index"), params, result, 0, sizeof(std::uint32_t)); - - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == hfErrorToInt(HostFunctionError::OutOfTransferLimit)); - } - - // Reset transfer limit to a small value that can accommodate overhead but not AccountID - // copy - vrt.setTransferLimit(vrt.transferDiff + 10); - - Account const alice("alice"); - auto const aliceID = env.master.id(); - vrt.setBytes(0, aliceID.data(), AccountID::size()); - - // This should fail because getDataAccountID() needs to copy AccountID (20 bytes) - // After getTransferLimit() overhead (1024), we only have 10 bytes left, not enough for 20 - { - WasmValVec params(4), result(1); - auto* trap = - ww(&import.at("accountroot_id"), params, result, 0, AccountID::size(), 100, 32); - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == hfErrorToInt(HostFunctionError::OutOfTransferLimit)); - } - - // Verify that reading slices (without copying) does NOT consume transfer limit - vrt.setTransferLimit(vrt.transferDiff + 10); - - // trace() uses getDataString() -> getDataSlice() which does NOT check transfer limit - std::string testMsg = "This message is longer than 10 bytes to prove slices don't count"; - vrt.setBytes(0, testMsg.data(), testMsg.size()); - vrt.setBytes( - 100, - reinterpret_cast("dummy"), - 5); // Empty data slice for trace - { - WasmValVec params(5), result(0); - // trace(msg_ptr, msg_len, data_type, data_ptr, data_len) -- returns nothing - auto* trap = - ww(&import.at("trace"), - params, - result, - 0, - testMsg.size(), - traceDataTypeToInt(TraceDataType::AsText), - 100, - 5); - - // Should not trap even though the message is >10 bytes, because trace only reads - // slices (no transfer limit check in getDataSlice) and never charges the limit. - BEAST_EXPECT(!trap); - } - - // setData should return OutOfTransferLimit when the transfer limit is exhausted. - // trace left the limit untouched, so the next getTransferLimit() overhead (1024) - // takes 1034 down to 10 -- not enough for the 32-byte hash copy. - { - WasmValVec params(2), result(1); - auto* trap = ww(&import.at("parent_ldgr_hash"), params, result, 500, 32); - - // the transfer limit went negative - BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && - BEAST_EXPECT( - result[0].of.i32 == hfErrorToInt(HostFunctionError::OutOfTransferLimit)); - } - } - - void - run() override - { - testGetLedgerSqn(); - testGetParentLedgerTime(); - testGetParentLedgerHash(); - testGetBaseFee(); - testIsAmendmentEnabled(); - testCacheLedgerObj(); - testGetTxField(); - testGetCurrentLedgerObjField(); - testGetLedgerObjField(); - testGetTxNestedField(); - testGetCurrentLedgerObjNestedField(); - testGetLedgerObjNestedField(); - testGetTxArrayLen(); - testGetCurrentLedgerObjArrayLen(); - testGetLedgerObjArrayLen(); - testGetTxNestedArrayLen(); - testGetCurrentLedgerObjNestedArrayLen(); - testGetLedgerObjNestedArrayLen(); - testUpdateData(); - testCheckSignature(); - testComputeSha512HalfHash(); - testKeyletFunctions(); - testGetNFT(); - testGetNFTIssuer(); - testGetNFTTaxon(); - testGetNFTFlags(); - testGetNFTTransferFee(); - testGetNFTSerial(); - testTrace(); - testTraceNum(); - testTraceAccount(); - testTraceAmount(); - testFloats(); - - testVectorIndexes(); - - testTransferLimit(); - } -}; - -BEAST_DEFINE_TESTSUITE(HostFuncImpl, app, xrpl); - -} // namespace xrpl::test -*/ diff --git a/src/tests/libxrpl/tx/wasm/FloatFixture.h b/src/tests/libxrpl/tx/wasm/FloatFixture.h new file mode 100644 index 0000000000..b5cbb66a21 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/FloatFixture.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +#include + +#include +#include +#include + +namespace xrpl::test { + +// Known float bit patterns (12-byte `wasm_float` regions) — inputs / expected values for +// the float host functions. `kNormalExp` is the exponent offset the encoding uses. +namespace floats { +inline constexpr int kNormalExp = 18; + +// clang-format off +inline Bytes const kIntMin = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00}; // -2^63 (rounds to -(2^63-1)) +inline Bytes const kIntZero = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00}; // 0 +inline Bytes const kIntMax = {0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}; // 2^63-1 +inline Bytes const kUintMax = {0x19, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, 0x00, 0x00, 0x00, 0x01}; // 2^64-1 +inline Bytes const kMaxExp = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00}; // 1e(kMaxExponent + kNormalExp) +inline Bytes const kPreMaxExp = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF}; // 1e(kMaxExponent + kNormalExp - 1) +inline Bytes const kMinusMaxExp = {0xF2, 0x1F, 0x49, 0x4C, 0x58, 0x9C, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00}; // -1e(kMaxExponent + kNormalExp) +inline Bytes const kMinExp = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00}; // 1e(kMinExponent - kNormalExp) +inline Bytes const kMax = {0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x80, 0x00}; // kMaxRep e(kMaxExponent - kNormalExp) +inline Bytes const kMaxIOU = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x63, 0xFF, 0x9C, 0x00, 0x00, 0x00, 0x4E}; // 9999999999999999e(96) +inline Bytes const kMinIOU = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x9D}; // 1e(-81) +inline Bytes const kOne = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // 1 +inline Bytes const kMinusOne = {0xF2, 0x1F, 0x49, 0x4C, 0x58, 0x9C, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // -1 +inline Bytes const kOneMore = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x03, 0xE8, 0xFF, 0xFF, 0xFF, 0xEE}; // 1.000000000000001 +inline Bytes const kTwo = {0x1B, 0xC1, 0x6D, 0x67, 0x4E, 0xC8, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // 2 +inline Bytes const kTen = {0x0D, 0xE0, 0xB6, 0xB3, 0xA7, 0x64, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEF}; // 10 +inline Bytes const kPi = {0x2B, 0x99, 0x2D, 0xDF, 0xA2, 0x32, 0x48, 0xE8, 0xFF, 0xFF, 0xFF, 0xEE}; // 3.141592653589793 +inline Bytes const kInvalidZero = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00}; // non-canonical zero +inline Bytes const kMinusThree = {0xD6, 0x5D, 0xDB, 0xE5, 0x09, 0xD4, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xEE}; // -3 +// clang-format on + +inline std::string const kInvalidData = "invalid_data"; +} // namespace floats + +struct FloatTest : WasmImplTest +{ + static constexpr std::int64_t kMin64 = std::numeric_limits::min(); + static constexpr std::int64_t kMax64 = std::numeric_limits::max(); + + static Slice + slice(Bytes const& b) + { + return Slice{b.data(), b.size()}; + } +}; + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/NFTFixture.h b/src/tests/libxrpl/tx/wasm/NFTFixture.h new file mode 100644 index 0000000000..950a477340 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/NFTFixture.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl::test { + +struct NFTTest : WasmImplTest +{ + static constexpr std::uint16_t kFlags = nft::kFlagTransferable | nft::kFlagBurnable; + static constexpr std::uint16_t kFee = 314; + static constexpr std::uint32_t kTaxon = 12345; + static constexpr std::uint32_t kSequence = 7; + + static uint256 + makeNftId(AccountID const& issuer) + { + return NFTokenMint::createNFTokenID(kFlags, kFee, issuer, nft::toTaxon(kTaxon), kSequence); + } + + // Mint a real NFToken owned by `issuer` (taxon 0) and return its id, read back from the + // owner's NFTokenPage. TxTest applies to the open ledger, which produces no metadata, so + // the id is recovered from ledger state rather than from the mint's metadata. + uint256 + mintNFT(Account const& issuer, std::optional uri = std::nullopt) + { + auto builder = transactions::NFTokenMintBuilder{issuer.id(), 0u}; + if (uri) + builder.setURI(Slice{uri->data(), uri->size()}); + auto const r = ledger.submit(builder, issuer); + EXPECT_EQ(r.ter, tesSUCCESS) << transToken(r.ter); + ledger.close(); + + // The single minted token lives in the owner's first NFTokenPage. + auto const& view = ledger.getOpenLedger(); + auto const first = keylet::nftokenPageMin(issuer.id()).key; + auto const last = keylet::nftokenPageMax(issuer.id()).key; + auto const pageKey = view.succ(first, last.next()); + EXPECT_TRUE(pageKey.has_value()); + auto const page = pageKey ? view.read(Keylet{ltNFTOKEN_PAGE, *pageKey}) : nullptr; + EXPECT_NE(page, nullptr); + if (!page) + return uint256{}; + auto const& tokens = page->getFieldArray(sfNFTokens); + EXPECT_FALSE(tokens.empty()); + return tokens.empty() ? uint256{} : tokens[0].getFieldH256(sfNFTokenID); + } +}; + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/RealHostFixture.h b/src/tests/libxrpl/tx/wasm/RealHostFixture.h index 5259c540e6..55474f52bb 100644 --- a/src/tests/libxrpl/tx/wasm/RealHostFixture.h +++ b/src/tests/libxrpl/tx/wasm/RealHostFixture.h @@ -1,85 +1,108 @@ #pragma once -// Base for the "impl" wasm tests: a *real* `WasmHostFunctionsImpl` over a *real* ledger, -// built with no Application / jtx / beast::unit_test::Suite. The ledger is a `TxTest` -// (genesis ledger + OpenView + real transactor dispatch); the host is constructed from -// its `ServiceRegistry` and `OpenView` through an `ApplyContext`. -// -// This is the counterpart to `HostContextFixture` (mock host, interop): here the host -// really computes, so a test asserts a value against the ledger's own source of truth -// (`keylet::escrow`, a real field's bytes, `wasm_float`), rather than what a mock was -// asked. Pure-computation host functions (keylets, floats, check_sig, sha512_half, nft -// decoders) ignore the ledger; the reading getters read the object `leKey` points at. - +#include #include +#include #include // TapNone +#include #include #include #include // keylet::account #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 +inline Bytes toBytes(std::uint8_t value) { return {value}; } -static Bytes +inline Bytes toBytes(std::uint16_t value) { - auto const* b = reinterpret_cast(&value); - auto const* e = reinterpret_cast(&value + 1); - return Bytes{b, e}; + return {static_cast(value), static_cast(value >> 8)}; } -static Bytes +inline Bytes toBytes(std::uint32_t value) { - auto const* b = reinterpret_cast(&value); - auto const* e = reinterpret_cast(&value + 1); - return Bytes{b, e}; + return { + static_cast(value), + static_cast(value >> 8), + static_cast(value >> 16), + static_cast(value >> 24)}; } -static Bytes +inline Bytes toBytes(uint256 const& value) { - return Bytes{value.begin(), value.end()}; + return Bytes{std::begin(value), std::end(value)}; } -static Bytes +inline Bytes +toBytes(std::string_view value) +{ + return Bytes{std::begin(value), std::end(value)}; +} + +inline Bytes +toBytes(std::span value) +{ + return Bytes{std::begin(value), std::end(value)}; +} + +inline Bytes +toBytes(AccountID const& account) +{ + return Bytes{std::begin(account), std::end(account)}; +} + +inline Bytes toBytes(Issue const& issue) { - Serializer s; + auto s = Serializer{}; s.addBitString(issue.currency); if (!isXRP(issue.currency)) s.addBitString(issue.account); - auto const data = s.getData(); - return data; + return s.getData(); } -static Bytes +inline Bytes toBytes(Asset const& asset) { if (asset.holds()) @@ -90,61 +113,247 @@ toBytes(Asset const& asset) return Bytes{mptID.cbegin(), mptID.cend()}; } -static Bytes +inline Bytes toBytes(STAmount const& amount) { - Serializer msg; + auto msg = Serializer{}; amount.add(msg); - auto const data = msg.getData(); - - return data; + return msg.getData(); } -static Bytes +inline Bytes toBytes(STNumber const& number) { - Serializer msg; + auto msg = Serializer{}; number.add(msg); - auto const data = msg.getData(); - - return data; + return msg.getData(); } -class WasmImplTest : public testing::Test +template +void +expectValue(std::expected const& result, U const& expected) +{ + ASSERT_TRUE(result.has_value()) + << "expected a value, got error " << static_cast(result.error()); + EXPECT_EQ(*result, expected); +} + +template +void +expectError(std::expected const& result, HostFunctionError expected) +{ + ASSERT_FALSE(result.has_value()) << "expected error, got a value"; + EXPECT_EQ(result.error(), expected); +} + +inline void +expectKeyletMatches(std::expected const& result, Keylet const& expected) +{ + expectValue(result, toBytes(expected.key)); +} + +struct SignedMessage +{ + Bytes message; + Bytes signature; + Bytes publicKey; +}; + +inline SignedMessage +signMessage(std::string_view message, KeyType keyType = KeyType::Secp256k1) +{ + auto const [pk, sk] = randomKeyPair(keyType); + auto const msg = Bytes{std::begin(message), std::end(message)}; + auto const sig = sign(pk, sk, Slice{msg.data(), msg.size()}); + return { + .message = msg, + .signature = Bytes{sig.data(), sig.data() + sig.size()}, + .publicKey = Bytes{pk.data(), pk.data() + pk.size()}}; +} + +inline uint256 +credentialId( + std::string_view hex = "0011223344556677889900112233445566778899001122334455667788990011") +{ + auto id = uint256{}; + EXPECT_TRUE(id.parseHex(std::string{hex})); + return id; +} + +inline STObject +makeMemo(Bytes const& data) +{ + auto memo = STObject::makeInnerObject(sfMemo); + memo.setFieldVL(sfMemoData, data); + return memo; +} + +struct TxAssembler +{ + TxType type; + std::function build; +}; + +inline TxAssembler +bareTx(TxType type = ttESCROW_FINISH) +{ + return {.type = type, .build = [](STObject&) {}}; +} + +inline TxAssembler +escrowFinishTx(TxTest& ledger, Account const& acct) +{ + return {.type = ttESCROW_FINISH, .build = [&ledger, acct](STObject& obj) { + auto credId = uint256{}; + EXPECT_TRUE(credId.parseHex( + "0011223344556677889900112233445566778899001122334455667788990011")); + + obj.setAccountID(sfAccount, acct.id()); + obj.setAccountID(sfOwner, acct.id()); + obj.setFieldU32(sfOfferSequence, ledger.getAccountRoot(acct.id()).getSequence()); + obj.setFieldArray(sfMemos, STArray{}); + auto credIds = STVector256{}; + credIds.pushBack(credId); + obj.setFieldV256(sfCredentialIDs, credIds); + }}; +} + +inline TxAssembler +ammDepositTx(Account const& acct, Asset const& asset1, Asset const& asset2) +{ + return {.type = ttAMM_DEPOSIT, .build = [acct, asset1, asset2](STObject& obj) { + obj.setAccountID(sfAccount, acct.id()); + obj.setFieldIssue(sfAsset, STIssue{sfAsset, asset1}); + obj.setFieldIssue(sfAsset2, STIssue{sfAsset2, asset2}); + }}; +} + +inline TxAssembler +mptIssuanceCreateTx(Account const& acct, std::uint8_t scale) +{ + return {.type = ttMPTOKEN_ISSUANCE_CREATE, .build = [acct, scale](STObject& obj) { + obj.setAccountID(sfAccount, acct.id()); + obj.setFieldU8(sfAssetScale, scale); + }}; +} + +class WasmHost { public: - // The real ledger. Tests populate it with `ledger.createAccount()` / `submit()` / - // `close()` before reading through the host. - TxTest ledger; - - // A real host bound to `leKey` — the "current"/home object the `*_field` and - // `*_arr_len` getters read. Defaults to a throwaway keylet for the many functions - // (keylets, floats, sig, hash, nft decoders) that never touch the current object. - // - // Returns a reference into a fixture-owned host so its `ApplyContext&` outlives it; - // call once per test. - WasmHostFunctionsImpl& - host(Keylet const& leKey = keylet::account(AccountID{})) + WasmHost( + std::shared_ptr tx, + std::unique_ptr context, + std::unique_ptr host) + : tx_{std::move(tx)}, context_{std::move(context)}, host_{std::move(host)} + { + } + + WasmHostFunctionsImpl* + operator->() const + { + return host_.get(); + } + WasmHostFunctionsImpl& + operator*() const { - // The finish tx the host runs under. Its contents are irrelevant to the - // functions these tests exercise; it only has to be a well-formed shell. - finishTx_ = std::make_shared(ttESCROW_FINISH, [](STObject&) {}); - context_.emplace( - ledger.getServiceRegistry(), - ledger.getOpenLedger(), - *finishTx_, - tesSUCCESS, - ledger.getOpenLedger().fees().base, - TapNone, - beast::Journal{beast::Journal::getNullSink()}); - host_.emplace(*context_, leKey); return *host_; } private: - std::shared_ptr finishTx_; - std::optional context_; - std::optional host_; + std::shared_ptr tx_; + std::unique_ptr context_; + std::unique_ptr host_; +}; + +class WasmImplTest : public testing::Test +{ +public: + TxTest ledger; + + Account + fund(char const* name, XRPAmount amount = XRP(1000)) + { + auto const account = Account{name}; + ledger.createAccount(account, amount); + return account; + } + + WasmHost + makeHost( + beast::Journal journal, + Keylet const& leKey = keylet::account(AccountID{}), + TxType txType = ttESCROW_FINISH, + std::function assembler = [](STObject&) {}) + { + auto tx = std::make_shared( + txType, [assembler = std::move(assembler)](STObject& obj) { assembler(obj); }); + auto context = std::make_unique( + ledger.getServiceRegistry(), + ledger.getOpenLedger(), + *tx, + tesSUCCESS, + ledger.getOpenLedger().fees().base, + TapNone, + journal); + auto host = std::make_unique(*context, leKey); + return WasmHost{std::move(tx), std::move(context), std::move(host)}; + } + + // The common case: a host that discards its log output. + WasmHost + makeHost( + Keylet const& leKey = keylet::account(AccountID{}), + TxType txType = ttESCROW_FINISH, + std::function assembler = [](STObject&) {}) + { + return makeHost( + beast::Journal{beast::Journal::getNullSink()}, leKey, txType, std::move(assembler)); + } + + // A host whose `trace` output is captured, so a test can read it back with `logged()`. + // The sink is a fixture member, so it outlives the host and accumulates across a test. + WasmHost + makeTracingHost( + Keylet const& leKey = keylet::account(AccountID{}), + TxType txType = ttESCROW_FINISH, + std::function assembler = [](STObject&) {}) + { + return makeHost(beast::Journal{traceSink_}, leKey, txType, std::move(assembler)); + } + + // Everything `trace` has written to the tracing host so far. + [[nodiscard]] std::string + logged() const + { + return traceSink_.messages(); + } + + // Submit a real SignerListSet so `keylet::signerList(owner)` exists — the object the + // signer-list nested-field / array-length getters read. `signers` pairs each signer + // account with its weight. + void + makeSignerList( + Account const& owner, + std::uint32_t quorum, + std::vector> const& signers) + { + auto entries = STArray{}; + for (auto const& [signer, weight] : signers) + { + auto entry = STObject::makeInnerObject(sfSignerEntry); + entry.setAccountID(sfAccount, signer.id()); + entry.setFieldU16(sfSignerWeight, weight); + entries.push_back(std::move(entry)); + } + auto const r = ledger.submit( + transactions::SignerListSetBuilder{owner.id(), quorum}.setSignerEntries(entries), + owner); + EXPECT_EQ(r.ter, tesSUCCESS) << transToken(r.ter); + ledger.close(); + } + +private: + CaptureSink traceSink_{beast::Severity::Trace}; }; } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/AccountKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/AccountKeylet.cpp index ec9a95eda0..b7bb703b7e 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/AccountKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/AccountKeylet.cpp @@ -4,12 +4,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct AccountKeyletImpl : WasmImplTest @@ -18,22 +14,14 @@ struct AccountKeyletImpl : WasmImplTest TEST_F(AccountKeyletImpl, MatchesAccountKeyletFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); - auto const expected = keylet::account(owner.id()); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().accountKeylet(owner); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches(makeHost()->accountKeylet(owner), keylet::account(owner.id())); } TEST_F(AccountKeyletImpl, UnsetAccountIsInvalidAccount) { - auto const result = host().accountKeylet(AccountID{}); - - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError(makeHost()->accountKeylet(AccountID{}), HostFunctionError::InvalidAccount); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/AmmKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/AmmKeylet.cpp index e5e9652ee7..272779ae99 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/AmmKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/AmmKeylet.cpp @@ -5,12 +5,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct AmmKeyletImpl : WasmImplTest @@ -19,35 +15,26 @@ struct AmmKeyletImpl : WasmImplTest TEST_F(AmmKeyletImpl, MatchesAmmKeyletFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); auto usdIssue = Issue{toCurrency("USD"), owner.id()}; - auto const expected = keylet::amm(xrpIssue(), usdIssue); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().ammKeylet(usdIssue, xrpIssue()); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches( + makeHost()->ammKeylet(usdIssue, xrpIssue()), keylet::amm(xrpIssue(), usdIssue)); } TEST_F(AmmKeyletImpl, InvalidIssue1) { - auto const result = host().ammKeylet(xrpIssue(), xrpIssue()); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidParams); + expectError(makeHost()->ammKeylet(xrpIssue(), xrpIssue()), HostFunctionError::InvalidParams); } TEST_F(AmmKeyletImpl, InvalidIssue2) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); auto baseMpt = makeMptID(1, owner.id()); - auto const result = host().ammKeylet(baseMpt, xrpIssue()); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidParams); + expectError(makeHost()->ammKeylet(baseMpt, xrpIssue()), HostFunctionError::InvalidParams); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/BaseFee.cpp b/src/tests/libxrpl/tx/wasm/host_functions/BaseFee.cpp index dbe9c75143..8cf2be216a 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/BaseFee.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/BaseFee.cpp @@ -1,8 +1,6 @@ #include #include -#include - namespace xrpl::test { struct BaseFeeImpl : WasmImplTest @@ -11,9 +9,7 @@ struct BaseFeeImpl : WasmImplTest TEST_F(BaseFeeImpl, MatchesLedger) { - auto const result = host().getBaseFee(); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, ledger.getOpenLedger().fees().base.drops()); + expectValue(makeHost()->getBaseFee(), ledger.getOpenLedger().fees().base.drops()); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/CacheLedgerObj.cpp b/src/tests/libxrpl/tx/wasm/host_functions/CacheLedgerObj.cpp index d5ba55cbb0..a717079c27 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/CacheLedgerObj.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/CacheLedgerObj.cpp @@ -9,7 +9,6 @@ #include #include -#include namespace xrpl::test { @@ -21,16 +20,16 @@ struct CacheLedgerObjImpl : WasmImplTest auto const owner = Account{"owner"}; ledger.createAccount(owner, XRP(1000)); - auto& h = host(); + auto h = makeHost(); auto const key = keylet::account(owner.id()).key; for (auto i = int32_t{1}; i < 257; ++i) { - auto const slot = h.cacheLedgerObj(key, i); + auto const slot = h->cacheLedgerObj(key, i); ASSERT_TRUE(slot.has_value()) << "cacheLedgerObj should find the created account"; EXPECT_EQ(*slot, i); - auto const account = h.getLedgerObjField(*slot, sfAccount); + auto const account = h->getLedgerObjField(*slot, sfAccount); ASSERT_TRUE(account.has_value()); Bytes const ownerBytes{owner.id().begin(), owner.id().end()}; EXPECT_EQ(*account, ownerBytes); @@ -43,7 +42,7 @@ struct CacheLedgerObjImpl : WasmImplTest // Every slot is now occupied, so asking to auto-allocate (cacheIdx == 0) has nowhere // to put the object. - auto const result = h.cacheLedgerObj(key, 0); + auto const result = h->cacheLedgerObj(key, 0); ASSERT_FALSE(result.has_value()); EXPECT_EQ(result.error(), HostFunctionError::SlotsFull); } @@ -61,11 +60,11 @@ TEST_F(CacheLedgerObjImpl, MatchesLedgerImplicitIndices) TEST_F(CacheLedgerObjImpl, OutOfRange) { - auto result = host().cacheLedgerObj(uint256{}, -1); + auto result = makeHost()->cacheLedgerObj(uint256{}, -1); ASSERT_FALSE(result.has_value()); EXPECT_EQ(result.error(), HostFunctionError::SlotOutRange); - result = host().cacheLedgerObj(uint256{}, 257); + result = makeHost()->cacheLedgerObj(uint256{}, 257); ASSERT_FALSE(result.has_value()); EXPECT_EQ(result.error(), HostFunctionError::SlotOutRange); } @@ -73,9 +72,26 @@ TEST_F(CacheLedgerObjImpl, OutOfRange) TEST_F(CacheLedgerObjImpl, LedgerObjNotFound) { auto const ghost = keylet::account(Account{"ghost"}.id()).key; - auto result = host().cacheLedgerObj(ghost, 0); + auto result = makeHost()->cacheLedgerObj(ghost, 0); ASSERT_FALSE(result.has_value()); EXPECT_EQ(result.error(), HostFunctionError::LedgerObjNotFound); } +// Two hosts built from the same fixture are fully independent: each owns its own slot +// table, so caching into one leaves the other's slots empty. (This is what the `WasmHost` +// handle buys over the old shared-fixture-state design.) +TEST_F(CacheLedgerObjImpl, IndependentHostsDoNotShareSlots) +{ + auto const owner = fund("owner"); + auto const key = keylet::account(owner.id()).key; + + auto a = makeHost(); + auto b = makeHost(); + + ASSERT_TRUE(a->cacheLedgerObj(key, 1).has_value()); + expectValue(a->getLedgerObjField(1, sfAccount), toBytes(owner.id())); + // `b` never cached anything, so its slot 1 is still empty. + expectError(b->getLedgerObjField(1, sfAccount), HostFunctionError::EmptySlot); +} + } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/CheckKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/CheckKeylet.cpp index 85373e4bc7..420a2d190a 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/CheckKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/CheckKeylet.cpp @@ -5,12 +5,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct CheckKeyletImpl : WasmImplTest @@ -19,22 +15,16 @@ struct CheckKeyletImpl : WasmImplTest TEST_F(CheckKeyletImpl, MatchesCheckKeyletFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); - auto const expected = keylet::check(owner.id(), SeqProxy::rawSequence(1u)); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().checkKeylet(owner.id(), 1u); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches( + makeHost()->checkKeylet(owner.id(), 1u), + keylet::check(owner.id(), SeqProxy::rawSequence(1u))); } TEST_F(CheckKeyletImpl, UnsetAccountIsInvalidAccount) { - auto const result = host().checkKeylet(AccountID{}, 1u); - - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError(makeHost()->checkKeylet(AccountID{}, 1u), HostFunctionError::InvalidAccount); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/CheckSignature.cpp b/src/tests/libxrpl/tx/wasm/host_functions/CheckSignature.cpp new file mode 100644 index 0000000000..5119c90acd --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/CheckSignature.cpp @@ -0,0 +1,80 @@ +#include +#include +#include + +#include +#include + +#include + +namespace xrpl::test { + +struct CheckSignatureImpl : WasmImplTest +{ +}; + +TEST_F(CheckSignatureImpl, ValidSignature) +{ + auto const kp = generateKeyPair(KeyType::Secp256k1, randomSeed()); + auto const& pk = kp.first; + auto const& sk = kp.second; + auto const& message = std::string{"hello signature"}; + auto const sig = sign(pk, sk, Slice(message.data(), message.size())); + + auto const result = makeHost()->checkSignature( + Slice{message.data(), message.size()}, Slice{sig.data(), sig.size()}, pk); + expectValue(result, std::int32_t{1}); +} + +TEST_F(CheckSignatureImpl, InvalidSignature) +{ + auto const kp = generateKeyPair(KeyType::Secp256k1, randomSeed()); + auto const& pk = kp.first; + auto const& sk = kp.second; + auto const& message = std::string{"hello signature"}; + auto const sig = sign(pk, sk, Slice(message.data(), message.size())); + auto const badSignature = std::string(sig.size(), 0xFF); + + auto const result = makeHost()->checkSignature( + Slice{message.data(), message.size()}, Slice{badSignature.data(), badSignature.size()}, pk); + expectValue(result, std::int32_t{0}); +} + +TEST_F(CheckSignatureImpl, InvalidPublicKey) +{ + auto const kp = generateKeyPair(KeyType::Secp256k1, randomSeed()); + auto const kp2 = generateKeyPair(KeyType::Secp256k1, randomSeed()); + auto const& pk = kp.first; + auto const& sk = kp.second; + auto const& message = std::string{"hello signature"}; + auto const sig = sign(pk, sk, Slice(message.data(), message.size())); + + auto const result = makeHost()->checkSignature( + Slice{message.data(), message.size()}, Slice{sig.data(), sig.size()}, kp2.first); + expectValue(result, std::int32_t{0}); +} + +TEST_F(CheckSignatureImpl, EmptySignature) +{ + auto const kp = generateKeyPair(KeyType::Secp256k1, randomSeed()); + auto const& pk = kp.first; + auto const& message = std::string{"hello signature"}; + + auto const result = + makeHost()->checkSignature(Slice{message.data(), message.size()}, Slice{}, pk); + expectValue(result, std::int32_t{0}); +} + +TEST_F(CheckSignatureImpl, EmptyMessage) +{ + auto const kp = generateKeyPair(KeyType::Secp256k1, randomSeed()); + auto const& pk = kp.first; + auto const& sk = kp.second; + auto const& message = std::string{"hello signature"}; + auto const sig = sign(pk, sk, Slice(message.data(), message.size())); + + auto const result = makeHost()->checkSignature(Slice{}, Slice{sig.data(), sig.size()}, pk); + expectValue(result, std::int32_t{0}); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/CredentialKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/CredentialKeylet.cpp index 74455297d4..5a81f79687 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/CredentialKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/CredentialKeylet.cpp @@ -6,12 +6,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct CredentialKeyletImpl : WasmImplTest @@ -20,23 +16,19 @@ struct CredentialKeyletImpl : WasmImplTest TEST_F(CredentialKeyletImpl, MatchesCredentialKeyletFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); auto const credTypeStr = std::string{"test"}; auto const credType = Slice{credTypeStr.data(), credTypeStr.size()}; - auto const expected = keylet::credential(owner.id(), owner.id(), credType); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().credentialKeylet(owner.id(), owner.id(), credType); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches( + makeHost()->credentialKeylet(owner.id(), owner.id(), credType), + keylet::credential(owner.id(), owner.id(), credType)); } TEST_F(CredentialKeyletImpl, CredentialTypeStringTooLong) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); auto constexpr credTypeStr = std::string_view{ "abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]" @@ -44,26 +36,25 @@ TEST_F(CredentialKeyletImpl, CredentialTypeStringTooLong) static_assert(credTypeStr.size() > kMaxCredentialTypeLength); auto const credType = Slice{credTypeStr.data(), credTypeStr.size()}; - auto const result = host().credentialKeylet(owner.id(), owner.id(), credType); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidParams); + expectError( + makeHost()->credentialKeylet(owner.id(), owner.id(), credType), + HostFunctionError::InvalidParams); } TEST_F(CredentialKeyletImpl, InvalidAccount) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); auto const credTypeStr = std::string{"test"}; auto const credType = Slice{credTypeStr.data(), credTypeStr.size()}; - auto result = host().credentialKeylet(AccountID{}, owner.id(), credType); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError( + makeHost()->credentialKeylet(AccountID{}, owner.id(), credType), + HostFunctionError::InvalidAccount); - result = host().credentialKeylet(owner.id(), AccountID{}, credType); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError( + makeHost()->credentialKeylet(owner.id(), AccountID{}, credType), + HostFunctionError::InvalidAccount); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/CurrentLedgerObjArrayLen.cpp b/src/tests/libxrpl/tx/wasm/host_functions/CurrentLedgerObjArrayLen.cpp new file mode 100644 index 0000000000..c66473782e --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/CurrentLedgerObjArrayLen.cpp @@ -0,0 +1,56 @@ +#include +#include +#include + +#include +#include +#include + +#include + +namespace xrpl::test { + +struct CurrentLedgerObjArrayLenImpl : WasmImplTest +{ + using WasmImplTest::makeHost; + + WasmHost + makeHost(Account const& acct) + { + makeSignerList(acct, 2, {{Account{"alice"}, 1}, {Account{"becky"}, 1}}); + auto assembler = bareTx(); + return makeHost(keylet::signerList(acct.id()), assembler.type, std::move(assembler.build)); + } +}; + +TEST_F(CurrentLedgerObjArrayLenImpl, SignerEntriesLength) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectValue(h->getCurrentLedgerObjArrayLen(sfSignerEntries), 2); +} + +TEST_F(CurrentLedgerObjArrayLenImpl, NonArrayFieldNoArray) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError(h->getCurrentLedgerObjArrayLen(sfAccount), HostFunctionError::NoArray); +} + +TEST_F(CurrentLedgerObjArrayLenImpl, MissingArrayFieldNotFound) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError(h->getCurrentLedgerObjArrayLen(sfMemos), HostFunctionError::FieldNotFound); +} + +TEST_F(CurrentLedgerObjArrayLenImpl, MissingCurrentObjectNotFound) +{ + auto const owner = fund("owner"); + auto assembler = bareTx(); + auto h = makeHost(keylet::signerList(owner.id()), assembler.type, std::move(assembler.build)); + expectError( + h->getCurrentLedgerObjArrayLen(sfSignerEntries), HostFunctionError::LedgerObjNotFound); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/CurrentLedgerObjField.cpp b/src/tests/libxrpl/tx/wasm/host_functions/CurrentLedgerObjField.cpp index feef6c6dcb..7c85864926 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/CurrentLedgerObjField.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/CurrentLedgerObjField.cpp @@ -12,9 +12,6 @@ #include #include -#include -#include - namespace xrpl::test { struct CurrentLedgerObjFieldImpl : WasmImplTest @@ -49,11 +46,7 @@ TEST_F(CurrentLedgerObjFieldImpl, ReadsfAccount) auto const escrow = makeEscrow(owner, Account{"dest"}); ASSERT_NE(ledger.getOpenLedger().read(escrow), nullptr) << "escrow object should exist"; - auto const account = host(escrow).getCurrentLedgerObjField(sfAccount); - - ASSERT_TRUE(account.has_value()); - auto const ownerBytes = Bytes{std::begin(owner.id()), std::end(owner.id())}; - EXPECT_EQ(*account, ownerBytes); + expectValue(makeHost(escrow)->getCurrentLedgerObjField(sfAccount), toBytes(owner.id())); } TEST_F(CurrentLedgerObjFieldImpl, ReadsfAccountDummyEscrow) @@ -63,10 +56,9 @@ TEST_F(CurrentLedgerObjFieldImpl, ReadsfAccountDummyEscrow) auto const ownerSeq = ledger.getAccountRoot(owner.id()).getSequence(); auto const escrow = keylet::escrow(owner.id(), SeqProxy::rawSequence(ownerSeq)); - auto const account = host(escrow).getCurrentLedgerObjField(sfAccount); - - ASSERT_TRUE(!account.has_value()); - ASSERT_TRUE(account.error() == HostFunctionError::LedgerObjNotFound); + expectError( + makeHost(escrow)->getCurrentLedgerObjField(sfAccount), + HostFunctionError::LedgerObjNotFound); } TEST_F(CurrentLedgerObjFieldImpl, ReadAmount) @@ -75,9 +67,7 @@ TEST_F(CurrentLedgerObjFieldImpl, ReadAmount) auto const escrow = makeEscrow(owner, Account{"dest"}); ASSERT_NE(ledger.getOpenLedger().read(escrow), nullptr) << "escrow object should exist"; - auto const amount = host(escrow).getCurrentLedgerObjField(sfAmount); - ASSERT_TRUE(amount.has_value()); - EXPECT_EQ(*amount, toBytes(XRP(100))); + expectValue(makeHost(escrow)->getCurrentLedgerObjField(sfAmount), toBytes(XRP(100))); } TEST_F(CurrentLedgerObjFieldImpl, ReadPreviousTxnID) @@ -87,10 +77,8 @@ TEST_F(CurrentLedgerObjFieldImpl, ReadPreviousTxnID) auto const escrow = makeEscrow(owner, Account{"dest"}, &transactionId); ASSERT_NE(ledger.getOpenLedger().read(escrow), nullptr) << "escrow object should exist"; - auto const previousTxnId = host(escrow).getCurrentLedgerObjField(sfPreviousTxnID); - - ASSERT_TRUE(previousTxnId.has_value()); - EXPECT_EQ(*previousTxnId, toBytes(transactionId)); + expectValue( + makeHost(escrow)->getCurrentLedgerObjField(sfPreviousTxnID), toBytes(transactionId)); } TEST_F(CurrentLedgerObjFieldImpl, ReadOwner) @@ -99,10 +87,8 @@ TEST_F(CurrentLedgerObjFieldImpl, ReadOwner) auto const escrow = makeEscrow(owner, Account{"dest"}); ASSERT_NE(ledger.getOpenLedger().read(escrow), nullptr) << "escrow object should exist"; - auto const ownerField = host(escrow).getCurrentLedgerObjField(sfOwner); - - ASSERT_TRUE(!ownerField.has_value()); - ASSERT_TRUE(ownerField.error() == HostFunctionError::FieldNotFound); + expectError( + makeHost(escrow)->getCurrentLedgerObjField(sfOwner), HostFunctionError::FieldNotFound); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/CurrentLedgerObjNestedArrayLen.cpp b/src/tests/libxrpl/tx/wasm/host_functions/CurrentLedgerObjNestedArrayLen.cpp new file mode 100644 index 0000000000..7a3403e87c --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/CurrentLedgerObjNestedArrayLen.cpp @@ -0,0 +1,61 @@ +#include +#include +#include + +#include +#include +#include + +#include + +namespace xrpl::test { + +struct CurrentLedgerObjNestedArrayLenImpl : WasmImplTest +{ + using WasmImplTest::makeHost; + + WasmHost + makeHost(Account const& acct) + { + makeSignerList(acct, 2, {{Account{"alice"}, 1}, {Account{"becky"}, 1}}); + auto assembler = bareTx(); + return makeHost(keylet::signerList(acct.id()), assembler.type, std::move(assembler.build)); + } +}; + +TEST_F(CurrentLedgerObjNestedArrayLenImpl, SignerEntriesLength) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectValue(h->getCurrentLedgerObjNestedArrayLen(FieldLocator{{sfSignerEntries.getCode()}}), 2); +} + +TEST_F(CurrentLedgerObjNestedArrayLenImpl, NonArrayFieldNoArray) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError( + h->getCurrentLedgerObjNestedArrayLen(FieldLocator{{sfSignerQuorum.getCode()}}), + HostFunctionError::NoArray); +} + +TEST_F(CurrentLedgerObjNestedArrayLenImpl, MissingFieldNotFound) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError( + h->getCurrentLedgerObjNestedArrayLen(FieldLocator{{sfSigners.getCode()}}), + HostFunctionError::FieldNotFound); +} + +TEST_F(CurrentLedgerObjNestedArrayLenImpl, MissingCurrentObjectNotFound) +{ + auto const owner = fund("owner"); + auto assembler = bareTx(); + auto h = makeHost(keylet::signerList(owner.id()), assembler.type, std::move(assembler.build)); + expectError( + h->getCurrentLedgerObjNestedArrayLen(FieldLocator{{sfSignerEntries.getCode()}}), + HostFunctionError::LedgerObjNotFound); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/CurrentLedgerObjNestedField.cpp b/src/tests/libxrpl/tx/wasm/host_functions/CurrentLedgerObjNestedField.cpp new file mode 100644 index 0000000000..69e6c2df0b --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/CurrentLedgerObjNestedField.cpp @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace xrpl::test { + +struct CurrentLedgerObjNestedFieldImpl : WasmImplTest +{ + using WasmImplTest::makeHost; + + WasmHost + makeHost(Account const& acct) + { + makeSignerList(acct, 2, {{Account{"alice"}, 1}, {Account{"becky"}, 1}}); + auto assembler = bareTx(); + return makeHost(keylet::signerList(acct.id()), assembler.type, std::move(assembler.build)); + } +}; + +TEST_F(CurrentLedgerObjNestedFieldImpl, MatchesNestedSignerQuorum) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectValue( + h->getCurrentLedgerObjNestedField(FieldLocator{{sfSignerQuorum.getCode()}}), + toBytes(static_cast(2))); +} + +TEST_F(CurrentLedgerObjNestedFieldImpl, MatchesNestedSignerWeight) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectValue( + h->getCurrentLedgerObjNestedField( + FieldLocator{{sfSignerEntries.getCode(), 0, sfSignerWeight.getCode()}}), + toBytes(static_cast(1))); +} + +TEST_F(CurrentLedgerObjNestedFieldImpl, MatchesNestedSignerAccount) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + + auto const sle = ledger.getOpenLedger().read(keylet::signerList(owner.id())); + ASSERT_NE(sle, nullptr); + auto const& entry0 = sle->getFieldArray(sfSignerEntries)[0]; + + expectValue( + h->getCurrentLedgerObjNestedField( + FieldLocator{{sfSignerEntries.getCode(), 0, sfAccount.getCode()}}), + toBytes(entry0.getAccountID(sfAccount))); +} + +TEST_F(CurrentLedgerObjNestedFieldImpl, MissingFieldNotFound) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError( + h->getCurrentLedgerObjNestedField( + FieldLocator{{sfSigners.getCode(), 0, sfAccount.getCode()}}), + HostFunctionError::FieldNotFound); +} + +TEST_F(CurrentLedgerObjNestedFieldImpl, IndexOutOfBounds) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + auto const err = HostFunctionError::IndexOutOfBounds; + + expectError( + h->getCurrentLedgerObjNestedField( + FieldLocator{{sfSignerEntries.getCode(), 2, sfAccount.getCode()}}), + err); + expectError( + h->getCurrentLedgerObjNestedField( + FieldLocator{{sfSignerEntries.getCode(), -1, sfAccount.getCode()}}), + err); +} + +TEST_F(CurrentLedgerObjNestedFieldImpl, UnknownFieldCodeInvalidField) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + auto const err = HostFunctionError::InvalidField; + + expectError(h->getCurrentLedgerObjNestedField(FieldLocator{{fieldCode(20000, 20000)}}), err); + expectError( + h->getCurrentLedgerObjNestedField( + FieldLocator{{sfSignerEntries.getCode(), 0, fieldCode(20000, 20000)}}), + err); +} + +TEST_F(CurrentLedgerObjNestedFieldImpl, NestIntoNonContainerMalformed) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError( + h->getCurrentLedgerObjNestedField( + FieldLocator{{sfSignerQuorum.getCode(), 0, sfAccount.getCode()}}), + HostFunctionError::LocatorMalformed); +} + +TEST_F(CurrentLedgerObjNestedFieldImpl, MissingCurrentObjectNotFound) +{ + auto const owner = fund("owner"); + auto assembler = bareTx(); + auto h = makeHost(keylet::signerList(owner.id()), assembler.type, std::move(assembler.build)); + expectError( + h->getCurrentLedgerObjNestedField(FieldLocator{{sfSignerQuorum.getCode()}}), + HostFunctionError::LedgerObjNotFound); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/DelegateKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/DelegateKeylet.cpp index 3db51f1a67..a504c2996c 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/DelegateKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/DelegateKeylet.cpp @@ -4,12 +4,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct DelegateKeyletImpl : WasmImplTest @@ -18,40 +14,31 @@ struct DelegateKeyletImpl : WasmImplTest TEST_F(DelegateKeyletImpl, MatchesDelegateKeyletFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); - auto const delegate = Account{"delegate"}; - ledger.createAccount(delegate, XRP(1000)); + auto const owner = fund("owner"); + auto const delegate = fund("delegate"); - auto const expected = keylet::delegate(owner.id(), delegate.id()); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().delegateKeylet(owner.id(), delegate.id()); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches( + makeHost()->delegateKeylet(owner.id(), delegate.id()), + keylet::delegate(owner.id(), delegate.id())); } TEST_F(DelegateKeyletImpl, CantDelegateToSelf) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); - auto const result = host().delegateKeylet(owner.id(), owner.id()); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidParams); + expectError( + makeHost()->delegateKeylet(owner.id(), owner.id()), HostFunctionError::InvalidParams); } TEST_F(DelegateKeyletImpl, InvalidAccount) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); - auto result = host().delegateKeylet(AccountID{}, owner.id()); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError( + makeHost()->delegateKeylet(AccountID{}, owner.id()), HostFunctionError::InvalidAccount); - result = host().delegateKeylet(owner.id(), AccountID{}); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError( + makeHost()->delegateKeylet(owner.id(), AccountID{}), HostFunctionError::InvalidAccount); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/DepositPreauthKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/DepositPreauthKeylet.cpp index f59ff2f873..6314c78c86 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/DepositPreauthKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/DepositPreauthKeylet.cpp @@ -4,12 +4,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct DepositPreauthKeyletImpl : WasmImplTest @@ -18,40 +14,33 @@ struct DepositPreauthKeyletImpl : WasmImplTest TEST_F(DepositPreauthKeyletImpl, MatchesDepositPreauthKeyletFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); - auto const destination = Account{"destination"}; - ledger.createAccount(destination, XRP(1000)); + auto const owner = fund("owner"); + auto const destination = fund("destination"); - auto const expected = keylet::depositPreauth(owner.id(), destination.id()); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().depositPreauthKeylet(owner.id(), destination.id()); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches( + makeHost()->depositPreauthKeylet(owner.id(), destination.id()), + keylet::depositPreauth(owner.id(), destination.id())); } TEST_F(DepositPreauthKeyletImpl, CantPreauthToSelf) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); - auto const result = host().depositPreauthKeylet(owner.id(), owner.id()); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidParams); + expectError( + makeHost()->depositPreauthKeylet(owner.id(), owner.id()), HostFunctionError::InvalidParams); } TEST_F(DepositPreauthKeyletImpl, InvalidAccount) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); - auto result = host().depositPreauthKeylet(AccountID{}, owner.id()); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError( + makeHost()->depositPreauthKeylet(AccountID{}, owner.id()), + HostFunctionError::InvalidAccount); - result = host().depositPreauthKeylet(owner.id(), AccountID{}); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError( + makeHost()->depositPreauthKeylet(owner.id(), AccountID{}), + HostFunctionError::InvalidAccount); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/DidKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/DidKeylet.cpp index fd7dec9a37..dfe110cdd8 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/DidKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/DidKeylet.cpp @@ -4,12 +4,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct DidKeyletImpl : WasmImplTest @@ -18,21 +14,14 @@ struct DidKeyletImpl : WasmImplTest TEST_F(DidKeyletImpl, MatchesDidKeyletFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); - auto const expected = keylet::did(owner.id()); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().didKeylet(owner.id()); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches(makeHost()->didKeylet(owner.id()), keylet::did(owner.id())); } TEST_F(DidKeyletImpl, InvalidAccount) { - auto result = host().didKeylet(AccountID{}); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError(makeHost()->didKeylet(AccountID{}), HostFunctionError::InvalidAccount); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/EscrowKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/EscrowKeylet.cpp index 82a3ebc503..de0162dc7c 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/EscrowKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/EscrowKeylet.cpp @@ -8,8 +8,6 @@ #include #include -#include -#include namespace xrpl::test { @@ -22,18 +20,15 @@ TEST_F(EscrowKeyletImpl, MatchesLedgerKeyletFunction) auto const owner = Account{"owner"}; auto const seq = std::uint32_t{42}; - auto const result = host().escrowKeylet(owner.id(), seq); - - ASSERT_TRUE(result.has_value()); - auto const expected = keylet::escrow(owner.id(), SeqProxy::rawSequence(seq)).key; - auto const expectedBytes = Bytes{std::begin(expected), std::end(expected)}; - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches( + makeHost()->escrowKeylet(owner.id(), seq), + keylet::escrow(owner.id(), SeqProxy::rawSequence(seq))); } TEST_F(EscrowKeyletImpl, DifferentAccountsGiveDifferentKeylets) { - auto const a = host().escrowKeylet(Account{"alice"}.id(), 7); - auto const b = host().escrowKeylet(Account{"becky"}.id(), 7); + auto const a = makeHost()->escrowKeylet(Account{"alice"}.id(), 7); + auto const b = makeHost()->escrowKeylet(Account{"becky"}.id(), 7); ASSERT_TRUE(a.has_value() && b.has_value()); EXPECT_NE(*a, *b); @@ -41,10 +36,7 @@ TEST_F(EscrowKeyletImpl, DifferentAccountsGiveDifferentKeylets) TEST_F(EscrowKeyletImpl, UnsetAccountIsInvalidAccount) { - auto const result = host().escrowKeylet(AccountID{}, 1); - - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError(makeHost()->escrowKeylet(AccountID{}, 1), HostFunctionError::InvalidAccount); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/FloatAdd.cpp b/src/tests/libxrpl/tx/wasm/host_functions/FloatAdd.cpp new file mode 100644 index 0000000000..31daa29eda --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/FloatAdd.cpp @@ -0,0 +1,46 @@ +#include +#include + +#include +#include +#include + +namespace xrpl::test { + +struct FloatAddImpl : FloatTest +{ +}; + +TEST_F(FloatAddImpl, BadModeIsMalformed) +{ + expectError( + makeHost()->floatAdd(slice(floats::kOne), slice(floats::kOne), -1), + HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatAddImpl, MalformedInput) +{ + expectError( + makeHost()->floatAdd(slice(floats::kOne), Slice{}, 0), + HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatAddImpl, MaxIouPlusMaxExpIsMaxExp) +{ + expectValue( + makeHost()->floatAdd(slice(floats::kMaxIOU), slice(floats::kMaxExp), 0), floats::kMaxExp); +} + +TEST_F(FloatAddImpl, MinPlusZeroIsMin) +{ + expectValue( + makeHost()->floatAdd(slice(floats::kIntMin), slice(floats::kIntZero), 0), floats::kIntMin); +} + +TEST_F(FloatAddImpl, MaxPlusMinIsZero) +{ + expectValue( + makeHost()->floatAdd(slice(floats::kIntMax), slice(floats::kIntMin), 0), floats::kIntZero); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/FloatCompare.cpp b/src/tests/libxrpl/tx/wasm/host_functions/FloatCompare.cpp new file mode 100644 index 0000000000..b90b4c5b07 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/FloatCompare.cpp @@ -0,0 +1,48 @@ +#include +#include + +#include +#include +#include + +namespace xrpl::test { + +struct FloatCompareImpl : FloatTest +{ +}; + +TEST_F(FloatCompareImpl, MalformedInputs) +{ + // A wrong-size (here empty) buffer is malformed; the impl normalizes any well-formed + // 12-byte buffer, so size is the only rejection. + expectError(makeHost()->floatCompare(Slice{}, Slice{}), HostFunctionError::FloatInputMalformed); + expectError( + makeHost()->floatCompare(slice(floats::kOne), Slice{}), + HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatCompareImpl, Less) +{ + expectValue(makeHost()->floatCompare(slice(floats::kIntMin), slice(floats::kIntZero)), 2); +} + +TEST_F(FloatCompareImpl, Greater) +{ + expectValue(makeHost()->floatCompare(slice(floats::kIntMax), slice(floats::kIntZero)), 1); +} + +TEST_F(FloatCompareImpl, Equal) +{ + expectValue(makeHost()->floatCompare(slice(floats::kOne), slice(floats::kOne)), 0); +} + +// A non-canonical encoding of 10 (mantissa 100000, exponent -4) is normalized on decode, so +// it compares equal to the canonical 10 — the impl accepts any well-formed 12-byte buffer. +TEST_F(FloatCompareImpl, NonCanonicalNormalizes) +{ + Bytes const nonCanonicalTen{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x86, 0xA0, 0xFF, 0xFF, 0xFF, 0xFC}; + expectValue(makeHost()->floatCompare(slice(nonCanonicalTen), slice(floats::kTen)), 0); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/FloatDivide.cpp b/src/tests/libxrpl/tx/wasm/host_functions/FloatDivide.cpp new file mode 100644 index 0000000000..ae3c81f8bb --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/FloatDivide.cpp @@ -0,0 +1,70 @@ +#include +#include +#include + +#include +#include +#include + +namespace xrpl::test { + +struct FloatDivideImpl : FloatTest +{ +}; + +TEST_F(FloatDivideImpl, BadModeIsMalformed) +{ + expectError( + makeHost()->floatDivide(slice(floats::kOne), slice(floats::kOne), -1), + HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatDivideImpl, MalformedInput) +{ + expectError( + makeHost()->floatDivide(slice(floats::kOne), Slice{}, 0), + HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatDivideImpl, DivideByZeroIsComputationError) +{ + expectError( + makeHost()->floatDivide(slice(floats::kOne), slice(floats::kIntZero), 0), + HostFunctionError::FloatComputationError); +} + +TEST_F(FloatDivideImpl, OverflowIsComputationError) +{ + // A divisor just below 1, so max / it overflows. + auto const y = makeHost()->floatFromMantExp(STAmount::kMaxValue, -floats::kNormalExp - 1, 0); + ASSERT_TRUE(y.has_value()); + expectError( + makeHost()->floatDivide(slice(floats::kMax), slice(*y), 0), + HostFunctionError::FloatComputationError); +} + +TEST_F(FloatDivideImpl, ZeroDividedByOneIsZero) +{ + expectValue( + makeHost()->floatDivide(slice(floats::kIntZero), slice(floats::kOne), 0), floats::kIntZero); +} + +TEST_F(FloatDivideImpl, MaxExpDividedByTenIsPreMaxExp) +{ + expectValue( + makeHost()->floatDivide(slice(floats::kMaxExp), slice(floats::kTen), 0), + floats::kPreMaxExp); +} + +// The rounding mode changes an inexact result: 1/3 rounded Downward differs from Upward. +TEST_F(FloatDivideImpl, RoundingModeAffectsInexactResult) +{ + auto const three = makeHost()->floatFromInt(3, 0); + ASSERT_TRUE(three.has_value()); + auto const down = makeHost()->floatDivide(slice(floats::kOne), slice(*three), 2); + auto const up = makeHost()->floatDivide(slice(floats::kOne), slice(*three), 3); + ASSERT_TRUE(down.has_value() && up.has_value()); + EXPECT_NE(*down, *up); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/FloatFromInt.cpp b/src/tests/libxrpl/tx/wasm/host_functions/FloatFromInt.cpp new file mode 100644 index 0000000000..febe731451 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/FloatFromInt.cpp @@ -0,0 +1,34 @@ +#include + +#include +#include +#include + +namespace xrpl::test { + +struct FloatFromIntImpl : FloatTest +{ +}; + +TEST_F(FloatFromIntImpl, BadModeIsMalformed) +{ + expectError(makeHost()->floatFromInt(kMin64, -1), HostFunctionError::FloatInputMalformed); + expectError(makeHost()->floatFromInt(kMin64, 4), HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatFromIntImpl, MinInt) +{ + expectValue(makeHost()->floatFromInt(kMin64, 0), floats::kIntMin); +} + +TEST_F(FloatFromIntImpl, Zero) +{ + expectValue(makeHost()->floatFromInt(0, 0), floats::kIntZero); +} + +TEST_F(FloatFromIntImpl, MaxInt) +{ + expectValue(makeHost()->floatFromInt(kMax64, 0), floats::kIntMax); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/FloatFromMantExp.cpp b/src/tests/libxrpl/tx/wasm/host_functions/FloatFromMantExp.cpp new file mode 100644 index 0000000000..5a5a424728 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/FloatFromMantExp.cpp @@ -0,0 +1,68 @@ +#include +#include +#include + +#include +#include +#include + +namespace xrpl::test { + +struct FloatFromMantExpImpl : FloatTest +{ + static constexpr int kMaxRawExp = Number::kMaxExponent + floats::kNormalExp; + static constexpr int kMinRawExp = Number::kMinExponent + floats::kNormalExp; +}; + +TEST_F(FloatFromMantExpImpl, BadModeIsMalformed) +{ + expectError(makeHost()->floatFromMantExp(1, 0, -1), HostFunctionError::FloatInputMalformed); + expectError(makeHost()->floatFromMantExp(1, 0, 4), HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatFromMantExpImpl, ExponentTooHighIsMalformed) +{ + expectError( + makeHost()->floatFromMantExp(1, kMaxRawExp + 1, 0), HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatFromMantExpImpl, UnderflowIsZero) +{ + expectValue(makeHost()->floatFromMantExp(1, kMinRawExp - 1, 0), floats::kIntZero); +} + +TEST_F(FloatFromMantExpImpl, MaxExponent) +{ + expectValue(makeHost()->floatFromMantExp(1, kMaxRawExp, 0), floats::kMaxExp); +} + +TEST_F(FloatFromMantExpImpl, MinusMaxExponent) +{ + expectValue(makeHost()->floatFromMantExp(-1, kMaxRawExp, 0), floats::kMinusMaxExp); +} + +TEST_F(FloatFromMantExpImpl, PreMaxExponent) +{ + expectValue(makeHost()->floatFromMantExp(1, kMaxRawExp - 1, 0), floats::kPreMaxExp); +} + +TEST_F(FloatFromMantExpImpl, MaxIou) +{ + expectValue( + makeHost()->floatFromMantExp(STAmount::kMaxValue, STAmount::kMaxOffset, 0), + floats::kMaxIOU); +} + +TEST_F(FloatFromMantExpImpl, MinExponent) +{ + expectValue( + makeHost()->floatFromMantExp(1, Number::kMinExponent - floats::kNormalExp, 0), + floats::kMinExp); +} + +TEST_F(FloatFromMantExpImpl, TenTimesTenthIsOne) +{ + expectValue(makeHost()->floatFromMantExp(10, -1, 0), floats::kOne); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/FloatFromStAmount.cpp b/src/tests/libxrpl/tx/wasm/host_functions/FloatFromStAmount.cpp new file mode 100644 index 0000000000..479bda146d --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/FloatFromStAmount.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace xrpl::test { + +struct FloatFromStAmountImpl : FloatTest +{ + static Issue + usd() + { + return Issue{toCurrency("USD"), Account{"gw"}.id()}; + } +}; + +TEST_F(FloatFromStAmountImpl, BadModeIsMalformed) +{ + auto const amount = STAmount{XRP(100)}; + expectError(makeHost()->floatFromSTAmount(amount, -1), HostFunctionError::FloatInputMalformed); + expectError(makeHost()->floatFromSTAmount(amount, 4), HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatFromStAmountImpl, ZeroXrp) +{ + expectValue(makeHost()->floatFromSTAmount(STAmount{XRP(0)}, 0), floats::kIntZero); +} + +TEST_F(FloatFromStAmountImpl, MinusOneXrp) +{ + // -1 XRP == -1'000'000 drops. + auto const expected = makeHost()->floatFromMantExp(-1'000'000, 0, 0); + ASSERT_TRUE(expected.has_value()); + expectValue(makeHost()->floatFromSTAmount(STAmount{XRP(-1)}, 0), *expected); +} + +TEST_F(FloatFromStAmountImpl, MaxDrops) +{ + auto const expected = makeHost()->floatFromMantExp(9'223'372'036'854'776, 3, 0); + ASSERT_TRUE(expected.has_value()); + expectValue(makeHost()->floatFromSTAmount(STAmount{noIssue(), kMax64}, 0), *expected); +} + +TEST_F(FloatFromStAmountImpl, MinIou) +{ + auto const amount = STAmount{ + IOUAmount{static_cast(STAmount::kMinValue), STAmount::kMinOffset}, usd()}; + expectValue(makeHost()->floatFromSTAmount(amount, 0), floats::kMinIOU); +} + +TEST_F(FloatFromStAmountImpl, MaxIou) +{ + auto const amount = STAmount{ + IOUAmount{static_cast(STAmount::kMaxValue), STAmount::kMaxOffset}, usd()}; + expectValue(makeHost()->floatFromSTAmount(amount, 0), floats::kMaxIOU); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/FloatFromStNumber.cpp b/src/tests/libxrpl/tx/wasm/host_functions/FloatFromStNumber.cpp new file mode 100644 index 0000000000..4405062cf9 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/FloatFromStNumber.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace xrpl::test { + +struct FloatFromStNumberImpl : FloatTest +{ +}; + +TEST_F(FloatFromStNumberImpl, BadModeIsMalformed) +{ + auto const n = STNumber{sfNumber, Number(123, 0)}; + expectError(makeHost()->floatFromSTNumber(n, -1), HostFunctionError::FloatInputMalformed); + expectError(makeHost()->floatFromSTNumber(n, 4), HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatFromStNumberImpl, MaxUint) +{ + auto const n = STNumber{ + sfNumber, Number(std::numeric_limits::max(), 0, Number::Normalized{})}; + expectValue(makeHost()->floatFromSTNumber(n, 0), floats::kUintMax); +} + +TEST_F(FloatFromStNumberImpl, MinusMaxExponent) +{ + auto const n = STNumber{sfNumber, Number(-1, Number::kMaxExponent + floats::kNormalExp)}; + expectValue(makeHost()->floatFromSTNumber(n, 0), floats::kMinusMaxExp); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/FloatFromUint.cpp b/src/tests/libxrpl/tx/wasm/host_functions/FloatFromUint.cpp new file mode 100644 index 0000000000..a51217d96f --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/FloatFromUint.cpp @@ -0,0 +1,33 @@ +#include + +#include +#include +#include + +#include +#include + +namespace xrpl::test { + +struct FloatFromUintImpl : FloatTest +{ + static constexpr std::uint64_t kMaxU64 = std::numeric_limits::max(); +}; + +TEST_F(FloatFromUintImpl, BadModeIsMalformed) +{ + expectError(makeHost()->floatFromUint(0, -1), HostFunctionError::FloatInputMalformed); + expectError(makeHost()->floatFromUint(0, 4), HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatFromUintImpl, Zero) +{ + expectValue(makeHost()->floatFromUint(0, 0), floats::kIntZero); +} + +TEST_F(FloatFromUintImpl, MaxUint) +{ + expectValue(makeHost()->floatFromUint(kMaxU64, 0), floats::kUintMax); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/FloatMultiply.cpp b/src/tests/libxrpl/tx/wasm/host_functions/FloatMultiply.cpp new file mode 100644 index 0000000000..fb63cca58d --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/FloatMultiply.cpp @@ -0,0 +1,55 @@ +#include +#include + +#include +#include +#include + +namespace xrpl::test { + +struct FloatMultiplyImpl : FloatTest +{ +}; + +TEST_F(FloatMultiplyImpl, BadModeIsMalformed) +{ + expectError( + makeHost()->floatMultiply(slice(floats::kOne), slice(floats::kOne), -1), + HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatMultiplyImpl, MalformedInput) +{ + expectError( + makeHost()->floatMultiply(slice(floats::kOne), Slice{}, 0), + HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatMultiplyImpl, OverflowIsComputationError) +{ + expectError( + makeHost()->floatMultiply(slice(floats::kMax), slice(floats::kOneMore), 0), + HostFunctionError::FloatComputationError); +} + +TEST_F(FloatMultiplyImpl, OneTimesOneIsOne) +{ + expectValue( + makeHost()->floatMultiply(slice(floats::kOne), slice(floats::kOne), 0), floats::kOne); +} + +TEST_F(FloatMultiplyImpl, ZeroTimesMaxIouIsZero) +{ + expectValue( + makeHost()->floatMultiply(slice(floats::kIntZero), slice(floats::kMaxIOU), 0), + floats::kIntZero); +} + +TEST_F(FloatMultiplyImpl, TenTimesPreMaxExpIsMaxExp) +{ + expectValue( + makeHost()->floatMultiply(slice(floats::kTen), slice(floats::kPreMaxExp), 0), + floats::kMaxExp); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/FloatPower.cpp b/src/tests/libxrpl/tx/wasm/host_functions/FloatPower.cpp new file mode 100644 index 0000000000..6dcc84465c --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/FloatPower.cpp @@ -0,0 +1,71 @@ +#include +#include +#include + +#include +#include +#include + +namespace xrpl::test { + +struct FloatPowerImpl : FloatTest +{ +}; + +TEST_F(FloatPowerImpl, BadModeIsMalformed) +{ + expectError( + makeHost()->floatPower(slice(floats::kOne), 2, -1), HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatPowerImpl, MalformedInput) +{ + expectError(makeHost()->floatPower(Slice{}, 3, 0), HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatPowerImpl, NegativeDegreeIsMalformed) +{ + expectError( + makeHost()->floatPower(slice(floats::kOne), -2, 0), HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatPowerImpl, OverflowIsComputationError) +{ + expectError( + makeHost()->floatPower(slice(floats::kMax), 2, 0), + HostFunctionError::FloatComputationError); +} + +TEST_F(FloatPowerImpl, DegreeTooLargeIsMalformed) +{ + expectError( + makeHost()->floatPower(slice(floats::kMax), Number::kMaxExponent + 1, 0), + HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatPowerImpl, DegreeZeroIsOne) +{ + expectValue(makeHost()->floatPower(slice(floats::kMaxIOU), 0, 0), floats::kOne); +} + +TEST_F(FloatPowerImpl, DegreeOneIsIdentity) +{ + expectValue(makeHost()->floatPower(slice(floats::kMaxIOU), 1, 0), floats::kMaxIOU); +} + +TEST_F(FloatPowerImpl, TenSquaredIsHundred) +{ + auto const hundred = makeHost()->floatFromMantExp(100, 0, 0); + ASSERT_TRUE(hundred.has_value()); + expectValue(makeHost()->floatPower(slice(floats::kTen), 2, 0), *hundred); +} + +TEST_F(FloatPowerImpl, TenthSquaredIsHundredth) +{ + auto const tenth = makeHost()->floatFromMantExp(1, -1, 0); + auto const hundredth = makeHost()->floatFromMantExp(1, -2, 0); + ASSERT_TRUE(tenth.has_value() && hundredth.has_value()); + expectValue(makeHost()->floatPower(slice(*tenth), 2, 0), *hundredth); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/FloatRoot.cpp b/src/tests/libxrpl/tx/wasm/host_functions/FloatRoot.cpp new file mode 100644 index 0000000000..9d1cca1a18 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/FloatRoot.cpp @@ -0,0 +1,63 @@ +#include +#include + +#include +#include +#include + +namespace xrpl::test { + +struct FloatRootImpl : FloatTest +{ +}; + +TEST_F(FloatRootImpl, BadModeIsMalformed) +{ + expectError( + makeHost()->floatRoot(slice(floats::kOne), 2, -1), HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatRootImpl, MalformedInput) +{ + expectError(makeHost()->floatRoot(Slice{}, 3, 0), HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatRootImpl, NegativeDegreeIsMalformed) +{ + expectError( + makeHost()->floatRoot(slice(floats::kOne), -2, 0), HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatRootImpl, RootOfZeroIsZero) +{ + expectValue(makeHost()->floatRoot(slice(floats::kIntZero), 2, 0), floats::kIntZero); +} + +TEST_F(FloatRootImpl, FirstRootIsIdentity) +{ + expectValue(makeHost()->floatRoot(slice(floats::kMaxIOU), 1, 0), floats::kMaxIOU); +} + +TEST_F(FloatRootImpl, SquareRootOfHundredIsTen) +{ + auto const hundred = makeHost()->floatFromMantExp(100, 0, 0); + ASSERT_TRUE(hundred.has_value()); + expectValue(makeHost()->floatRoot(slice(*hundred), 2, 0), floats::kTen); +} + +TEST_F(FloatRootImpl, CubeRootOfThousandIsTen) +{ + auto const thousand = makeHost()->floatFromMantExp(1000, 0, 0); + ASSERT_TRUE(thousand.has_value()); + expectValue(makeHost()->floatRoot(slice(*thousand), 3, 0), floats::kTen); +} + +TEST_F(FloatRootImpl, SquareRootOfHundredthIsTenth) +{ + auto const hundredth = makeHost()->floatFromMantExp(1, -2, 0); + auto const tenth = makeHost()->floatFromMantExp(1, -1, 0); + ASSERT_TRUE(hundredth.has_value() && tenth.has_value()); + expectValue(makeHost()->floatRoot(slice(*hundredth), 2, 0), *tenth); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/FloatSubtract.cpp b/src/tests/libxrpl/tx/wasm/host_functions/FloatSubtract.cpp new file mode 100644 index 0000000000..b60cb24b34 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/FloatSubtract.cpp @@ -0,0 +1,49 @@ +#include +#include + +#include +#include +#include + +namespace xrpl::test { + +struct FloatSubtractImpl : FloatTest +{ +}; + +TEST_F(FloatSubtractImpl, BadModeIsMalformed) +{ + expectError( + makeHost()->floatSubtract(slice(floats::kOne), slice(floats::kOne), -1), + HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatSubtractImpl, MalformedInput) +{ + expectError( + makeHost()->floatSubtract(slice(floats::kOne), Slice{}, 0), + HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatSubtractImpl, MinusMaxExpMinusMaxIouIsMinusMaxExp) +{ + expectValue( + makeHost()->floatSubtract(slice(floats::kMinusMaxExp), slice(floats::kMaxIOU), 0), + floats::kMinusMaxExp); +} + +TEST_F(FloatSubtractImpl, MinMinusZeroIsMin) +{ + expectValue( + makeHost()->floatSubtract(slice(floats::kIntMin), slice(floats::kIntZero), 0), + floats::kIntMin); +} + +TEST_F(FloatSubtractImpl, ZeroMinusOneIsMinusOne) +{ + expectValue( + makeHost()->floatSubtract(slice(floats::kIntZero), slice(floats::kOne), 0), + floats::kMinusOne); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/FloatToInt.cpp b/src/tests/libxrpl/tx/wasm/host_functions/FloatToInt.cpp new file mode 100644 index 0000000000..a6a1ef31a8 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/FloatToInt.cpp @@ -0,0 +1,70 @@ +#include +#include + +#include +#include +#include + +#include + +namespace xrpl::test { + +struct FloatToIntImpl : FloatTest +{ +}; + +TEST_F(FloatToIntImpl, BadModeIsMalformed) +{ + expectError( + makeHost()->floatToInt(slice(floats::kOne), -1), HostFunctionError::FloatInputMalformed); + expectError( + makeHost()->floatToInt(slice(floats::kOne), 4), HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatToIntImpl, MalformedInputs) +{ + expectError(makeHost()->floatToInt(Slice{}, 0), HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatToIntImpl, Zero) +{ + expectValue(makeHost()->floatToInt(slice(floats::kIntZero), 0), std::int64_t{0}); +} + +TEST_F(FloatToIntImpl, One) +{ + expectValue(makeHost()->floatToInt(slice(floats::kOne), 0), std::int64_t{1}); +} + +TEST_F(FloatToIntImpl, MinusOne) +{ + expectValue(makeHost()->floatToInt(slice(floats::kMinusOne), 0), std::int64_t{-1}); +} + +TEST_F(FloatToIntImpl, Max) +{ + expectValue(makeHost()->floatToInt(slice(floats::kIntMax), 0), kMax64); +} + +TEST_F(FloatToIntImpl, Min) +{ + // floatIntMin rounds to -(2^63-1), i.e. -kMax64. + expectValue(makeHost()->floatToInt(slice(floats::kIntMin), 0), -kMax64); +} + +TEST_F(FloatToIntImpl, OverflowsInt64IsComputationError) +{ + expectError( + makeHost()->floatToInt(slice(floats::kUintMax), 0), + HostFunctionError::FloatComputationError); +} + +TEST_F(FloatToIntImpl, PiRoundsByMode) +{ + expectValue(makeHost()->floatToInt(slice(floats::kPi), 0), std::int64_t{3}); // ToNearest + expectValue(makeHost()->floatToInt(slice(floats::kPi), 1), std::int64_t{3}); // TowardsZero + expectValue(makeHost()->floatToInt(slice(floats::kPi), 2), std::int64_t{3}); // Downward + expectValue(makeHost()->floatToInt(slice(floats::kPi), 3), std::int64_t{4}); // Upward +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/FloatToMantExp.cpp b/src/tests/libxrpl/tx/wasm/host_functions/FloatToMantExp.cpp new file mode 100644 index 0000000000..4f7fbd0c6a --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/FloatToMantExp.cpp @@ -0,0 +1,80 @@ +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace xrpl::test { + +struct FloatToMantExpImpl : FloatTest +{ + static constexpr std::int32_t kExpMin = std::numeric_limits::min(); + + static FloatPair + pair(std::int64_t mantissa, std::int32_t exponent) + { + return FloatPair{mantissa, exponent}; + } +}; + +TEST_F(FloatToMantExpImpl, MalformedInput) +{ + expectError(makeHost()->floatToMantExp(Slice{}), HostFunctionError::FloatInputMalformed); +} + +TEST_F(FloatToMantExpImpl, Zero) +{ + expectValue(makeHost()->floatToMantExp(slice(floats::kIntZero)), pair(0, kExpMin)); +} + +TEST_F(FloatToMantExpImpl, One) +{ + expectValue( + makeHost()->floatToMantExp(slice(floats::kOne)), + pair(1'000'000'000'000'000'000, -floats::kNormalExp)); +} + +TEST_F(FloatToMantExpImpl, MinusOne) +{ + expectValue( + makeHost()->floatToMantExp(slice(floats::kMinusOne)), + pair(-1'000'000'000'000'000'000, -floats::kNormalExp)); +} + +TEST_F(FloatToMantExpImpl, Ten) +{ + expectValue( + makeHost()->floatToMantExp(slice(floats::kTen)), + pair(1'000'000'000'000'000'000, -floats::kNormalExp + 1)); +} + +TEST_F(FloatToMantExpImpl, Pi) +{ + expectValue( + makeHost()->floatToMantExp(slice(floats::kPi)), + pair(3'141'592'653'589'793'000, -floats::kNormalExp)); +} + +TEST_F(FloatToMantExpImpl, IntMax) +{ + expectValue(makeHost()->floatToMantExp(slice(floats::kIntMax)), pair(kMax64, 0)); +} + +TEST_F(FloatToMantExpImpl, IntMin) +{ + expectValue(makeHost()->floatToMantExp(slice(floats::kIntMin)), pair(-kMax64, 0)); +} + +TEST_F(FloatToMantExpImpl, Max) +{ + expectValue( + makeHost()->floatToMantExp(slice(floats::kMax)), + pair(Number::kMaxRep, Number::kMaxExponent)); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/GetNFT.cpp b/src/tests/libxrpl/tx/wasm/host_functions/GetNFT.cpp new file mode 100644 index 0000000000..2443de27c2 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/GetNFT.cpp @@ -0,0 +1,54 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace xrpl::test { + +struct GetNFTImpl : NFTTest +{ +}; + +TEST_F(GetNFTImpl, UnsetAccountIsInvalidAccount) +{ + auto const issuer = Account{"issuer"}; + expectError( + makeHost()->getNFT(AccountID{}, makeNftId(issuer.id())), HostFunctionError::InvalidAccount); +} + +TEST_F(GetNFTImpl, ZeroIdIsInvalidParams) +{ + auto const owner = fund("owner"); + expectError(makeHost()->getNFT(owner.id(), uint256{}), HostFunctionError::InvalidParams); +} + +TEST_F(GetNFTImpl, MissingTokenIsNotFound) +{ + auto const owner = fund("owner"); + expectError( + makeHost()->getNFT(owner.id(), makeNftId(owner.id())), + HostFunctionError::LedgerObjNotFound); +} + +TEST_F(GetNFTImpl, ReturnsUri) +{ + auto const owner = fund("owner"); + auto const uri = std::string_view{"https://example.com/nft"}; + auto const id = mintNFT(owner, uri); + expectValue(makeHost()->getNFT(owner.id(), id), toBytes(uri)); +} + +TEST_F(GetNFTImpl, WithoutUriFieldNotFound) +{ + auto const owner = fund("owner"); + auto const id = mintNFT(owner); + expectError(makeHost()->getNFT(owner.id(), id), HostFunctionError::FieldNotFound); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/IsAmendmentEnabled.cpp b/src/tests/libxrpl/tx/wasm/host_functions/IsAmendmentEnabled.cpp index 64e6c86454..471c6e171d 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/IsAmendmentEnabled.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/IsAmendmentEnabled.cpp @@ -4,7 +4,6 @@ #include #include -#include #include namespace xrpl::test { @@ -17,34 +16,30 @@ TEST_F(IsAmendmentEnabledImpl, EnabledAmendmentByIdReadsOne) { auto const id = getRegisteredFeature("TokenEscrow"); ASSERT_TRUE(id.has_value()); - auto const result = host().isAmendmentEnabled(id.value_or(uint256{})); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, 1); + auto const result = makeHost()->isAmendmentEnabled(id.value_or(uint256{})); + expectValue(result, 1); } TEST_F(IsAmendmentEnabledImpl, EnabledAmendmentByNameReadsOne) { - auto const result = host().isAmendmentEnabled(std::string_view{"TokenEscrow"}); + auto const result = makeHost()->isAmendmentEnabled(std::string_view{"TokenEscrow"}); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, 1); + expectValue(result, 1); } TEST_F(IsAmendmentEnabledImpl, UnknownAmendmentByIdReadsZero) { - auto const result = host().isAmendmentEnabled( + auto const result = makeHost()->isAmendmentEnabled( uint256{"DEADBEEF00000000000000000000000000000000000000000000000000000000"}); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, 0); + expectValue(result, 0); } TEST_F(IsAmendmentEnabledImpl, UnknownAmendmentNameReadsZero) { - auto const result = host().isAmendmentEnabled(std::string_view{"DEADBEEF"}); + auto const result = makeHost()->isAmendmentEnabled(std::string_view{"DEADBEEF"}); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, 0); + expectValue(result, 0); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/LedgerObjArrayLen.cpp b/src/tests/libxrpl/tx/wasm/host_functions/LedgerObjArrayLen.cpp new file mode 100644 index 0000000000..5efd264939 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/LedgerObjArrayLen.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace xrpl::test { + +struct LedgerObjArrayLenImpl : WasmImplTest +{ + using WasmImplTest::makeHost; + + WasmHost + makeHost(Account const& acct) + { + makeSignerList(acct, 2, {{Account{"alice"}, 1}, {Account{"becky"}, 1}}); + auto assembler = bareTx(); + auto h = makeHost(keylet::account(AccountID{}), assembler.type, std::move(assembler.build)); + h->cacheLedgerObj(keylet::signerList(acct.id()).key, 1); + return h; + } +}; + +TEST_F(LedgerObjArrayLenImpl, SignerEntriesLength) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectValue(h->getLedgerObjArrayLen(1, sfSignerEntries), 2); +} + +TEST_F(LedgerObjArrayLenImpl, NonArrayFieldNoArray) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError(h->getLedgerObjArrayLen(1, sfAccount), HostFunctionError::NoArray); +} + +TEST_F(LedgerObjArrayLenImpl, MissingArrayFieldNotFound) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError(h->getLedgerObjArrayLen(1, sfMemos), HostFunctionError::FieldNotFound); +} + +TEST_F(LedgerObjArrayLenImpl, SlotErrors) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError(h->getLedgerObjArrayLen(0, sfSignerEntries), HostFunctionError::SlotOutRange); + expectError(h->getLedgerObjArrayLen(257, sfSignerEntries), HostFunctionError::SlotOutRange); + expectError(h->getLedgerObjArrayLen(2, sfSignerEntries), HostFunctionError::EmptySlot); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/LedgerObjField.cpp b/src/tests/libxrpl/tx/wasm/host_functions/LedgerObjField.cpp new file mode 100644 index 0000000000..2c6f15fa2d --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/LedgerObjField.cpp @@ -0,0 +1,80 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +namespace xrpl::test { + +struct LedgerObjFieldImpl : WasmImplTest +{ + template + void + checkCachedField( + Account const& acct, + std::uint32_t index, + SField const& field, + TxAssembler assembler, + Functor&& f) + { + auto const accountKeylet = keylet::account(acct.id()); + auto h = makeHost(accountKeylet, assembler.type, std::move(assembler.build)); + h->cacheLedgerObj(accountKeylet.key, 1); + expectValue(h->getLedgerObjField(index, field), f()); + } + + void + checkCachedFieldError( + Account const& acct, + std::uint32_t index, + SField const& field, + TxAssembler assembler, + HostFunctionError error) + { + auto const accountKeylet = keylet::account(acct.id()); + auto h = makeHost(accountKeylet, assembler.type, std::move(assembler.build)); + h->cacheLedgerObj(accountKeylet.key, 1); + expectError(h->getLedgerObjField(index, field), error); + } +}; + +TEST_F(LedgerObjFieldImpl, MatchesAccount) +{ + auto const owner = fund("owner"); + checkCachedField(owner, 1, sfAccount, bareTx(), [&] { return toBytes(owner.id()); }); +} + +TEST_F(LedgerObjFieldImpl, MatchesBalance) +{ + auto const owner = fund("owner"); + auto const root = ledger.getOpenLedger().read(keylet::account(owner.id())); + checkCachedField( + owner, 1, sfBalance, bareTx(), [&] { return toBytes(root->getFieldAmount(sfBalance)); }); +} + +TEST_F(LedgerObjFieldImpl, MatchesAccountSlotOutOfRange) +{ + auto const owner = fund("owner"); + checkCachedFieldError(owner, 0, sfAccount, bareTx(), HostFunctionError::SlotOutRange); + checkCachedFieldError(owner, 257, sfAccount, bareTx(), HostFunctionError::SlotOutRange); +} + +TEST_F(LedgerObjFieldImpl, MatchesAccountEmptySlot) +{ + auto const owner = fund("owner"); + checkCachedFieldError(owner, 2, sfAccount, bareTx(), HostFunctionError::EmptySlot); +} + +TEST_F(LedgerObjFieldImpl, MatchesOwnerFieldNotFound) +{ + auto const owner = fund("owner"); + checkCachedFieldError(owner, 1, sfOwner, bareTx(), HostFunctionError::FieldNotFound); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/LedgerObjNestedArrayLen.cpp b/src/tests/libxrpl/tx/wasm/host_functions/LedgerObjNestedArrayLen.cpp new file mode 100644 index 0000000000..acd33956ef --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/LedgerObjNestedArrayLen.cpp @@ -0,0 +1,79 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace xrpl::test { + +struct LedgerObjNestedArrayLenImpl : WasmImplTest +{ + using WasmImplTest::makeHost; + + WasmHost + makeHost(Account const& acct) + { + makeSignerList(acct, 2, {{Account{"alice"}, 1}, {Account{"becky"}, 1}}); + auto assembler = bareTx(); + auto h = makeHost(keylet::account(AccountID{}), assembler.type, std::move(assembler.build)); + h->cacheLedgerObj(keylet::signerList(acct.id()).key, 1); + return h; + } +}; + +TEST_F(LedgerObjNestedArrayLenImpl, SignerEntriesLength) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectValue(h->getLedgerObjNestedArrayLen(1, FieldLocator{{sfSignerEntries.getCode()}}), 2); +} + +TEST_F(LedgerObjNestedArrayLenImpl, NonArrayFieldNoArray) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError( + h->getLedgerObjNestedArrayLen(1, FieldLocator{{sfSignerQuorum.getCode()}}), + HostFunctionError::NoArray); +} + +TEST_F(LedgerObjNestedArrayLenImpl, MissingFieldNotFound) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError( + h->getLedgerObjNestedArrayLen(1, FieldLocator{{sfSigners.getCode()}}), + HostFunctionError::FieldNotFound); +} + +TEST_F(LedgerObjNestedArrayLenImpl, SlotErrors) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError( + h->getLedgerObjNestedArrayLen(0, FieldLocator{{sfSignerEntries.getCode()}}), + HostFunctionError::SlotOutRange); + expectError( + h->getLedgerObjNestedArrayLen(257, FieldLocator{{sfSignerEntries.getCode()}}), + HostFunctionError::SlotOutRange); + expectError( + h->getLedgerObjNestedArrayLen(2, FieldLocator{{sfSignerEntries.getCode()}}), + HostFunctionError::EmptySlot); +} + +TEST_F(LedgerObjNestedArrayLenImpl, NestIntoNonContainerMalformed) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError( + h->getLedgerObjNestedArrayLen( + 1, FieldLocator{{sfSignerQuorum.getCode(), 0, sfAccount.getCode()}}), + HostFunctionError::LocatorMalformed); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/LedgerObjNestedField.cpp b/src/tests/libxrpl/tx/wasm/host_functions/LedgerObjNestedField.cpp new file mode 100644 index 0000000000..73d5e52121 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/LedgerObjNestedField.cpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace xrpl::test { + +struct LedgerObjNestedFieldImpl : WasmImplTest +{ + using WasmImplTest::makeHost; + + WasmHost + makeHost(Account const& acct) + { + makeSignerList(acct, 2, {{Account{"alice"}, 1}, {Account{"becky"}, 1}}); + auto assembler = bareTx(); + auto h = makeHost(keylet::account(AccountID{}), assembler.type, std::move(assembler.build)); + h->cacheLedgerObj(keylet::signerList(acct.id()).key, 1); + return h; + } +}; + +TEST_F(LedgerObjNestedFieldImpl, MatchesNestedSignerAccountsByIndex) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + + auto const sle = ledger.getOpenLedger().read(keylet::signerList(owner.id())); + ASSERT_NE(sle, nullptr); + auto const& entries = sle->getFieldArray(sfSignerEntries); + + expectValue( + h->getLedgerObjNestedField( + 1, FieldLocator{{sfSignerEntries.getCode(), 0, sfAccount.getCode()}}), + toBytes(entries[0].getAccountID(sfAccount))); + expectValue( + h->getLedgerObjNestedField( + 1, FieldLocator{{sfSignerEntries.getCode(), 1, sfAccount.getCode()}}), + toBytes(entries[1].getAccountID(sfAccount))); + EXPECT_NE(entries[0].getAccountID(sfAccount), entries[1].getAccountID(sfAccount)); +} + +TEST_F(LedgerObjNestedFieldImpl, MatchesNestedSignerWeight) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectValue( + h->getLedgerObjNestedField( + 1, FieldLocator{{sfSignerEntries.getCode(), 0, sfSignerWeight.getCode()}}), + toBytes(static_cast(1))); +} + +TEST_F(LedgerObjNestedFieldImpl, MatchesBaseSignerQuorum) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectValue( + h->getLedgerObjNestedField(1, FieldLocator{{sfSignerQuorum.getCode()}}), + toBytes(static_cast(2))); +} + +TEST_F(LedgerObjNestedFieldImpl, MissingFieldNotFound) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + auto const err = HostFunctionError::FieldNotFound; + + expectError( + h->getLedgerObjNestedField(1, FieldLocator{{sfSigners.getCode(), 0, sfAccount.getCode()}}), + err); + expectError( + h->getLedgerObjNestedField( + 1, FieldLocator{{sfSignerEntries.getCode(), 0, sfDestination.getCode()}}), + err); +} + +TEST_F(LedgerObjNestedFieldImpl, IndexOutOfBounds) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + auto const err = HostFunctionError::IndexOutOfBounds; + + expectError( + h->getLedgerObjNestedField( + 1, FieldLocator{{sfSignerEntries.getCode(), 2, sfAccount.getCode()}}), + err); + expectError( + h->getLedgerObjNestedField( + 1, FieldLocator{{sfSignerEntries.getCode(), -1, sfAccount.getCode()}}), + err); +} + +TEST_F(LedgerObjNestedFieldImpl, UnknownFieldCodeInvalidField) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + auto const err = HostFunctionError::InvalidField; + + expectError( + h->getLedgerObjNestedField( + 1, FieldLocator{{fieldCode(99999, 99999), 0, sfAccount.getCode()}}), + err); + expectError( + h->getLedgerObjNestedField( + 1, FieldLocator{{sfSignerEntries.getCode(), 0, fieldCode(99999, 99999)}}), + err); +} + +TEST_F(LedgerObjNestedFieldImpl, SlotErrors) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + + // 0 and 257 are outside the 1..256 slot range. + expectError( + h->getLedgerObjNestedField(0, FieldLocator{{sfSignerQuorum.getCode()}}), + HostFunctionError::SlotOutRange); + expectError( + h->getLedgerObjNestedField(257, FieldLocator{{sfSignerQuorum.getCode()}}), + HostFunctionError::SlotOutRange); + // Slot 2 is in range but nothing was cached there. + expectError( + h->getLedgerObjNestedField(2, FieldLocator{{sfSignerQuorum.getCode()}}), + HostFunctionError::EmptySlot); +} + +TEST_F(LedgerObjNestedFieldImpl, ContainerWithoutIndexNotLeaf) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError( + h->getLedgerObjNestedField(1, FieldLocator{{sfSignerEntries.getCode()}}), + HostFunctionError::NotLeafField); +} + +TEST_F(LedgerObjNestedFieldImpl, NestIntoNonContainerMalformed) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError( + h->getLedgerObjNestedField( + 1, FieldLocator{{sfSignerQuorum.getCode(), 0, sfAccount.getCode()}}), + HostFunctionError::LocatorMalformed); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/LedgerSqn.cpp b/src/tests/libxrpl/tx/wasm/host_functions/LedgerSqn.cpp index a17c00b93c..d9f072cd9a 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/LedgerSqn.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/LedgerSqn.cpp @@ -1,8 +1,6 @@ #include #include -#include - namespace xrpl::test { struct LedgerSqnImpl : WasmImplTest @@ -11,9 +9,7 @@ struct LedgerSqnImpl : WasmImplTest TEST_F(LedgerSqnImpl, MatchesLedger) { - auto const result = host().getLedgerSqn(); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, ledger.getOpenLedger().header().seq); + expectValue(makeHost()->getLedgerSqn(), ledger.getOpenLedger().header().seq); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/MptokenIssuanceKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/MptokenIssuanceKeylet.cpp index 7618cfd2ac..308df3a9df 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/MptokenIssuanceKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/MptokenIssuanceKeylet.cpp @@ -4,12 +4,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct MptokenIssuanceKeyletImpl : WasmImplTest @@ -18,21 +14,17 @@ struct MptokenIssuanceKeyletImpl : WasmImplTest TEST_F(MptokenIssuanceKeyletImpl, MatchesMptokenIssuanceKeyletFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); - auto const expected = keylet::mptokenIssuance(makeMptID(1u, owner.id())); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().mptokenIssuanceKeylet(owner.id(), 1u); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches( + makeHost()->mptokenIssuanceKeylet(owner.id(), 1u), + keylet::mptokenIssuance(makeMptID(1u, owner.id()))); } TEST_F(MptokenIssuanceKeyletImpl, InvalidAccount) { - auto result = host().mptokenIssuanceKeylet(AccountID{}, 1u); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError( + makeHost()->mptokenIssuanceKeylet(AccountID{}, 1u), HostFunctionError::InvalidAccount); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/MptokenKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/MptokenKeylet.cpp index 9ebb9695c2..2add30b31b 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/MptokenKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/MptokenKeylet.cpp @@ -4,12 +4,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct MptokenKeyletImpl : WasmImplTest @@ -18,37 +14,27 @@ struct MptokenKeyletImpl : WasmImplTest TEST_F(MptokenKeyletImpl, MatchesMptokenKeyletFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); - auto const anotherAccount = Account{"account"}; - ledger.createAccount(anotherAccount, XRP(1000)); + auto const owner = fund("owner"); + auto const anotherAccount = fund("account"); auto const mpt = makeMptID(1u, owner.id()); - auto const expected = keylet::mptoken(mpt, anotherAccount.id()); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().mptokenKeylet(mpt, anotherAccount.id()); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches( + makeHost()->mptokenKeylet(mpt, anotherAccount.id()), + keylet::mptoken(mpt, anotherAccount.id())); } TEST_F(MptokenKeyletImpl, InvalidMpt) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); - auto result = host().mptokenKeylet(MPTID{}, owner.id()); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidParams); + auto const owner = fund("owner"); + expectError(makeHost()->mptokenKeylet(MPTID{}, owner.id()), HostFunctionError::InvalidParams); } TEST_F(MptokenKeyletImpl, InvalidAccount) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); auto const mpt = makeMptID(1u, owner.id()); - auto result = host().mptokenKeylet(mpt, AccountID{}); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError(makeHost()->mptokenKeylet(mpt, AccountID{}), HostFunctionError::InvalidAccount); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/NFT.cpp b/src/tests/libxrpl/tx/wasm/host_functions/NFT.cpp deleted file mode 100644 index 85b265a056..0000000000 --- a/src/tests/libxrpl/tx/wasm/host_functions/NFT.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -namespace xrpl::test { - -struct NFTImpl : WasmImplTest -{ -}; - -TEST_F(NFTImpl, MatchesVaultFunction) -{ - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); - - auto const expected = keylet::vault(owner.id(), SeqProxy::rawSequence(1u)); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().vaultKeylet(owner.id(), 1u); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); -} - -TEST_F(NFTImpl, InvalidAccount) -{ - auto result = host().vaultKeylet(AccountID{}, 1u); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); -} - -} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/NFTFlags.cpp b/src/tests/libxrpl/tx/wasm/host_functions/NFTFlags.cpp new file mode 100644 index 0000000000..d06ad23a17 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/NFTFlags.cpp @@ -0,0 +1,26 @@ + +#include +#include +#include +#include + +#include + +namespace xrpl::test { + +struct NFTFlagsImpl : NFTTest +{ +}; + +TEST_F(NFTFlagsImpl, FlagsDecodeFromId) +{ + auto const issuer = Account{"issuer"}; + expectValue(makeHost()->getNFTFlags(makeNftId(issuer.id())), std::int32_t{kFlags}); +} + +TEST_F(NFTFlagsImpl, FlagsShouldBeZeroWithZeroNftId) +{ + expectValue(makeHost()->getNFTFlags(uint256{}), std::int32_t{}); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/NFTIssuer.cpp b/src/tests/libxrpl/tx/wasm/host_functions/NFTIssuer.cpp new file mode 100644 index 0000000000..6ee10ee6f9 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/NFTIssuer.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include +#include +#include +#include + +#include + +namespace xrpl::test { + +struct NFTIssuerImpl : NFTTest +{ +}; + +TEST_F(NFTIssuerImpl, IssuerDecodesFromId) +{ + auto const issuer = Account{"issuer"}; + expectValue(makeHost()->getNFTIssuer(makeNftId(issuer.id())), toBytes(issuer.id())); +} + +TEST_F(NFTIssuerImpl, IssuerZeroIsInvalidParams) +{ + expectError(makeHost()->getNFTIssuer(makeNftId(AccountID{})), HostFunctionError::InvalidParams); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/NFTSequence.cpp b/src/tests/libxrpl/tx/wasm/host_functions/NFTSequence.cpp new file mode 100644 index 0000000000..af8f2227a4 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/NFTSequence.cpp @@ -0,0 +1,26 @@ + +#include +#include +#include +#include + +#include + +namespace xrpl::test { + +struct NFTSequenceImpl : NFTTest +{ +}; + +TEST_F(NFTSequenceImpl, SequenceDecodesFromId) +{ + auto const issuer = Account{"issuer"}; + expectValue(makeHost()->getNFTSequence(makeNftId(issuer.id())), kSequence); +} + +TEST_F(NFTSequenceImpl, SequenceShouldBeZeroWithZeroNftId) +{ + expectValue(makeHost()->getNFTSequence(uint256{}), std::int32_t{}); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/NFTTaxon.cpp b/src/tests/libxrpl/tx/wasm/host_functions/NFTTaxon.cpp new file mode 100644 index 0000000000..da2942e6cd --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/NFTTaxon.cpp @@ -0,0 +1,19 @@ + +#include +#include +#include +#include + +namespace xrpl::test { + +struct NFTTaxonImpl : NFTTest +{ +}; + +TEST_F(NFTTaxonImpl, TaxonDecodesFromId) +{ + auto const issuer = Account{"issuer"}; + expectValue(makeHost()->getNFTTaxon(makeNftId(issuer.id())), kTaxon); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/NFTTransferFee.cpp b/src/tests/libxrpl/tx/wasm/host_functions/NFTTransferFee.cpp new file mode 100644 index 0000000000..c9181b872c --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/NFTTransferFee.cpp @@ -0,0 +1,26 @@ + +#include +#include +#include +#include + +#include + +namespace xrpl::test { + +struct NFTTransferFeeImpl : NFTTest +{ +}; + +TEST_F(NFTTransferFeeImpl, TransferFeeDecodesFromId) +{ + auto const issuer = Account{"issuer"}; + expectValue(makeHost()->getNFTTransferFee(makeNftId(issuer.id())), std::int32_t{kFee}); +} + +TEST_F(NFTTransferFeeImpl, TransferFeeShouldBeZeroWithZeroNftId) +{ + expectValue(makeHost()->getNFTTransferFee(uint256{}), std::int32_t{}); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/NftokenOfferKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/NftokenOfferKeylet.cpp index 928d84de12..e6dee50a06 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/NftokenOfferKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/NftokenOfferKeylet.cpp @@ -5,12 +5,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct NftokenOfferKeyletImpl : WasmImplTest @@ -19,21 +15,16 @@ struct NftokenOfferKeyletImpl : WasmImplTest TEST_F(NftokenOfferKeyletImpl, MatchesNftokenOfferFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); - auto const expected = keylet::nftokenOffer(owner.id(), SeqProxy::rawSequence(1u)); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().nftokenOfferKeylet(owner.id(), 1u); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches( + makeHost()->nftokenOfferKeylet(owner.id(), 1u), + keylet::nftokenOffer(owner.id(), SeqProxy::rawSequence(1u))); } TEST_F(NftokenOfferKeyletImpl, InvalidAccount) { - auto result = host().nftokenOfferKeylet(AccountID{}, 1u); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError(makeHost()->nftokenOfferKeylet(AccountID{}, 1u), HostFunctionError::InvalidAccount); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/OfferKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/OfferKeylet.cpp index 203fe4e183..6ea5f25fdd 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/OfferKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/OfferKeylet.cpp @@ -5,12 +5,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct OfferKeyletImpl : WasmImplTest @@ -19,21 +15,16 @@ struct OfferKeyletImpl : WasmImplTest TEST_F(OfferKeyletImpl, MatchesOfferFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); - auto const expected = keylet::offer(owner.id(), SeqProxy::rawSequence(1u)); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().offerKeylet(owner.id(), 1u); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches( + makeHost()->offerKeylet(owner.id(), 1u), + keylet::offer(owner.id(), SeqProxy::rawSequence(1u))); } TEST_F(OfferKeyletImpl, InvalidAccount) { - auto result = host().offerKeylet(AccountID{}, 1u); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError(makeHost()->offerKeylet(AccountID{}, 1u), HostFunctionError::InvalidAccount); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/OracleKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/OracleKeylet.cpp index b6bdc89d59..f0330eb827 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/OracleKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/OracleKeylet.cpp @@ -4,12 +4,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct OracleKeyletImpl : WasmImplTest @@ -18,21 +14,14 @@ struct OracleKeyletImpl : WasmImplTest TEST_F(OracleKeyletImpl, MatchesOracleFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); - auto const expected = keylet::oracle(owner.id(), 1u); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().oracleKeylet(owner.id(), 1u); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches(makeHost()->oracleKeylet(owner.id(), 1u), keylet::oracle(owner.id(), 1u)); } TEST_F(OracleKeyletImpl, InvalidAccount) { - auto result = host().oracleKeylet(AccountID{}, 1u); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError(makeHost()->oracleKeylet(AccountID{}, 1u), HostFunctionError::InvalidAccount); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/ParentLedgerHash.cpp b/src/tests/libxrpl/tx/wasm/host_functions/ParentLedgerHash.cpp index 3d0114804f..77176d4806 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/ParentLedgerHash.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/ParentLedgerHash.cpp @@ -1,8 +1,6 @@ #include #include -#include - namespace xrpl::test { struct ParentLedgerHashImpl : WasmImplTest @@ -11,9 +9,7 @@ struct ParentLedgerHashImpl : WasmImplTest TEST_F(ParentLedgerHashImpl, MatchesLedger) { - auto const result = host().getParentLedgerHash(); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, ledger.getOpenLedger().header().parentHash); + expectValue(makeHost()->getParentLedgerHash(), ledger.getOpenLedger().header().parentHash); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/ParentLedgerTime.cpp b/src/tests/libxrpl/tx/wasm/host_functions/ParentLedgerTime.cpp index aab394e1ac..bc284847f1 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/ParentLedgerTime.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/ParentLedgerTime.cpp @@ -1,8 +1,6 @@ #include #include -#include - namespace xrpl::test { struct ParentLedgerTimeImpl : WasmImplTest @@ -11,9 +9,9 @@ struct ParentLedgerTimeImpl : WasmImplTest TEST_F(ParentLedgerTimeImpl, MatchesLedger) { - auto const result = host().getParentLedgerTime(); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, ledger.getOpenLedger().parentCloseTime().time_since_epoch().count()); + expectValue( + makeHost()->getParentLedgerTime(), + ledger.getOpenLedger().parentCloseTime().time_since_epoch().count()); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/PaychannelKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/PaychannelKeylet.cpp index 20d17d3dc6..f11da07836 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/PaychannelKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/PaychannelKeylet.cpp @@ -5,12 +5,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct PaychannelKeyletImpl : WasmImplTest @@ -19,41 +15,33 @@ struct PaychannelKeyletImpl : WasmImplTest TEST_F(PaychannelKeyletImpl, MatchesPaychannelFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); - auto const destination = Account{"destination"}; - ledger.createAccount(destination, XRP(1000)); + auto const owner = fund("owner"); + auto const destination = fund("destination"); - auto const expected = - keylet::payChannel(owner.id(), destination.id(), SeqProxy::rawSequence(1u)); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().paychannelKeylet(owner.id(), destination.id(), 1u); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches( + makeHost()->paychannelKeylet(owner.id(), destination.id(), 1u), + keylet::payChannel(owner.id(), destination.id(), SeqProxy::rawSequence(1u))); } TEST_F(PaychannelKeyletImpl, CantUseSelf) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); - auto result = host().paychannelKeylet(owner.id(), owner.id(), 1u); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidParams); + expectError( + makeHost()->paychannelKeylet(owner.id(), owner.id(), 1u), HostFunctionError::InvalidParams); } TEST_F(PaychannelKeyletImpl, InvalidAccount) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); - auto result = host().paychannelKeylet(AccountID{}, owner.id(), 1u); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError( + makeHost()->paychannelKeylet(AccountID{}, owner.id(), 1u), + HostFunctionError::InvalidAccount); - result = host().paychannelKeylet(owner.id(), AccountID{}, 1u); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError( + makeHost()->paychannelKeylet(owner.id(), AccountID{}, 1u), + HostFunctionError::InvalidAccount); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/PermissionedDomainedKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/PermissionedDomainedKeylet.cpp index 7a345768a4..76518a796a 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/PermissionedDomainedKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/PermissionedDomainedKeylet.cpp @@ -5,12 +5,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct PermissionedDomainKeyletImpl : WasmImplTest @@ -19,21 +15,17 @@ struct PermissionedDomainKeyletImpl : WasmImplTest TEST_F(PermissionedDomainKeyletImpl, MatchesPermissionedDomainFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); - auto const expected = keylet::permissionedDomain(owner.id(), SeqProxy::rawSequence(1u)); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().permissionedDomainKeylet(owner.id(), 1u); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches( + makeHost()->permissionedDomainKeylet(owner.id(), 1u), + keylet::permissionedDomain(owner.id(), SeqProxy::rawSequence(1u))); } TEST_F(PermissionedDomainKeyletImpl, InvalidAccount) { - auto result = host().permissionedDomainKeylet(AccountID{}, 1u); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError( + makeHost()->permissionedDomainKeylet(AccountID{}, 1u), HostFunctionError::InvalidAccount); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/Sha512Half.cpp b/src/tests/libxrpl/tx/wasm/host_functions/Sha512Half.cpp new file mode 100644 index 0000000000..f8f6b197d2 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/Sha512Half.cpp @@ -0,0 +1,21 @@ +#include + +#include +#include + +#include + +namespace xrpl::test { + +struct Sha512HalfImpl : WasmImplTest +{ +}; + +TEST_F(Sha512HalfImpl, LogsMessageAndData) +{ + static constexpr auto data = std::string_view{"hello world"}; + auto const result = makeHost()->computeSha512HalfHash({data.data(), data.size()}); + expectValue(result, sha512Half(Slice{data.data(), data.size()})); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/SignerListKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/SignerListKeylet.cpp index a85eb614d0..a67e387f41 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/SignerListKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/SignerListKeylet.cpp @@ -4,12 +4,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct SignerListKeyletImpl : WasmImplTest @@ -18,21 +14,14 @@ struct SignerListKeyletImpl : WasmImplTest TEST_F(SignerListKeyletImpl, MatchesSignerListFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); - auto const expected = keylet::signerList(owner.id()); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().signerListKeylet(owner.id()); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches(makeHost()->signerListKeylet(owner.id()), keylet::signerList(owner.id())); } TEST_F(SignerListKeyletImpl, InvalidAccount) { - auto result = host().signerListKeylet(AccountID{}); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError(makeHost()->signerListKeylet(AccountID{}), HostFunctionError::InvalidAccount); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/TicketKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/TicketKeylet.cpp index 391c0ca8dc..102ef62f33 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/TicketKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/TicketKeylet.cpp @@ -5,12 +5,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct TicketKeyletImpl : WasmImplTest @@ -19,21 +15,16 @@ struct TicketKeyletImpl : WasmImplTest TEST_F(TicketKeyletImpl, MatchesTicketFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); - auto const expected = keylet::ticket(owner.id(), SeqProxy::rawTicket(1u)); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().ticketKeylet(owner.id(), 1u); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches( + makeHost()->ticketKeylet(owner.id(), 1u), + keylet::ticket(owner.id(), SeqProxy::rawTicket(1u))); } TEST_F(TicketKeyletImpl, InvalidAccount) { - auto result = host().ticketKeylet(AccountID{}, 1u); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError(makeHost()->ticketKeylet(AccountID{}, 1u), HostFunctionError::InvalidAccount); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/Trace.cpp b/src/tests/libxrpl/tx/wasm/host_functions/Trace.cpp new file mode 100644 index 0000000000..c4b165647c --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/Trace.cpp @@ -0,0 +1,30 @@ +#include + +#include +#include +#include + +#include + +namespace xrpl::test { + +struct TraceImpl : WasmImplTest +{ +}; + +TEST_F(TraceImpl, LogsMessageAndData) +{ + auto h = makeTracingHost(); + h->trace("hello", "world"); + EXPECT_NE(logged().find("hello world"), std::string::npos) << logged(); +} + +TEST_F(TraceImpl, NothingLoggedBelowTraceSeverity) +{ + CaptureSink sink{beast::Severity::Error}; + auto h = makeHost(beast::Journal{sink}); + h->trace("hello", "world"); + EXPECT_TRUE(sink.messages().empty()) << sink.messages(); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/TrustLineKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/TrustLineKeylet.cpp index 71a8cd1f37..868eaeceb9 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/TrustLineKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/TrustLineKeylet.cpp @@ -5,12 +5,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct TrustlineKeyletImpl : WasmImplTest @@ -19,58 +15,49 @@ struct TrustlineKeyletImpl : WasmImplTest TEST_F(TrustlineKeyletImpl, MatchesTrustlineKeyletFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); - auto const destination = Account{"destination"}; - ledger.createAccount(destination, XRP(1000)); + auto const owner = fund("owner"); + auto const destination = fund("destination"); auto const usd = toCurrency("USD"); - auto const expected = keylet::trustLine(owner.id(), destination.id(), usd); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().trustLineKeylet(owner.id(), destination.id(), usd); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches( + makeHost()->trustLineKeylet(owner.id(), destination.id(), usd), + keylet::trustLine(owner.id(), destination.id(), usd)); } TEST_F(TrustlineKeyletImpl, InvalidCurrency) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); - auto const destination = Account{"destination"}; - ledger.createAccount(destination, XRP(1000)); + auto const owner = fund("owner"); + auto const destination = fund("destination"); - auto const result = host().trustLineKeylet(owner.id(), destination.id(), toCurrency("")); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidParams); + expectError( + makeHost()->trustLineKeylet(owner.id(), destination.id(), toCurrency("")), + HostFunctionError::InvalidParams); } TEST_F(TrustlineKeyletImpl, CantTrustlineToSelf) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); auto const usd = toCurrency("USD"); - auto const result = host().trustLineKeylet(owner.id(), owner.id(), usd); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidParams); + expectError( + makeHost()->trustLineKeylet(owner.id(), owner.id(), usd), HostFunctionError::InvalidParams); } TEST_F(TrustlineKeyletImpl, InvalidAccount) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); auto const usd = toCurrency("USD"); - auto result = host().trustLineKeylet(AccountID{}, owner.id(), usd); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError( + makeHost()->trustLineKeylet(AccountID{}, owner.id(), usd), + HostFunctionError::InvalidAccount); - result = host().trustLineKeylet(owner.id(), AccountID{}, usd); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError( + makeHost()->trustLineKeylet(owner.id(), AccountID{}, usd), + HostFunctionError::InvalidAccount); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/TxArrayLen.cpp b/src/tests/libxrpl/tx/wasm/host_functions/TxArrayLen.cpp new file mode 100644 index 0000000000..090a616fa5 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/TxArrayLen.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace xrpl::test { + +struct TxArrayLenImpl : WasmImplTest +{ + using WasmImplTest::makeHost; + + WasmHost + makeHost(Account const& acct) + { + auto assembler = escrowFinishTx(ledger, acct); + assembler.build = [inner = std::move(assembler.build)](STObject& obj) { + inner(obj); + auto memos = STArray{}; + memos.push_back(makeMemo(toBytes("hello"))); + memos.push_back(makeMemo(toBytes("world"))); + obj.setFieldArray(sfMemos, memos); + }; + return makeHost(keylet::account(acct.id()), assembler.type, std::move(assembler.build)); + } +}; + +TEST_F(TxArrayLenImpl, MemosLength) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectValue(h->getTxArrayLen(sfMemos), 2); +} + +TEST_F(TxArrayLenImpl, CredentialIdsLength) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectValue(h->getTxArrayLen(sfCredentialIDs), 1); +} + +TEST_F(TxArrayLenImpl, NonArrayFieldNoArray) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError(h->getTxArrayLen(sfAccount), HostFunctionError::NoArray); +} + +TEST_F(TxArrayLenImpl, MissingArrayFieldNotFound) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError(h->getTxArrayLen(sfSigners), HostFunctionError::FieldNotFound); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/TxField.cpp b/src/tests/libxrpl/tx/wasm/host_functions/TxField.cpp index 485abd6d51..bda2c01350 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/TxField.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/TxField.cpp @@ -1,7 +1,9 @@ -#include #include +#include +#include #include -#include +#include +#include #include #include @@ -10,73 +12,160 @@ #include #include -#include +#include +#include namespace xrpl::test { -struct CacheLedgerObjImpl : WasmImplTest +struct TxFieldImpl : WasmImplTest { + template void - runMatchesLedger(bool implicit) + checkTxField(Account const& acct, SField const& field, TxAssembler assembler, Functor&& f) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto h = makeHost(keylet::account(acct.id()), assembler.type, std::move(assembler.build)); + expectValue(h->getTxField(field), f()); + } - auto& h = host(); - auto const key = keylet::account(owner.id()).key; - - for (auto i = int32_t{1}; i < 257; ++i) - { - auto const slot = h.cacheLedgerObj(key, i); - ASSERT_TRUE(slot.has_value()) << "cacheLedgerObj should find the created account"; - EXPECT_EQ(*slot, i); - - auto const account = h.getLedgerObjField(*slot, sfAccount); - ASSERT_TRUE(account.has_value()); - Bytes const ownerBytes{owner.id().begin(), owner.id().end()}; - EXPECT_EQ(*account, ownerBytes); - - auto const sle = ledger.getOpenLedger().read(keylet::account(owner.id())); - ASSERT_NE(sle, nullptr); - auto const& ledgerAccount = sle->getAccountID(sfAccount); - EXPECT_EQ(*account, (Bytes{ledgerAccount.begin(), ledgerAccount.end()})); - } - - // Every slot is now occupied, so asking to auto-allocate (cacheIdx == 0) has nowhere - // to put the object. - auto const result = h.cacheLedgerObj(key, 0); - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::SlotsFull); + void + checkTxFieldError( + Account const& acct, + SField const& field, + TxAssembler assembler, + HostFunctionError error) + { + auto h = makeHost(keylet::account(acct.id()), assembler.type, std::move(assembler.build)); + expectError(h->getTxField(field), error); } }; -TEST_F(CacheLedgerObjImpl, MatchesLedgerExplicitIndices) +TEST_F(TxFieldImpl, MPTokenIssuanceCreateTxMatchesScale) { - runMatchesLedger(false); + auto const owner = Account{"owner"}; + ledger.createAccount(owner, XRP(1000)); + auto const expectedScale = std::uint8_t{8}; + checkTxField(owner, sfAssetScale, mptIssuanceCreateTx(owner, expectedScale), [&] { + return toBytes(expectedScale); + }); } -TEST_F(CacheLedgerObjImpl, MatchesLedgerImplicitIndices) +TEST_F(TxFieldImpl, AmmDepositTxUSDMatchesAsset) { - runMatchesLedger(true); + auto const owner = Account{"owner"}; + ledger.createAccount(owner, XRP(1000)); + auto usdIssue = Issue{toCurrency("USD"), owner.id()}; + checkTxField( + owner, sfAsset, ammDepositTx(owner, xrpIssue(), usdIssue), [&] { return Bytes(20, 0); }); } -TEST_F(CacheLedgerObjImpl, OutOfRange) +TEST_F(TxFieldImpl, AmmDepositTxUSDMatchesAsset2) { - auto result = host().cacheLedgerObj(uint256{}, -1); - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::SlotOutRange); - - result = host().cacheLedgerObj(uint256{}, 257); - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::SlotOutRange); + auto const owner = Account{"owner"}; + ledger.createAccount(owner, XRP(1000)); + auto usdIssue = Issue{toCurrency("USD"), owner.id()}; + checkTxField(owner, sfAsset2, ammDepositTx(owner, xrpIssue(), usdIssue), [&] { + return toBytes(Asset{usdIssue}); + }); } -TEST_F(CacheLedgerObjImpl, LedgerObjNotFound) +TEST_F(TxFieldImpl, AmmDepositTxGBPMatchesAsset) { - auto const ghost = keylet::account(Account{"ghost"}.id()).key; - auto result = host().cacheLedgerObj(ghost, 0); - ASSERT_FALSE(result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::LedgerObjNotFound); + auto const owner = Account{"owner"}; + ledger.createAccount(owner, XRP(1000)); + auto gbpIssue = Issue{toCurrency("GBP"), owner.id()}; + auto mptId = makeMptID(1, owner); + auto mptIssue = MPTIssue{mptId}; + checkTxField(owner, sfAsset, ammDepositTx(owner, gbpIssue, mptIssue), [&] { + return toBytes(Asset{gbpIssue}); + }); +} + +TEST_F(TxFieldImpl, AmmDepositTxGBPMatchesAsset2) +{ + auto const owner = Account{"owner"}; + ledger.createAccount(owner, XRP(1000)); + auto gbpIssue = Issue{toCurrency("GBP"), owner.id()}; + auto mptId = makeMptID(1, owner); + auto mptIssue = MPTIssue{mptId}; + checkTxField(owner, sfAsset2, ammDepositTx(owner, gbpIssue, mptIssue), [&] { + return toBytes(Asset{mptId}); + }); +} + +TEST_F(TxFieldImpl, EscrowTxMatchesAccount) +{ + auto const owner = Account{"owner"}; + ledger.createAccount(owner, XRP(1000)); + checkTxField(owner, sfAccount, escrowFinishTx(ledger, owner), [&] { + return Bytes{std::begin(owner.id()), std::end(owner.id())}; + }); +} + +TEST_F(TxFieldImpl, EscrowTxMatchesOwner) +{ + auto const owner = Account{"owner"}; + ledger.createAccount(owner, XRP(1000)); + checkTxField(owner, sfOwner, escrowFinishTx(ledger, owner), [&] { + return Bytes{std::begin(owner.id()), std::end(owner.id())}; + }); +} + +TEST_F(TxFieldImpl, EscrowTxMatchesTransactionType) +{ + auto const owner = Account{"owner"}; + ledger.createAccount(owner, XRP(1000)); + checkTxField(owner, sfTransactionType, escrowFinishTx(ledger, owner), [] { + return toBytes(ttESCROW_FINISH); + }); +} + +TEST_F(TxFieldImpl, EscrowTxMatchesOfferSequence) +{ + auto const owner = Account{"owner"}; + ledger.createAccount(owner, XRP(1000)); + checkTxField(owner, sfOfferSequence, escrowFinishTx(ledger, owner), [&] { + return toBytes(ledger.getAccountRoot(owner.id()).getSequence()); + }); +} + +TEST_F(TxFieldImpl, EscrowTxMatchesDestination) +{ + auto const owner = Account{"owner"}; + ledger.createAccount(owner, XRP(1000)); + checkTxFieldError( + owner, sfDestination, escrowFinishTx(ledger, owner), HostFunctionError::FieldNotFound); +} + +TEST_F(TxFieldImpl, EscrowTxMatchesMemos) +{ + auto const owner = Account{"owner"}; + ledger.createAccount(owner, XRP(1000)); + checkTxFieldError( + owner, sfMemos, escrowFinishTx(ledger, owner), HostFunctionError::NotLeafField); +} + +TEST_F(TxFieldImpl, EscrowTxMatchesCredentialIDs) +{ + auto const owner = Account{"owner"}; + ledger.createAccount(owner, XRP(1000)); + checkTxFieldError( + owner, sfCredentialIDs, escrowFinishTx(ledger, owner), HostFunctionError::NotLeafField); +} + +TEST_F(TxFieldImpl, EscrowTxMatchesInvalid) +{ + auto const owner = Account{"owner"}; + ledger.createAccount(owner, XRP(1000)); + checkTxFieldError( + owner, sfInvalid, escrowFinishTx(ledger, owner), HostFunctionError::FieldNotFound); +} + +TEST_F(TxFieldImpl, EscrowTxMatchesGeneric) +{ + auto const owner = Account{"owner"}; + ledger.createAccount(owner, XRP(1000)); + checkTxFieldError( + owner, sfGeneric, escrowFinishTx(ledger, owner), HostFunctionError::FieldNotFound); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/TxNestedArrayLen.cpp b/src/tests/libxrpl/tx/wasm/host_functions/TxNestedArrayLen.cpp new file mode 100644 index 0000000000..60b4971577 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/TxNestedArrayLen.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +namespace xrpl::test { + +struct TxNestedArrayLenImpl : WasmImplTest +{ + using WasmImplTest::makeHost; + + WasmHost + makeHost(Account const& acct) + { + auto assembler = escrowFinishTx(ledger, acct); + assembler.build = [inner = std::move(assembler.build)](STObject& obj) { + inner(obj); + auto memos = STArray{}; + memos.push_back(makeMemo(toBytes("hello"))); + obj.setFieldArray(sfMemos, memos); + }; + return makeHost(keylet::account(acct.id()), assembler.type, std::move(assembler.build)); + } +}; + +TEST_F(TxNestedArrayLenImpl, MemosLength) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectValue(h->getTxNestedArrayLen(FieldLocator{{sfMemos.getCode()}}), 1); +} + +TEST_F(TxNestedArrayLenImpl, CredentialIdsLength) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectValue(h->getTxNestedArrayLen(FieldLocator{{sfCredentialIDs.getCode()}}), 1); +} + +TEST_F(TxNestedArrayLenImpl, NonArrayFieldNoArray) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError( + h->getTxNestedArrayLen(FieldLocator{{sfAccount.getCode()}}), HostFunctionError::NoArray); +} + +TEST_F(TxNestedArrayLenImpl, MissingFieldNotFound) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError( + h->getTxNestedArrayLen(FieldLocator{{sfSigners.getCode()}}), + HostFunctionError::FieldNotFound); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/TxNestedField.cpp b/src/tests/libxrpl/tx/wasm/host_functions/TxNestedField.cpp new file mode 100644 index 0000000000..d6c9b68797 --- /dev/null +++ b/src/tests/libxrpl/tx/wasm/host_functions/TxNestedField.cpp @@ -0,0 +1,133 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace xrpl::test { + +struct TxNestedFieldImpl : WasmImplTest +{ + TxAssembler + assemble(Account const& acct) + { + auto assembler = escrowFinishTx(ledger, acct); + assembler.build = [inner = std::move(assembler.build)](STObject& obj) { + inner(obj); + auto memos = STArray{}; + auto memo = STObject::makeInnerObject(sfMemo); + memo.setFieldVL(sfMemoData, Slice{"hello", 5}); + memos.push_back(std::move(memo)); + obj.setFieldArray(sfMemos, memos); + }; + return assembler; + } + + using WasmImplTest::makeHost; + + WasmHost + makeHost(Account const& acct) + { + auto assembler = assemble(acct); + return makeHost(keylet::account(acct.id()), assembler.type, std::move(assembler.build)); + } +}; + +TEST_F(TxNestedFieldImpl, MatchesNestedMemo) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectValue( + h->getTxNestedField(FieldLocator{{sfMemos.getCode(), 0, sfMemoData.getCode()}}), + toBytes("hello")); +} + +TEST_F(TxNestedFieldImpl, MatchesCredId) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectValue( + h->getTxNestedField(FieldLocator{{sfCredentialIDs.getCode(), 0}}), toBytes(credentialId())); +} + +TEST_F(TxNestedFieldImpl, MatchesBaseFieldViaNestedLocator) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectValue(h->getTxNestedField(FieldLocator{{sfAccount.getCode()}}), toBytes(owner.id())); +} + +TEST_F(TxNestedFieldImpl, MissingFieldNotFound) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + auto const err = HostFunctionError::FieldNotFound; + + expectError( + h->getTxNestedField(FieldLocator{{sfSigners.getCode(), 0, sfAccount.getCode()}}), err); + expectError(h->getTxNestedField(FieldLocator{{sfMemos.getCode(), 0, sfURI.getCode()}}), err); + expectError(h->getTxNestedField(FieldLocator{{sfMemos.getCode(), 0, -1}}), err); + expectError(h->getTxNestedField(FieldLocator{{-1, 0, sfAccount.getCode()}}), err); + expectError(h->getTxNestedField(FieldLocator{{0, 0, sfAccount.getCode()}}), err); +} + +TEST_F(TxNestedFieldImpl, IndexOutOfBounds) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + auto const err = HostFunctionError::IndexOutOfBounds; + + expectError( + h->getTxNestedField(FieldLocator{{sfMemos.getCode(), 1, sfMemoData.getCode()}}), err); + expectError(h->getTxNestedField(FieldLocator{{sfCredentialIDs.getCode(), 1}}), err); + expectError( + h->getTxNestedField(FieldLocator{{sfMemos.getCode(), -1, sfMemoData.getCode()}}), err); + expectError(h->getTxNestedField(FieldLocator{{sfCredentialIDs.getCode(), -1}}), err); +} + +TEST_F(TxNestedFieldImpl, UnknownFieldCodeInvalidField) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + auto const err = HostFunctionError::InvalidField; + + expectError( + h->getTxNestedField(FieldLocator{{fieldCode(20000, 20000), 0, sfAccount.getCode()}}), err); + expectError( + h->getTxNestedField(FieldLocator{{sfMemos.getCode(), 0, fieldCode(20000, 20000)}}), err); + // Far-negative code: not in the SField map at all. + expectError( + h->getTxNestedField( + FieldLocator{{std::numeric_limits::min(), 0, sfAccount.getCode()}}), + err); +} + +TEST_F(TxNestedFieldImpl, ContainerWithoutIndexNotLeaf) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + auto const err = HostFunctionError::NotLeafField; + + expectError(h->getTxNestedField(FieldLocator{{sfMemos.getCode()}}), err); + expectError(h->getTxNestedField(FieldLocator{{sfCredentialIDs.getCode()}}), err); +} + +TEST_F(TxNestedFieldImpl, NestIntoNonContainerMalformed) +{ + auto const owner = fund("owner"); + auto h = makeHost(owner); + expectError( + h->getTxNestedField(FieldLocator{{sfAccount.getCode(), 0, sfAccount.getCode()}}), + HostFunctionError::LocatorMalformed); +} + +} // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/UpdateData.cpp b/src/tests/libxrpl/tx/wasm/host_functions/UpdateData.cpp index 6d2cb9bd18..f1ce71be60 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/UpdateData.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/UpdateData.cpp @@ -4,8 +4,6 @@ #include #include -#include - namespace xrpl::test { struct UpdateDataImpl : WasmImplTest @@ -14,22 +12,19 @@ struct UpdateDataImpl : WasmImplTest TEST_F(UpdateDataImpl, SmallData) { - auto& h = host(); + auto h = makeHost(); auto data = Bytes(10, 0x42); - auto result = h.updateData(Slice{data.data(), data.size()}); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, data.size()); + expectValue(h->updateData(Slice{data.data(), data.size()}), data.size()); // TODO: getData() does not seem to be called when the smart escrow finishes. - EXPECT_EQ(h.getData(), data); + EXPECT_EQ(h->getData(), data); } TEST_F(UpdateDataImpl, LargeData) { - auto& h = host(); + auto h = makeHost(); auto data = Bytes(kMaxWasmDataLength + 1, 0x42); - auto result = h.updateData(Slice{data.data(), data.size()}); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::DataFieldTooLarge); + expectError( + h->updateData(Slice{data.data(), data.size()}), HostFunctionError::DataFieldTooLarge); } } // namespace xrpl::test diff --git a/src/tests/libxrpl/tx/wasm/host_functions/VaultKeylet.cpp b/src/tests/libxrpl/tx/wasm/host_functions/VaultKeylet.cpp index 131ef99079..617df3c4de 100644 --- a/src/tests/libxrpl/tx/wasm/host_functions/VaultKeylet.cpp +++ b/src/tests/libxrpl/tx/wasm/host_functions/VaultKeylet.cpp @@ -5,12 +5,8 @@ #include #include -#include #include -#include -#include - namespace xrpl::test { struct VaultKeyletImpl : WasmImplTest @@ -19,21 +15,16 @@ struct VaultKeyletImpl : WasmImplTest TEST_F(VaultKeyletImpl, MatchesVaultFunction) { - auto const owner = Account{"owner"}; - ledger.createAccount(owner, XRP(1000)); + auto const owner = fund("owner"); - auto const expected = keylet::vault(owner.id(), SeqProxy::rawSequence(1u)); - auto const expectedBytes = Bytes{std::begin(expected.key), std::end(expected.key)}; - auto const result = host().vaultKeylet(owner.id(), 1u); - ASSERT_TRUE(result.has_value()); - EXPECT_EQ(*result, expectedBytes); + expectKeyletMatches( + makeHost()->vaultKeylet(owner.id(), 1u), + keylet::vault(owner.id(), SeqProxy::rawSequence(1u))); } TEST_F(VaultKeyletImpl, InvalidAccount) { - auto result = host().vaultKeylet(AccountID{}, 1u); - ASSERT_TRUE(!result.has_value()); - EXPECT_EQ(result.error(), HostFunctionError::InvalidAccount); + expectError(makeHost()->vaultKeylet(AccountID{}, 1u), HostFunctionError::InvalidAccount); } } // namespace xrpl::test