diff --git a/.cspell.config.yaml b/.cspell.config.yaml index f5ef2d2bba..e2383c78d9 100644 --- a/.cspell.config.yaml +++ b/.cspell.config.yaml @@ -7,6 +7,7 @@ ignorePaths: - cmake/** - LICENSE.md - .clang-tidy + - src/test/app/wasm_fixtures/*.c language: en allowCompoundWords: true # TODO (#6334) ignoreRandomStrings: true @@ -67,6 +68,7 @@ words: - Btrfs - Buildx - canonicality + - cdylib - canonicalised - changespq - checkme @@ -295,6 +297,7 @@ words: - STATSDCOLLECTOR - stissue - stnum + - stnumber - stobj - stobject - stpath @@ -362,6 +365,7 @@ words: - wthread - xbridge - xchain + - xfloat - ximinez - XMACRO - xrpkuwait diff --git a/CMakeLists.txt b/CMakeLists.txt index d289b1f0fe..1bbd4181ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,6 +96,7 @@ find_package(OpenSSL REQUIRED) find_package(secp256k1 REQUIRED) find_package(SOCI REQUIRED) find_package(SQLite3 REQUIRED) +find_package(wasmi REQUIRED) find_package(xxHash REQUIRED) target_link_libraries( diff --git a/cmake/XrplCore.cmake b/cmake/XrplCore.cmake index 7badb01a67..99b2d300d5 100644 --- a/cmake/XrplCore.cmake +++ b/cmake/XrplCore.cmake @@ -67,6 +67,7 @@ target_link_libraries( Xrpl::opts Xrpl::syslibs secp256k1::secp256k1 + wasmi::wasmi xrpl.libpb xxHash::xxhash $<$:antithesis-sdk-cpp> diff --git a/conan.lock b/conan.lock index c6a4070c77..ea5e66149d 100644 --- a/conan.lock +++ b/conan.lock @@ -3,6 +3,7 @@ "requires": [ "zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1782392402.122708", "xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1782392402.420688", + "wasmi/1.0.9#1fecdab9b90c96698eb35ea99ca4f5cb%1782307153.343419", "sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1782392403.185447", "soci/4.0.3#e726491a03468795453f7c83fc924a96%1782392402.679521", "snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1782307151.633168", diff --git a/conanfile.py b/conanfile.py index f883761f0e..09cb6deeae 100644 --- a/conanfile.py +++ b/conanfile.py @@ -34,6 +34,7 @@ class Xrpl(ConanFile): "nudb/2.0.9", "openssl/3.6.3", "soci/4.0.3", + "wasmi/1.0.9", "zlib/1.3.2", ] @@ -221,6 +222,7 @@ class Xrpl(ConanFile): "soci::soci", "secp256k1::secp256k1", "sqlite3::sqlite", + "wasmi::wasmi", "xxhash::xxhash", "zlib::zlib", ] diff --git a/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index f90800c715..9b3846d588 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -549,8 +549,21 @@ public: setround(RoundingMode inMode); /** - * Returns which mantissa scale is currently in use for normalization. + * Convert an integer to a RoundingMode, validating that it is in range. * + * Returns std::nullopt if the value does not correspond to a valid + * RoundingMode. + */ + static std::optional + checkedRoundingMode(int mode) noexcept + { + if (mode < static_cast(RoundingMode::ToNearest) || + mode > static_cast(RoundingMode::Upward)) + return std::nullopt; + return static_cast(mode); + } + + /** * If you think you need to call this outside of unit tests, no you don't. */ static MantissaRange::MantissaScale diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index e83e1c97b6..fe44b38bb8 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -322,6 +322,16 @@ constexpr std::uint8_t kVaultMaximumIouScale = 18; */ constexpr std::uint8_t kMaxAssetCheckDepth = 5; +/** + * Maximum length of a Data field in Escrow object that can be updated by WASM code. + */ +constexpr std::size_t kMaxWasmDataLength = 1 * 1024; // 1KB + +/** + * Maximum amount of data transfer across hostfunction<->wasm border. + */ +constexpr std::size_t kWasmTransferLimit = 1 << 20; // 1MB + /** * A ledger index. */ diff --git a/include/xrpl/protocol/TER.h b/include/xrpl/protocol/TER.h index 730d021254..b2f45b3267 100644 --- a/include/xrpl/protocol/TER.h +++ b/include/xrpl/protocol/TER.h @@ -129,8 +129,10 @@ enum TEMcodes : TERUnderlyingType { temARRAY_TOO_LARGE, temBAD_TRANSFER_FEE, temINVALID_INNER_BATCH, + temBAD_MPT, temBAD_CIPHERTEXT, + temBAD_WASM, }; //------------------------------------------------------------------------------ @@ -370,6 +372,7 @@ enum TECcodes : TERUnderlyingType { tecNO_DELEGATE_PERMISSION = 198, tecBAD_PROOF = 199, tecNO_SPONSOR_PERMISSION = 200, + tecOUT_OF_GAS = 201, }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/tx/wasm/HostFunc.h b/include/xrpl/tx/wasm/HostFunc.h new file mode 100644 index 0000000000..f318610488 --- /dev/null +++ b/include/xrpl/tx/wasm/HostFunc.h @@ -0,0 +1,523 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +namespace wasm_float { + +std::string +floatToString(Slice const& data); + +std::expected +floatFromIntImpl(int64_t x, int32_t mode); + +std::expected +floatFromUintImpl(uint64_t x, int32_t mode); + +std::expected +floatFromSTAmountImpl(STAmount const& x, int32_t mode); + +std::expected +floatFromSTNumberImpl(STNumber const& x, int32_t mode); + +std::expected +floatToIntImpl(Slice const& x, int32_t mode); + +std::expected +floatToMantExpImpl(Slice const& x); + +std::expected +floatFromMantExpImpl(int64_t mantissa, int32_t exponent, int32_t mode); + +std::expected +floatCompareImpl(Slice const& x, Slice const& y); + +std::expected +floatAddImpl(Slice const& x, Slice const& y, int32_t mode); + +std::expected +floatSubtractImpl(Slice const& x, Slice const& y, int32_t mode); + +std::expected +floatMultiplyImpl(Slice const& x, Slice const& y, int32_t mode); + +std::expected +floatDivideImpl(Slice const& x, Slice const& y, int32_t mode); + +std::expected +floatRootImpl(Slice const& x, int32_t n, int32_t mode); + +std::expected +floatPowerImpl(Slice const& x, int32_t n, int32_t mode); + +} // namespace wasm_float + +// Intended to work only through wasm runtime. Don't call them directly, except with unit tests +class HostFunctions +{ +protected: + RTOptRef rt_; + beast::Journal j_; + +public: + HostFunctions(beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) : j_(j) + { + } + + void + setRT(WasmRuntimeWrapper& rt) + { + rt_ = rt; + } + + void + resetRT() + { + rt_ = std::nullopt; + } + + [[nodiscard]] WasmRuntimeWrapper& + getRT() const + { + if (!rt_) + Throw("Wasm runtime not set"); + return rt_->get(); + } + + [[nodiscard]] beast::Journal + getJournal() const + { + return j_; + } + + // LCOV_EXCL_START + + [[nodiscard]] virtual bool + checkSelf() const + { + return true; + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getLedgerSqn() const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getParentLedgerTime() const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getParentLedgerHash() const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getBaseFee() const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + isAmendmentEnabled(uint256 const& amendmentId) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + isAmendmentEnabled(std::string_view const& amendmentName) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + virtual std::expected + cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getTxField(SField const& fname) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getCurrentLedgerObjField(SField const& fname) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getLedgerObjField(int32_t cacheIdx, SField const& fname) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getTxNestedField(FieldLocator const& locator) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getCurrentLedgerObjNestedField(FieldLocator const& locator) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getLedgerObjNestedField(int32_t cacheIdx, FieldLocator const& locator) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getTxArrayLen(SField const& fname) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getCurrentLedgerObjArrayLen(SField const& fname) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getTxNestedArrayLen(FieldLocator const& locator) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getCurrentLedgerObjNestedArrayLen(FieldLocator const& locator) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getLedgerObjNestedArrayLen(int32_t cacheIdx, FieldLocator const& locator) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + virtual std::expected + updateData(Slice const& data) + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + computeSha512HalfHash(Slice const& data) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + accountKeylet(AccountID const& account) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + ammKeylet(Asset const& issue1, Asset const& issue2) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + checkKeylet(AccountID const& account, std::uint32_t seq) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType) + const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + didKeylet(AccountID const& account) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + delegateKeylet(AccountID const& account, AccountID const& authorize) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + depositPreauthKeylet(AccountID const& account, AccountID const& authorize) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + escrowKeylet(AccountID const& account, std::uint32_t seq) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + trustLineKeylet(AccountID const& account1, AccountID const& account2, Currency const& currency) + const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + mptokenIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + mptokenKeylet(MPTID const& mptid, AccountID const& holder) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + nftokenOfferKeylet(AccountID const& account, std::uint32_t seq) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + offerKeylet(AccountID const& account, std::uint32_t seq) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + oracleKeylet(AccountID const& account, std::uint32_t docId) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + paychannelKeylet(AccountID const& account, AccountID const& destination, std::uint32_t seq) + const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + signerListKeylet(AccountID const& account) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + ticketKeylet(AccountID const& account, std::uint32_t seq) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + vaultKeylet(AccountID const& account, std::uint32_t seq) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getNFT(AccountID const& account, uint256 const& nftId) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getNFTIssuer(uint256 const& nftId) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getNFTTaxon(uint256 const& nftId) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getNFTFlags(uint256 const& nftId) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getNFTTransferFee(uint256 const& nftId) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + getNFTSequence(uint256 const& nftId) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + trace(std::string_view const& msg, Slice const& data, bool asHex) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + traceNum(std::string_view const& msg, int64_t data) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + traceAccount(std::string_view const& msg, AccountID const& account) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + traceFloat(std::string_view const& msg, Slice const& data) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + traceAmount(std::string_view const& msg, STAmount const& amount) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + floatFromInt(int64_t x, int32_t mode) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + floatFromUint(uint64_t x, int32_t mode) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + floatFromSTAmount(STAmount const& x, int32_t mode) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + floatFromSTNumber(STNumber const& x, int32_t mode) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + floatToInt(Slice const& x, int32_t mode) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + floatToMantExp(Slice const& x) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + floatFromMantExp(int64_t mantissa, int32_t exponent, int32_t mode) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + floatCompare(Slice const& x, Slice const& y) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + floatAdd(Slice const& x, Slice const& y, int32_t mode) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + floatSubtract(Slice const& x, Slice const& y, int32_t mode) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + floatMultiply(Slice const& x, Slice const& y, int32_t mode) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + floatDivide(Slice const& x, Slice const& y, int32_t mode) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + floatRoot(Slice const& x, int32_t n, int32_t mode) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] [[nodiscard]] virtual std::expected + floatPower(Slice const& x, int32_t n, int32_t mode) const + { + return std::unexpected(HostFunctionError::Unimplemented); + } + + virtual ~HostFunctions() = default; + // LCOV_EXCL_STOP +}; + +using HFRef = std::reference_wrapper; + +} // namespace xrpl diff --git a/include/xrpl/tx/wasm/HostFuncImpl.h b/include/xrpl/tx/wasm/HostFuncImpl.h new file mode 100644 index 0000000000..37ee4af5a8 --- /dev/null +++ b/include/xrpl/tx/wasm/HostFuncImpl.h @@ -0,0 +1,302 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +// Intended to work only through wasm runtime. Don't call them directly, except with unit tests +class WasmHostFunctionsImpl : public HostFunctions +{ + ApplyContext& ctx_; + + Keylet leKey_; + mutable std::optional> currentLedgerObj_; + + static int constexpr maxCache = 256; + std::array, maxCache> cache_; + + std::optional data_; + +public: + std::expected, HostFunctionError> + getCurrentLedgerObj() const + { + if (!currentLedgerObj_) + currentLedgerObj_ = ctx_.view().read(leKey_); + if (*currentLedgerObj_) + return *currentLedgerObj_; + return std::unexpected(HostFunctionError::LedgerObjNotFound); + } + + std::expected + normalizeCacheIndex(int32_t cacheIdx) const + { + --cacheIdx; + if (cacheIdx < 0 || cacheIdx >= maxCache) + return std::unexpected(HostFunctionError::SlotOutRange); + if (!cache_[cacheIdx]) + return std::unexpected(HostFunctionError::EmptySlot); + return cacheIdx; + } + + template + void + log(std::string_view const& msg, F&& dataFn) const + { +#ifdef DEBUG_OUTPUT + auto& j = std::cerr; +#else + if (!getJournal().active(beast::Severity::Trace)) + return; + auto j = getJournal().trace(); +#endif + j << "WasmTrace[" << toShortString(leKey_.key) << "]: " << msg << " " << dataFn(); + +#ifdef DEBUG_OUTPUT + j << std::endl; +#endif + } + +public: + WasmHostFunctionsImpl(ApplyContext& ct, Keylet const& leKey) + : HostFunctions(ct.journal), ctx_(ct), leKey_(leKey) + { + } + + bool + checkSelf() const override + { + return !currentLedgerObj_ && !data_ && + std::ranges::none_of(cache_, [](auto const& p) { return !!p; }); + } + + std::optional const& + getData() const + { + return data_; + } + + std::expected + getLedgerSqn() const override; + + std::expected + getParentLedgerTime() const override; + + std::expected + getParentLedgerHash() const override; + + std::expected + getBaseFee() const override; + + std::expected + isAmendmentEnabled(uint256 const& amendmentId) const override; + + std::expected + isAmendmentEnabled(std::string_view const& amendmentName) const override; + + std::expected + cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) override; + + std::expected + getTxField(SField const& fname) const override; + + std::expected + getCurrentLedgerObjField(SField const& fname) const override; + + std::expected + getLedgerObjField(int32_t cacheIdx, SField const& fname) const override; + + std::expected + getTxNestedField(FieldLocator const& locator) const override; + + std::expected + getCurrentLedgerObjNestedField(FieldLocator const& locator) const override; + + std::expected + getLedgerObjNestedField(int32_t cacheIdx, FieldLocator const& locator) const override; + + std::expected + getTxArrayLen(SField const& fname) const override; + + std::expected + getCurrentLedgerObjArrayLen(SField const& fname) const override; + + std::expected + getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const override; + + std::expected + getTxNestedArrayLen(FieldLocator const& locator) const override; + + std::expected + getCurrentLedgerObjNestedArrayLen(FieldLocator const& locator) const override; + + std::expected + getLedgerObjNestedArrayLen(int32_t cacheIdx, FieldLocator const& locator) const override; + + std::expected + updateData(Slice const& data) override; + + std::expected + checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey) + const override; + + std::expected + computeSha512HalfHash(Slice const& data) const override; + + std::expected + accountKeylet(AccountID const& account) const override; + + std::expected + ammKeylet(Asset const& issue1, Asset const& issue2) const override; + + std::expected + checkKeylet(AccountID const& account, std::uint32_t seq) const override; + + std::expected + credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType) + const override; + + std::expected + didKeylet(AccountID const& account) const override; + + std::expected + delegateKeylet(AccountID const& account, AccountID const& authorize) const override; + + std::expected + depositPreauthKeylet(AccountID const& account, AccountID const& authorize) const override; + + std::expected + escrowKeylet(AccountID const& account, std::uint32_t seq) const override; + + std::expected + trustLineKeylet(AccountID const& account1, AccountID const& account2, Currency const& currency) + const override; + + std::expected + mptokenIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const override; + + std::expected + mptokenKeylet(MPTID const& mptid, AccountID const& holder) const override; + + std::expected + nftokenOfferKeylet(AccountID const& account, std::uint32_t seq) const override; + + std::expected + offerKeylet(AccountID const& account, std::uint32_t seq) const override; + + std::expected + oracleKeylet(AccountID const& account, std::uint32_t docId) const override; + + std::expected + paychannelKeylet(AccountID const& account, AccountID const& destination, std::uint32_t seq) + const override; + + std::expected + permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const override; + + std::expected + signerListKeylet(AccountID const& account) const override; + + std::expected + ticketKeylet(AccountID const& account, std::uint32_t seq) const override; + + std::expected + vaultKeylet(AccountID const& account, std::uint32_t seq) const override; + + std::expected + getNFT(AccountID const& account, uint256 const& nftId) const override; + + std::expected + getNFTIssuer(uint256 const& nftId) const override; + + std::expected + getNFTTaxon(uint256 const& nftId) const override; + + std::expected + getNFTFlags(uint256 const& nftId) const override; + + std::expected + getNFTTransferFee(uint256 const& nftId) const override; + + std::expected + getNFTSequence(uint256 const& nftId) const override; + + std::expected + trace(std::string_view const& msg, Slice const& data, bool asHex) const override; + + std::expected + traceNum(std::string_view const& msg, int64_t data) const override; + + std::expected + traceAccount(std::string_view const& msg, AccountID const& account) const override; + + std::expected + traceFloat(std::string_view const& msg, Slice const& data) const override; + + std::expected + traceAmount(std::string_view const& msg, STAmount const& amount) const override; + + std::expected + floatFromInt(int64_t x, int32_t mode) const override; + + std::expected + floatFromUint(uint64_t x, int32_t mode) const override; + + std::expected + floatFromSTAmount(STAmount const& x, int32_t mode) const override; + + std::expected + floatFromSTNumber(STNumber const& x, int32_t mode) const override; + + std::expected + floatToInt(Slice const& x, int32_t mode) const override; + + std::expected + floatToMantExp(Slice const& x) const override; + + std::expected + floatFromMantExp(int64_t mantissa, int32_t exponent, int32_t mode) const override; + + std::expected + floatCompare(Slice const& x, Slice const& y) const override; + + std::expected + floatAdd(Slice const& x, Slice const& y, int32_t mode) const override; + + std::expected + floatSubtract(Slice const& x, Slice const& y, int32_t mode) const override; + + std::expected + floatMultiply(Slice const& x, Slice const& y, int32_t mode) const override; + + std::expected + floatDivide(Slice const& x, Slice const& y, int32_t mode) const override; + + std::expected + floatRoot(Slice const& x, int32_t n, int32_t mode) const override; + + std::expected + floatPower(Slice const& x, int32_t n, int32_t mode) const override; +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/wasm/HostFuncWrapper.h b/include/xrpl/tx/wasm/HostFuncWrapper.h new file mode 100644 index 0000000000..1d04d7202a --- /dev/null +++ b/include/xrpl/tx/wasm/HostFuncWrapper.h @@ -0,0 +1,254 @@ +#pragma once + +#include + +#include + +#include + +namespace xrpl { + +#define WASM_CB_PARAMS_LIST void *env, wasm_val_vec_t const *params, wasm_val_vec_t *results +#define WASM_SECONDARY_CB_PARAMS_LIST \ + HostFunctions &hf, wasm_val_vec_t const *params, wasm_val_vec_t *results + +wasm_trap_t* HostFuncMain_wrap(WASM_CB_PARAMS_LIST); + +using getLedgerSqn_proto = int32_t(uint8_t*, int32_t); +wasm_trap_t* getLedgerSqn_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getParentLedgerTime_proto = int32_t(uint8_t*, int32_t); +wasm_trap_t* getParentLedgerTime_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getParentLedgerHash_proto = int32_t(uint8_t*, int32_t); +wasm_trap_t* getParentLedgerHash_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getBaseFee_proto = int32_t(uint8_t*, int32_t); +wasm_trap_t* getBaseFee_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using isAmendmentEnabled_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* isAmendmentEnabled_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using cacheLedgerObj_proto = int32_t(uint8_t const*, int32_t, int32_t); +wasm_trap_t* cacheLedgerObj_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getTxField_proto = int32_t(int32_t, uint8_t*, int32_t); +wasm_trap_t* getTxField_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getCurrentLedgerObjField_proto = int32_t(int32_t, uint8_t*, int32_t); +wasm_trap_t* getCurrentLedgerObjField_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getLedgerObjField_proto = int32_t(int32_t, int32_t, uint8_t*, int32_t); +wasm_trap_t* getLedgerObjField_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getTxNestedField_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* getTxNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getCurrentLedgerObjNestedField_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* getCurrentLedgerObjNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getLedgerObjNestedField_proto = int32_t(int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* getLedgerObjNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getTxArrayLen_proto = int32_t(int32_t); +wasm_trap_t* getTxArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getCurrentLedgerObjArrayLen_proto = int32_t(int32_t); +wasm_trap_t* getCurrentLedgerObjArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getLedgerObjArrayLen_proto = int32_t(int32_t, int32_t); +wasm_trap_t* getLedgerObjArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getTxNestedArrayLen_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* getTxNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getCurrentLedgerObjNestedArrayLen_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* getCurrentLedgerObjNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getLedgerObjNestedArrayLen_proto = int32_t(int32_t, uint8_t const*, int32_t); +wasm_trap_t* getLedgerObjNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using updateData_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* updateData_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using checkSignature_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t const*, int32_t); +wasm_trap_t* checkSignature_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using computeSha512HalfHash_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* computeSha512HalfHash_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using accountKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* accountKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using ammKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* ammKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using checkKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* checkKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using credentialKeylet_proto = int32_t( + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* credentialKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using delegateKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* delegateKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using depositPreauthKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* depositPreauthKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using didKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* didKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using escrowKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* escrowKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using trustLineKeylet_proto = int32_t( + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* trustLineKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using mptokenIssuanceKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* mptokenIssuanceKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using mptokenKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* mptokenKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using nftokenOfferKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* nftokenOfferKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using offerKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* offerKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using oracleKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* oracleKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using paychannelKeylet_proto = int32_t( + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* paychannelKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using permissionedDomainKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* permissionedDomainKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using signerListKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* signerListKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using ticketKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* ticketKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using vaultKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* vaultKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getNFT_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* getNFT_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getNFTIssuer_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* getNFTIssuer_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getNFTTaxon_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* getNFTTaxon_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getNFTFlags_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* getNFTFlags_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getNFTTransferFee_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* getNFTTransferFee_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using getNFTSequence_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* getNFTSequence_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using trace_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, int32_t); +wasm_trap_t* trace_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using traceNum_proto = int32_t(uint8_t const*, int32_t, int64_t); +wasm_trap_t* traceNum_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using traceAccount_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t); +wasm_trap_t* traceAccount_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using traceFloat_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t); +wasm_trap_t* traceFloat_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using traceAmount_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t); +wasm_trap_t* traceAmount_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using floatFromInt_proto = int32_t(int64_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* floatFromInt_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using floatFromUint_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* floatFromUint_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using floatFromSTAmount_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* floatFromSTAmount_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using floatFromSTNumber_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* floatFromSTNumber_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using floatToInt_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* floatToInt_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using floatToMantExp_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, uint8_t*, int32_t); +wasm_trap_t* floatToMantExp_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using floatFromMantExp_proto = int32_t(int64_t, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* floatFromMantExp_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using floatCompare_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t); +wasm_trap_t* floatCompare_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using floatAdd_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* floatAdd_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using floatSubtract_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* floatSubtract_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using floatMultiply_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* floatMultiply_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using floatDivide_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* floatDivide_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using floatRoot_proto = int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* floatRoot_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +using floatPower_proto = int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* floatPower_wrap(WASM_SECONDARY_CB_PARAMS_LIST); + +} // namespace xrpl diff --git a/include/xrpl/tx/wasm/README.md b/include/xrpl/tx/wasm/README.md new file mode 100644 index 0000000000..04958b663a --- /dev/null +++ b/include/xrpl/tx/wasm/README.md @@ -0,0 +1,189 @@ +# WASM Module for Programmable Escrows + +This module provides WebAssembly (WASM) execution capabilities for programmable +escrows on the XRP Ledger. When an escrow is finished, the WASM code runs to +determine whether the escrow conditions are met, enabling custom programmable +logic for escrow release conditions. + +For the full specification, see +[XLS-0102: WASM VM](https://xls.xrpl.org/xls/XLS-0102-wasm-vm.html). + +## Architecture + +The module follows a layered architecture: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ WasmEngine (WasmVM.h) │ +│ runEscrowWasm(), preflightEscrowWasm() │ +│ Host function registration │ +├─────────────────────────────────────────────────────────────┤ +│ WasmiEngine (WasmiVM.h) │ +│ Low-level wasmi interpreter integration │ +├─────────────────────────────────────────────────────────────┤ +│ HostFuncWrapper │ HostFuncImpl │ +│ C-style WASM bridges │ C++ implementations │ +├─────────────────────────────────────────────────────────────┤ +│ HostFunc (Interface) │ +│ Abstract base class for host functions │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Key Components + +- **`WasmVM.h` / `detail/WasmVM.cpp`** - High-level facade providing: + - `WasmEngine` singleton that wraps the underlying WASM interpreter + - `runEscrowWasm()` - Execute WASM code for escrow finish + - `preflightEscrowWasm()` - Validate WASM code during preflight + - `createWasmImport()` - Register all host functions + +- **`WasmiVM.h` / `detail/WasmiVM.cpp`** - Low-level integration with the + [wasmi](https://github.com/wasmi-labs/wasmi) WebAssembly interpreter: + - `WasmiEngine` - Manages WASM modules, instances, and execution + - Memory management and gas metering + - Function invocation and result handling + +- **`HostFunc.h`** - Abstract `HostFunctions` base class defining the interface + for all callable host functions. Each method returns + `std::expected`. + +- **`HostFuncImpl.h` / `detail/HostFuncImpl*.cpp`** - Concrete + `WasmHostFunctionsImpl` class that implements host functions with access to + `ApplyContext` for ledger state queries. Implementation split across files: + - `HostFuncImpl.cpp` - Core utilities (updateData, checkSignature, etc.) + - `HostFuncImplFloat.cpp` - Float/number arithmetic operations + - `HostFuncImplGetter.cpp` - Field access (transaction, ledger objects) + - `HostFuncImplKeylet.cpp` - Keylet construction functions + - `HostFuncImplLedgerHeader.cpp` - Ledger header info access + - `HostFuncImplNFT.cpp` - NFT-related queries + - `HostFuncImplTrace.cpp` - Debugging/tracing functions + +- **`HostFuncWrapper.h` / `detail/HostFuncWrapper.cpp`** - C-style wrapper + functions that bridge WASM calls to C++ `HostFunctions` methods. Each host + function has: + - A `_proto` type alias defining the function signature + - A `_wrap` function that extracts parameters and calls the implementation + +- **`ParamsHelper.h`** - Utilities for WASM parameter handling: + - `WASM_IMPORT_FUNC` / `WASM_IMPORT_FUNC2` macros for registration + - `wasmParams()` helper for building parameter vectors + - Type conversion between WASM and C++ types + +## Host Functions + +Host functions allow WASM code to interact with the XRP Ledger. They are +organized into categories: + +- **Ledger Information** - Access ledger sequence, timestamps, hashes, fees +- **Transaction & Ledger Object Access** - Read fields from the transaction + and ledger objects (including the current escrow object) +- **Keylet Construction** - Build keylets to look up various ledger object types +- **Cryptography** - Signature verification and hashing +- **Float Arithmetic** - Mathematical operations for amount calculations +- **NFT Operations** - Query NFT properties +- **Tracing/Debugging** - Log messages for debugging + +For the complete list of available host functions, their WASM names, and gas +costs, see the [XLS-0102 specification](https://xls.xrpl.org/xls/XLS-0102-wasm-vm.html) +or `detail/WasmVM.cpp` where they are registered via `WASM_IMPORT_FUNC2` macros. +For method signatures, see `HostFunc.h`. + +## Gas Model + +Each host function has an associated gas cost. The gas cost is specified when +registering the function in `detail/WasmVM.cpp`: + +```cpp +WASM_IMPORT_FUNC2(i, getLedgerSqn, "get_ledger_sqn", hfs, 60); +// ^^ gas cost +``` + +WASM execution is metered, and if the gas limit is exceeded, execution fails. + +## Entry Point + +The WASM module must export a function with the name defined by +`escrowFunctionName` (currently `"escrow_finish"`). This function: + +- Takes no parameters (or parameters passed via host function calls) +- Returns an `int32_t`: + - `1` (or positive): Escrow conditions are met, allow finish + - `0` (or negative): Escrow conditions are not met, reject finish + +## Adding a New Host Function + +To add a new host function, follow these steps: + +### 1. Add to HostFunc.h (Base Class) + +Add a virtual method declaration with a default implementation that returns an +error: + +```cpp +virtual std::expected +myNewFunction(ParamType1 param1, ParamType2 param2) +{ + return std::unexpected(HostFunctionError::INTERNAL); +} +``` + +### 2. Add to HostFuncImpl.h (Declaration) + +Add the method override declaration in `WasmHostFunctionsImpl`: + +```cpp +std::expected +myNewFunction(ParamType1 param1, ParamType2 param2) override; +``` + +### 3. Implement in detail/HostFuncImpl\*.cpp + +Add the implementation in the appropriate file: + +```cpp +std::expected +WasmHostFunctionsImpl::myNewFunction(ParamType1 param1, ParamType2 param2) +{ + // Implementation using ctx (ApplyContext) for ledger access + return result; +} +``` + +### 4. Add Wrapper to HostFuncWrapper.h + +Add the prototype and wrapper declaration: + +```cpp +using myNewFunction_proto = int32_t(uint8_t const*, int32_t, ...); +wasm_trap_t* +myNewFunction_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); +``` + +### 5. Implement Wrapper in detail/HostFuncWrapper.cpp + +Implement the C-style wrapper that bridges WASM to C++: + +```cpp +wasm_trap_t* +myNewFunction_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + // Extract parameters from params + // Call hfs->myNewFunction(...) + // Set results and return +} +``` + +### 6. Register in WasmVM.cpp + +Add the function registration in `setCommonHostFunctions()` or +`createWasmImport()`: + +```cpp +WASM_IMPORT_FUNC2(i, myNewFunction, "my_new_function", hfs, 100); +// ^^ WASM name ^^ gas cost +``` + +> [!IMPORTANT] +> New host functions MUST be amendment-gated in `WasmVM.cpp`. +> Wrap the registration in an amendment check to ensure the function is only +> available after the corresponding amendment is enabled on the network. diff --git a/include/xrpl/tx/wasm/WasmCommon.h b/include/xrpl/tx/wasm/WasmCommon.h new file mode 100644 index 0000000000..3e55777b7c --- /dev/null +++ b/include/xrpl/tx/wasm/WasmCommon.h @@ -0,0 +1,241 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +using Bytes = std::vector; +using Hash = xrpl::uint256; +using FloatPair = std::pair; + +// Error signals that cross the wasm boundary as trap messages (the C API has no +// trap code). WasmiEngine::call maps them to TER: hfErrInternal -> tecINTERNAL, +// hfErrOutOfGas / wasmi's OutOfFuel -> tecOUT_OF_GAS, anything else -> +// tecFAILED_PROCESSING. +// +// Matched as substrings, not by equality: the C API returns the Rust Debug form +// of the error, e.g. `Error { kind: Message("HfInternal") }` or +// `Error { kind: TrapCode(OutOfFuel) }`. +std::string_view inline constexpr hfErrInternal = "HfInternal"; +std::string_view inline constexpr hfErrOutOfGas = "HfOutOfGas"; +std::string_view inline constexpr wasmiTrapOutOfFuel = "OutOfFuel"; + +enum class HostFunctionError : int32_t { + Unimplemented = -1, + FieldNotFound = -2, + BufferTooSmall = -3, + NoArray = -4, + NotLeafField = -5, + LocatorMalformed = -6, + SlotOutRange = -7, + SlotsFull = -8, + EmptySlot = -9, + LedgerObjNotFound = -10, + OutOfTransferLimit = -11, + DataFieldTooLarge = -12, + PointerOutOfBounds = -13, + NoMemExported = -14, + InvalidParams = -15, + InvalidAccount = -16, + InvalidField = -17, + IndexOutOfBounds = -18, + FloatInputMalformed = -19, + FloatComputationError = -20, +}; + +enum class WasmTypes { WtI32, WtI64 }; + +struct Wmem +{ + std::uint8_t* p = nullptr; + std::size_t s = 0; + + Wmem() = default; + Wmem(void* ptr, std::size_t size) : p(reinterpret_cast(ptr)), s(size) + { + } +}; + +template +struct WasmResult +{ + T result; + int64_t cost; +}; +using EscrowResult = WasmResult; + +// Engine error when wasm does not run to completion. `cost` is the gas consumed +// when meaningful (tecOUT_OF_GAS / tecFAILED_PROCESSING; caller writes it to tx +// metadata); std::nullopt for tecINTERNAL and malformed input (no gas reported). +struct WasmTER +{ + TER ter; + std::optional cost; +}; + +class FieldLocator +{ + int32_t const* ptr_ = nullptr; + uint32_t size_ = 0; + std::vector buf_; + +public: + FieldLocator(std::vector&& buf) + : ptr_(&buf[0]), size_(buf.size()), buf_(std::move(buf)) + { + } + + FieldLocator(int32_t const* ptr, uint32_t const size) : ptr_(ptr), size_(size) + { + } + + FieldLocator(FieldLocator const&) = delete; + FieldLocator& + operator=(FieldLocator const&) = delete; + FieldLocator(FieldLocator&&) = default; + FieldLocator& + operator=(FieldLocator&&) = default; + + int32_t + operator[](unsigned i) const + { + if (i >= size_) + Throw("index out of bounds"); + return ptr_[i]; + } + + [[nodiscard]] uint32_t + size() const + { + return size_; + } + + [[nodiscard]] int32_t const* + data() const + { + return ptr_; + } + + [[nodiscard]] bool + empty() const + { + return size_ == 0; + } +}; + +class WasmRuntimeWrapper +{ +public: + virtual ~WasmRuntimeWrapper() = default; + + virtual Wmem + getMem() = 0; + + virtual std::int64_t + getGas() = 0; + + virtual std::int64_t + setGas(std::int64_t gas) = 0; + + virtual std::int64_t + getTransferLimit() = 0; + + virtual std::int64_t + setTransferLimit(std::int64_t transferLimit) = 0; +}; +using RTOptRef = std::optional>; + +struct WasmParam +{ + // We are not supporting float/double + + WasmTypes type = WasmTypes::WtI32; + union + { + std::int32_t i32; + std::int64_t i64 = 0; + } of; +}; + +template +inline void +wasmParamsHlp(std::vector& v, std::int32_t p, Types&&... args) +{ + v.push_back({.type = WasmTypes::WtI32, .of = {.i32 = p}}); + wasmParamsHlp(v, std::forward(args)...); +} + +template +inline void +wasmParamsHlp(std::vector& v, std::int64_t p, Types&&... args) +{ + v.push_back({.type = WasmTypes::WtI64, .of = {.i64 = p}}); + wasmParamsHlp(v, std::forward(args)...); +} + +inline void +wasmParamsHlp(std::vector& v) +{ +} + +template +inline std::vector +wasmParams(Types&&... args) +{ + std::vector v; + v.reserve(sizeof...(args)); + wasmParamsHlp(v, std::forward(args)...); + return v; +} + +template +constexpr T +adjustWasmEndianessHlp(T x) +{ + static_assert(std::is_integral_v, "Only integral types"); + if constexpr (Size > 1) + { + using U = std::make_unsigned_t; + U u = static_cast(x); + U const low = (u & 0xFF) << ((Size - 1) << 3); + u = adjustWasmEndianessHlp(u >> 8); + return static_cast(low | u); + } + + return x; +} + +template +constexpr T +adjustWasmEndianess(T x) +{ + // LCOV_EXCL_START + static_assert(std::is_integral_v, "Only integral types"); + if constexpr (std::endian::native == std::endian::big) + { + return adjustWasmEndianessHlp(x); + } + return x; + // LCOV_EXCL_STOP +} + +constexpr int32_t +hfErrorToInt(HostFunctionError e) +{ + return static_cast(e); +} + +} // namespace xrpl diff --git a/include/xrpl/tx/wasm/WasmImportsHelper.h b/include/xrpl/tx/wasm/WasmImportsHelper.h new file mode 100644 index 0000000000..0c31e969c1 --- /dev/null +++ b/include/xrpl/tx/wasm/WasmImportsHelper.h @@ -0,0 +1,126 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace bft = boost::function_types; + +namespace xrpl { + +using wasmSecondaryCbFuncType = + wasm_trap_t*(HostFunctions&, wasm_val_vec_t const*, wasm_val_vec_t*); + +struct WasmImportFunc +{ + std::string_view name; + std::optional result; + std::vector params; + + wasmSecondaryCbFuncType* wrap = nullptr; + uint32_t gas = 0; +}; + +using WasmUserData = std::pair; +// string - import function name +using ImportVec = std::unordered_map; + +template +void +WasmImpArgs(WasmImportFunc& e) +{ + if constexpr (N < C) + { + using at = boost::mpl::at_c::type; + if constexpr (std::is_pointer_v || std::is_same_v) + { + e.params.push_back(WasmTypes::WtI32); + } + else if constexpr (std::is_same_v) + { + e.params.push_back(WasmTypes::WtI64); + } + else + { + static_assert(std::is_pointer_v, "Unsupported argument type"); + } + + return WasmImpArgs(e); + } +} + +template +inline constexpr bool wasmDependentFalse = false; + +template +void +WasmImpRet(WasmImportFunc& e) +{ + if constexpr (std::is_pointer_v || std::is_same_v) + { + e.result = WasmTypes::WtI32; + } + else if constexpr (std::is_same_v) + { + e.result = WasmTypes::WtI64; + } + else if constexpr (std::is_void_v) + { + e.result.reset(); + } + else + { + static_assert(wasmDependentFalse, "Unsupported return type"); + } +} + +template +void +WasmImpFuncHelper(WasmImportFunc& e) +{ + using rt = bft::result_type::type; + using pt = bft::parameter_types::type; + // typename boost::mpl::at_c::type + + WasmImpRet(e); + WasmImpArgs<0, bft::function_arity::value, pt>(e); + // WasmImpWrap(e, std::forward(f)); +} + +// imp_name - string literal, must have static lifetime +template +void +WasmImpFunc( + ImportVec& v, + std::string_view impName, + wasmSecondaryCbFuncType* fWrap, + HostFunctions& hf, + uint32_t gas = 0) +{ + WasmImportFunc e; + e.name = impName; + e.wrap = fWrap; + e.gas = gas; + WasmImpFuncHelper(e); + v.emplace(impName, std::make_pair(HFRef(hf), std::move(e))); +} + +#define WASM_IMPORT_FUNC(v, f, ...) WasmImpFunc(v, #f, &f##_wrap, ##__VA_ARGS__) + +// n - string literal name, must have static lifetime +#define WASM_IMPORT_FUNC2(v, f, n, ...) WasmImpFunc(v, n, &f##_wrap, ##__VA_ARGS__) + +} // namespace xrpl diff --git a/include/xrpl/tx/wasm/WasmVM.h b/include/xrpl/tx/wasm/WasmVM.h new file mode 100644 index 0000000000..e20488de00 --- /dev/null +++ b/include/xrpl/tx/wasm/WasmVM.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +std::string_view inline constexpr wEnv = "env"; +std::string_view inline constexpr wHostLib = "host_lib"; +std::string_view inline constexpr wMem = "memory"; +std::string_view inline constexpr wStore = "store"; +std::string_view inline constexpr wLoad = "load"; +std::string_view inline constexpr wSize = "size"; +std::string_view inline constexpr wAlloc = "allocate"; +std::string_view inline constexpr wDealloc = "deallocate"; +std::string_view inline constexpr wProcExit = "proc_exit"; + +std::string_view inline constexpr escrowFunctionName = "escrow_finish"; + +uint32_t inline constexpr maxPages = 128; // 8MB = 64KB*128 + +class WasmiEngine; + +class WasmEngine +{ + std::unique_ptr const impl_; + + WasmEngine(); + +public: + WasmEngine(WasmEngine const&) = delete; + WasmEngine(WasmEngine&&) = delete; + WasmEngine& + operator=(WasmEngine const&) = delete; + WasmEngine& + operator=(WasmEngine&&) = delete; + + static WasmEngine& + instance(); + + std::expected, WasmTER> + run(Bytes const& wasmCode, + HostFunctions& hfs, + int64_t gasLimit, + std::string_view funcName = {}, + std::vector const& params = {}, + ImportVec const& imports = {}, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}); + + NotTEC + check( + Bytes const& wasmCode, + HostFunctions& hfs, + std::string_view funcName, + std::vector const& params = {}, + ImportVec const& imports = {}, + beast::Journal j = beast::Journal{beast::Journal::getNullSink()}); + + // Host functions helper functionality + void* + newTrap(std::string const& txt = std::string()); + + [[nodiscard]] beast::Journal + getJournal() const; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +ImportVec +createWasmImport(HostFunctions& hfs); + +std::expected +runEscrowWasm( + Bytes const& wasmCode, + HostFunctions& hfs, + int64_t gasLimit, + std::string_view funcName = escrowFunctionName, + std::vector const& params = {}); + +NotTEC +preflightEscrowWasm( + Bytes const& wasmCode, + HostFunctions& hfs, + std::string_view funcName = escrowFunctionName, + std::vector const& params = {}); + +} // namespace xrpl diff --git a/include/xrpl/tx/wasm/WasmiVM.h b/include/xrpl/tx/wasm/WasmiVM.h new file mode 100644 index 0000000000..5a72cd35f6 --- /dev/null +++ b/include/xrpl/tx/wasm/WasmiVM.h @@ -0,0 +1,462 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +template +class WasmVec +{ + using TD = std::remove_pointer_t; + T vec_; + +public: + WasmVec(size_t s = 0) : vec_ WASM_EMPTY_VEC + { + if (s > 0) + Create(&vec_, s); // zeroes memory + } + + ~WasmVec() + { + clear(); + } + + WasmVec(WasmVec const&) = delete; + WasmVec& + operator=(WasmVec const&) = delete; + + WasmVec(WasmVec&& other) noexcept : vec_ WASM_EMPTY_VEC + { + *this = std::move(other); + } + + WasmVec& + operator=(WasmVec&& other) noexcept + { + if (this != &other) + { + clear(); + vec_ = other.vec_; + other.vec_ = WASM_EMPTY_VEC; + } + return *this; + } + + void + clear() + { + Destroy(&vec_); // call destructor for every elements too + vec_ = WASM_EMPTY_VEC; + } + + T + release() + { + T result = vec_; + vec_ = WASM_EMPTY_VEC; + return result; + } + + T* + get() + { + return &vec_; + } + + [[nodiscard]] T const* + get() const + { + return &vec_; + } + + TD& + operator[](size_t i) + { + if (i >= vec_.size) + Throw("Out of bound"); + return vec_.data[i]; + } + + TD const& + operator[](size_t i) const + { + if (i >= vec_.size) + Throw("Out of bound"); + return vec_.data[i]; + } + + [[nodiscard]] size_t + size() const + { + return vec_.size; + } + + [[nodiscard]] bool + empty() const + { + return vec_.size == 0u; + } +}; + +using WasmValtypeVec = + WasmVec; +using WasmValVec = WasmVec; +using WasmExternVec = + WasmVec; +using WasmExporttypeVec = WasmVec< + wasm_exporttype_vec_t, + &wasm_exporttype_vec_new_uninitialized, + &wasm_exporttype_vec_delete>; +using WasmImporttypeVec = WasmVec< + wasm_importtype_vec_t, + &wasm_importtype_vec_new_uninitialized, + &wasm_importtype_vec_delete>; + +struct WasmiResult +{ + WasmValVec r; + // Set iff the call trapped. Holds the TER the trap was classified into + // (tecINTERNAL / tecOUT_OF_GAS / tecFAILED_PROCESSING); see + // WasmiEngine::call. std::nullopt means the call returned normally. + std::optional ter; + + WasmiResult(unsigned n = 0) : r(n) + { + } + + WasmiResult() = delete; + ~WasmiResult() = default; + WasmiResult(WasmiResult&& o) = default; + WasmiResult& + operator=(WasmiResult&& o) = default; +}; + +using ModulePtr = std::unique_ptr; +using InstancePtr = std::unique_ptr; +using EnginePtr = std::unique_ptr; +using StorePtr = std::unique_ptr; + +using FuncInfo = std::pair; + +class InstanceWrapper +{ + wasm_store_t* store_ = nullptr; + WasmExternVec exports_; + mutable int memIdx_ = -1; + InstancePtr instance_; + beast::Journal j_ = beast::Journal(beast::Journal::getNullSink()); + std::int64_t transferLimit_ = kWasmTransferLimit; + +private: + static InstancePtr + init( + StorePtr& s, + ModulePtr& m, + WasmExternVec& expt, + WasmExternVec const& imports, + beast::Journal j); + +public: + InstanceWrapper() : instance_(nullptr, &wasm_instance_delete) {}; + + InstanceWrapper(InstanceWrapper const&) = delete; + + InstanceWrapper(InstanceWrapper&& o) : instance_(nullptr, &wasm_instance_delete) + { + *this = std::move(o); // LCOV_EXCL_LINE + } + + InstanceWrapper(StorePtr& s, ModulePtr& m, WasmExternVec const& imports, beast::Journal j) + : store_(s.get()), instance_(init(s, m, exports_, imports, j)), j_(j) + { + } + + InstanceWrapper& + operator=(InstanceWrapper&& o); + + InstanceWrapper& + operator=(InstanceWrapper const&) = delete; + + operator bool() const + { + return static_cast(instance_); + } + + FuncInfo + getFunc(std::string_view funcName, WasmExporttypeVec const& exportTypes) const; + + Wmem + getMem() const; + + std::int64_t + getGas() const; + + std::int64_t + setGas(std::int64_t) const; + + std::int64_t + getTransferLimit() const; + + std::int64_t + setTransferLimit(std::int64_t); +}; + +class ModuleWrapper +{ + ModulePtr module_; + InstanceWrapper instanceWrap_; + WasmExporttypeVec exportTypes_; + beast::Journal j_ = beast::Journal(beast::Journal::getNullSink()); + +public: + // LCOV_EXCL_START + ModuleWrapper() : module_(nullptr, &wasm_module_delete) + { + } + + ModuleWrapper(ModuleWrapper&& o) : module_(nullptr, &wasm_module_delete) + { + *this = std::move(o); + } + // LCOV_EXCL_STOP + + ModuleWrapper& + operator=(ModuleWrapper&& o); + ModuleWrapper( + StorePtr& s, + Bytes const& wasmBin, + bool instantiate, + ImportVec const& imports, + beast::Journal j); + ~ModuleWrapper() = default; + + operator bool() const + { + return instanceWrap_; + } + + FuncInfo + getFunc(std::string_view funcName) const + { + return instanceWrap_.getFunc(funcName, exportTypes_); + } + + wasm_functype_t const* + getFuncType(std::string_view funcName) const; + + Wmem + getMem() const + { + return instanceWrap_.getMem(); + } + + InstanceWrapper& + getInstance(int i = 0) + { + return instanceWrap_; + } + + InstanceWrapper const& + getInstance(int i = 0) const + { + return instanceWrap_; + } + + int + addInstance(StorePtr& s, WasmExternVec const& imports) + { + instanceWrap_ = {s, module_, imports, j_}; + return 0; + } + + std::int64_t + getGas() const + { + return instanceWrap_ ? instanceWrap_.getGas() : -1; + } + +private: + static ModulePtr + init(StorePtr& s, Bytes const& wasmBin, beast::Journal j); + + WasmExternVec + buildImports(StorePtr& s, ImportVec const& imports) const; +}; + +class WasmiEngine +{ + EnginePtr engine_; + StorePtr store_; + std::unique_ptr moduleWrap_; + beast::Journal j_ = beast::Journal(beast::Journal::getNullSink()); + + std::mutex m_; // 1 instance mutex + +public: + WasmiEngine() : engine_(init()), store_(nullptr, &wasm_store_delete) + { + } + + ~WasmiEngine() = default; + + static EnginePtr + init(); + + std::expected, WasmTER> + run(Bytes const& wasmCode, + HostFunctions& hfs, + int64_t gas, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + beast::Journal j); + + NotTEC + check( + Bytes const& wasmCode, + HostFunctions& hfs, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + beast::Journal j); + + [[nodiscard]] std::int64_t + getGas() const + { + return moduleWrap_ ? moduleWrap_->getGas() : -1; // LCOV_EXCL_LINE + } + + // Host functions helper functionality + wasm_trap_t* + newTrap(std::string const& msg); + + // LCOV_EXCL_START + [[nodiscard]] beast::Journal + getJournal() const + { + return j_; + } + // LCOV_EXCL_STOP + +private: + [[nodiscard]] InstanceWrapper& + getRT(int m = 0, int i = 0) const + { + if (!moduleWrap_) + Throw("no module"); + return moduleWrap_->getInstance(i); + } + + [[nodiscard]] Wmem + getMem() const + { + return moduleWrap_ ? moduleWrap_->getMem() : Wmem(); + } + + std::expected, WasmTER> + runHlp( + Bytes const& wasmCode, + HostFunctions& hfs, + int64_t gas, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + beast::Journal j); + + NotTEC + checkHlp( + Bytes const& wasmCode, + HostFunctions& hfs, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + beast::Journal j); + + int + addModule(Bytes const& wasmCode, bool instantiate, ImportVec const& imports, int64_t gas); + void + clearModules(); + + // int addInstance(); + + int32_t + runFunc(std::string_view const funcName, int32_t p); + + int32_t + makeModule(Bytes const& wasmCode, WasmExternVec const& imports = {}); + + [[nodiscard]] FuncInfo + getFunc(std::string_view funcName) const + { + return moduleWrap_->getFunc(funcName); + } + + static std::vector + convertParams(std::vector const& params); + + static int + compareParamTypes(wasm_valtype_vec_t const* ftp, std::vector const& p); + + static void + addParam(std::vector& in, int32_t p); + static void + addParam(std::vector& in, int64_t p); + + template + inline WasmiResult + call(std::string_view func, Types&&... args); + + template + inline WasmiResult + call(FuncInfo const& f, Types&&... args); + + template + inline WasmiResult + call(FuncInfo const& f, std::vector& in); + + template + inline WasmiResult + call(FuncInfo const& f, std::vector& in, std::int32_t p, Types&&... args); + + template + inline WasmiResult + call(FuncInfo const& f, std::vector& in, std::int64_t p, Types&&... args); + + template + inline WasmiResult + call( + FuncInfo const& f, + std::vector& in, + uint8_t const* d, + int32_t sz, + Types&&... args); + + template + inline WasmiResult + call(FuncInfo const& f, std::vector& in, Bytes const& p, Types&&... args); +}; + +} // namespace xrpl diff --git a/src/libxrpl/protocol/TER.cpp b/src/libxrpl/protocol/TER.cpp index c2167d58ce..345edc2ee4 100644 --- a/src/libxrpl/protocol/TER.cpp +++ b/src/libxrpl/protocol/TER.cpp @@ -108,6 +108,7 @@ transResults() MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."), MAKE_ERROR(tecBAD_PROOF, "Proof cannot be verified"), MAKE_ERROR(tecNO_SPONSOR_PERMISSION, "Sponsor has not authorized this transaction."), + MAKE_ERROR(tecOUT_OF_GAS, "The WASM code ran out of gas during execution."), MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."), MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."), @@ -204,6 +205,7 @@ transResults() MAKE_ERROR(temBAD_TRANSFER_FEE, "Malformed: Transfer fee is outside valid range."), MAKE_ERROR(temINVALID_INNER_BATCH, "Malformed: Invalid inner batch transaction."), MAKE_ERROR(temBAD_CIPHERTEXT, "Malformed: Invalid ciphertext."), + MAKE_ERROR(temBAD_WASM, "Malformed: Provided WASM code is invalid."), MAKE_ERROR(terRETRY, "Retry transaction."), MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."), diff --git a/src/libxrpl/tx/wasm/HostFuncImpl.cpp b/src/libxrpl/tx/wasm/HostFuncImpl.cpp new file mode 100644 index 0000000000..2067062deb --- /dev/null +++ b/src/libxrpl/tx/wasm/HostFuncImpl.cpp @@ -0,0 +1,52 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl { + +// ========================================================= +// SECTION: WRITE FUNCTION +// ========================================================= + +std::expected +WasmHostFunctionsImpl::updateData(Slice const& data) +{ + if (data.size() > kMaxWasmDataLength) + return std::unexpected(HostFunctionError::DataFieldTooLarge); + + data_ = Bytes(data.begin(), data.end()); + return data_->size(); +} + +// ========================================================= +// SECTION: UTILS +// ========================================================= + +std::expected +WasmHostFunctionsImpl::checkSignature( + Slice const& message, + Slice const& signature, + Slice const& pubkey) const +{ + if (!publicKeyType(pubkey)) + return std::unexpected(HostFunctionError::InvalidParams); + + PublicKey const pk(pubkey); + return verify(pk, message, signature); +} + +std::expected +WasmHostFunctionsImpl::computeSha512HalfHash(Slice const& data) const +{ + auto const hash = sha512Half(data); + return hash; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/wasm/HostFuncImplFloat.cpp b/src/libxrpl/tx/wasm/HostFuncImplFloat.cpp new file mode 100644 index 0000000000..2abd73d82c --- /dev/null +++ b/src/libxrpl/tx/wasm/HostFuncImplFloat.cpp @@ -0,0 +1,530 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#ifdef _DEBUG +// #define DEBUG_OUTPUT 1 +#endif + +namespace xrpl { + +namespace wasm_float { + +namespace detail { + +// Decode a serialized STNumber float payload. Returns nullopt if the data is +// not a well-formed encoding. +std::optional +floatDecode(Slice const& data) +{ + static unsigned constexpr encodedFloatSize = 12; + if (data.size() != encodedFloatSize) + return std::nullopt; + try + { + SerialIter it(data); + return STNumber(it, sfNumber).value(); + } + catch (...) + { + return std::nullopt; + } +} + +// Build a Number from a raw mantissa/exponent pair. Returns nullopt if the +// value cannot be represented, e.g. the exponent is out of range. +std::optional +numberFromMantExp(int64_t mantissa, int32_t exponent) +{ + try + { + return Number(mantissa, exponent); + } + catch (...) + { + return std::nullopt; + } +} + +// Serialize a Number to the STNumber float encoding. +std::expected +floatEncode(Number const& n) +{ + Serializer msg; + STNumber(sfNumber, n).add(msg); + auto data = msg.getData(); + +#ifdef DEBUG_OUTPUT + std::cout << "m: " << std::setw(20) << n.mantissa() << ", e: " << std::setw(12) << 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; +#endif + return std::expected(std::move(data)); +} + +struct FloatState +{ + // Set only when the requested mode is valid; sets the rounding mode on + // construction and restores the previous mode on destruction. + std::optional guard; + + explicit FloatState(int32_t mode) + { + if (auto const rm = Number::checkedRoundingMode(mode)) + guard.emplace(*rm); + } + + explicit + operator bool() const + { + return guard.has_value(); + } +}; + +} // namespace detail + +std::string +floatToString(Slice const& data) +{ + // set default mode as we don't expect it will be used here + detail::FloatState const rm(static_cast(Number::RoundingMode::ToNearest)); + auto const num = detail::floatDecode(data); + if (!num) + { + std::string hex; + hex.reserve(data.size() * 2); + boost::algorithm::hex(data.begin(), data.end(), std::back_inserter(hex)); + return "Invalid data: " + hex; + } + return to_string(*num); +} + +std::expected +floatFromIntImpl(int64_t x, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return std::unexpected(HostFunctionError::FloatInputMalformed); + + return detail::floatEncode(Number(x)); + } + // LCOV_EXCL_START + catch (...) + { + return std::unexpected(HostFunctionError::FloatComputationError); + } + // LCOV_EXCL_STOP +} + +std::expected +floatFromUintImpl(uint64_t x, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return std::unexpected(HostFunctionError::FloatInputMalformed); + + return detail::floatEncode(Number(x, 0, Number::Normalized{})); + } + // LCOV_EXCL_START + catch (...) + { + return std::unexpected(HostFunctionError::FloatComputationError); + } + // LCOV_EXCL_STOP +} + +std::expected +floatFromSTAmountImpl(STAmount const& x, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return std::unexpected(HostFunctionError::FloatInputMalformed); + + return detail::floatEncode(static_cast(x)); + } + // LCOV_EXCL_START + catch (...) + { + return std::unexpected(HostFunctionError::FloatComputationError); + } + // LCOV_EXCL_STOP +} + +std::expected +floatFromSTNumberImpl(STNumber const& x, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return std::unexpected(HostFunctionError::FloatInputMalformed); + + return detail::floatEncode(x.value()); + } + // LCOV_EXCL_START + catch (...) + { + return std::unexpected(HostFunctionError::FloatComputationError); + } + // LCOV_EXCL_STOP +} + +std::expected +floatToIntImpl(Slice const& x, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return std::unexpected(HostFunctionError::FloatInputMalformed); + + auto const num = detail::floatDecode(x); + if (!num) + return std::unexpected(HostFunctionError::FloatInputMalformed); // LCOV_EXCL_LINE + return static_cast(*num); + } + // LCOV_EXCL_START + catch (...) + { + return std::unexpected(HostFunctionError::FloatComputationError); + } + // LCOV_EXCL_STOP +} + +std::expected +floatToMantExpImpl(Slice const& x) +{ + try + { + detail::FloatState const rm(static_cast(Number::RoundingMode::ToNearest)); + if (!rm) + return std::unexpected(HostFunctionError::FloatInputMalformed); + + auto const num = detail::floatDecode(x); + if (!num) + return std::unexpected(HostFunctionError::FloatInputMalformed); // LCOV_EXCL_LINE + + return FloatPair(num->mantissa(), num->exponent()); + } + // LCOV_EXCL_START + catch (...) + { + return std::unexpected(HostFunctionError::FloatComputationError); + } + // LCOV_EXCL_STOP +} + +std::expected +floatFromMantExpImpl(int64_t mantissa, int32_t exponent, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return std::unexpected(HostFunctionError::FloatInputMalformed); + auto const num = detail::numberFromMantExp(mantissa, exponent); + if (!num) + return std::unexpected(HostFunctionError::FloatInputMalformed); + return detail::floatEncode(*num); + } + catch (...) + { + return std::unexpected(HostFunctionError::FloatComputationError); + } +} + +std::expected +floatCompareImpl(Slice const& x, Slice const& y) +{ + try + { + // set default mode as we don't expect it will be used here + detail::FloatState const rm(static_cast(Number::RoundingMode::ToNearest)); + + auto const xx = detail::floatDecode(x); + if (!xx) + return std::unexpected(HostFunctionError::FloatInputMalformed); + auto const yy = detail::floatDecode(y); + if (!yy) + return std::unexpected(HostFunctionError::FloatInputMalformed); + if (*xx < *yy) + return 2; + if (*xx == *yy) + return 0; + return 1; + } + // LCOV_EXCL_START + catch (...) + { + return std::unexpected(HostFunctionError::FloatComputationError); + } + // LCOV_EXCL_STOP +} + +std::expected +floatAddImpl(Slice const& x, Slice const& y, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return std::unexpected(HostFunctionError::FloatInputMalformed); + + auto const xx = detail::floatDecode(x); + if (!xx) + return std::unexpected(HostFunctionError::FloatInputMalformed); + auto const yy = detail::floatDecode(y); + if (!yy) + return std::unexpected(HostFunctionError::FloatInputMalformed); + + return detail::floatEncode(*xx + *yy); + } + // LCOV_EXCL_START + catch (...) + { + return std::unexpected(HostFunctionError::FloatComputationError); + } + // LCOV_EXCL_STOP +} + +std::expected +floatSubtractImpl(Slice const& x, Slice const& y, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return std::unexpected(HostFunctionError::FloatInputMalformed); + auto const xx = detail::floatDecode(x); + if (!xx) + return std::unexpected(HostFunctionError::FloatInputMalformed); + auto const yy = detail::floatDecode(y); + if (!yy) + return std::unexpected(HostFunctionError::FloatInputMalformed); + + return detail::floatEncode(*xx - *yy); + } + // LCOV_EXCL_START + catch (...) + { + return std::unexpected(HostFunctionError::FloatComputationError); + } + // LCOV_EXCL_STOP +} + +std::expected +floatMultiplyImpl(Slice const& x, Slice const& y, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return std::unexpected(HostFunctionError::FloatInputMalformed); + auto const xx = detail::floatDecode(x); + if (!xx) + return std::unexpected(HostFunctionError::FloatInputMalformed); + auto const yy = detail::floatDecode(y); + if (!yy) + return std::unexpected(HostFunctionError::FloatInputMalformed); + + return detail::floatEncode(*xx * *yy); + } + // LCOV_EXCL_START + catch (...) + { + return std::unexpected(HostFunctionError::FloatComputationError); + } + // LCOV_EXCL_STOP +} + +std::expected +floatDivideImpl(Slice const& x, Slice const& y, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return std::unexpected(HostFunctionError::FloatInputMalformed); + auto const xx = detail::floatDecode(x); + if (!xx) + return std::unexpected(HostFunctionError::FloatInputMalformed); + auto const yy = detail::floatDecode(y); + if (!yy) + return std::unexpected(HostFunctionError::FloatInputMalformed); + + return detail::floatEncode(*xx / *yy); + } + catch (...) + { + return std::unexpected(HostFunctionError::FloatComputationError); + } +} + +std::expected +floatRootImpl(Slice const& x, int32_t n, int32_t mode) +{ + try + { + if (n < 1) + return std::unexpected(HostFunctionError::FloatInputMalformed); + + detail::FloatState const rm(mode); + if (!rm) + return std::unexpected(HostFunctionError::FloatInputMalformed); + + auto const xx = detail::floatDecode(x); + if (!xx) + return std::unexpected(HostFunctionError::FloatInputMalformed); + + return detail::floatEncode(root(*xx, n)); + } + // LCOV_EXCL_START + catch (...) + { + return std::unexpected(HostFunctionError::FloatComputationError); + } + // LCOV_EXCL_STOP +} + +std::expected +floatPowerImpl(Slice const& x, int32_t n, int32_t mode) +{ + try + { + if ((n < 0) || (n > Number::kMaxExponent)) + return std::unexpected(HostFunctionError::FloatInputMalformed); + + detail::FloatState const rm(mode); + if (!rm) + return std::unexpected(HostFunctionError::FloatInputMalformed); + + auto const xx = detail::floatDecode(x); + if (!xx) + return std::unexpected(HostFunctionError::FloatInputMalformed); + if (*xx == Number() && (n == 0)) + return std::unexpected(HostFunctionError::InvalidParams); + + return detail::floatEncode(power(*xx, n, 1)); + } + // LCOV_EXCL_START + catch (...) + { + return std::unexpected(HostFunctionError::FloatComputationError); + } + // LCOV_EXCL_STOP +} + +} // namespace wasm_float + +// ========================================================= +// ACTUAL HOST FUNCTIONS +// ========================================================= + +std::expected +WasmHostFunctionsImpl::floatFromInt(int64_t x, int32_t mode) const +{ + return wasm_float::floatFromIntImpl(x, mode); +} + +std::expected +WasmHostFunctionsImpl::floatFromUint(uint64_t x, int32_t mode) const +{ + return wasm_float::floatFromUintImpl(x, mode); +} + +std::expected +WasmHostFunctionsImpl::floatFromSTAmount(STAmount const& x, int32_t mode) const +{ + return wasm_float::floatFromSTAmountImpl(x, mode); +} + +std::expected +WasmHostFunctionsImpl::floatFromSTNumber(STNumber const& x, int32_t mode) const +{ + return wasm_float::floatFromSTNumberImpl(x, mode); +} + +std::expected +WasmHostFunctionsImpl::floatToInt(Slice const& x, int32_t mode) const +{ + return wasm_float::floatToIntImpl(x, mode); +} + +std::expected +WasmHostFunctionsImpl::floatToMantExp(Slice const& x) const +{ + return wasm_float::floatToMantExpImpl(x); +} + +std::expected +WasmHostFunctionsImpl::floatFromMantExp(int64_t mantissa, int32_t exponent, int32_t mode) const +{ + return wasm_float::floatFromMantExpImpl(mantissa, exponent, mode); +} + +std::expected +WasmHostFunctionsImpl::floatCompare(Slice const& x, Slice const& y) const +{ + return wasm_float::floatCompareImpl(x, y); +} + +std::expected +WasmHostFunctionsImpl::floatAdd(Slice const& x, Slice const& y, int32_t mode) const +{ + return wasm_float::floatAddImpl(x, y, mode); +} + +std::expected +WasmHostFunctionsImpl::floatSubtract(Slice const& x, Slice const& y, int32_t mode) const +{ + return wasm_float::floatSubtractImpl(x, y, mode); +} + +std::expected +WasmHostFunctionsImpl::floatMultiply(Slice const& x, Slice const& y, int32_t mode) const +{ + return wasm_float::floatMultiplyImpl(x, y, mode); +} + +std::expected +WasmHostFunctionsImpl::floatDivide(Slice const& x, Slice const& y, int32_t mode) const +{ + return wasm_float::floatDivideImpl(x, y, mode); +} + +std::expected +WasmHostFunctionsImpl::floatRoot(Slice const& x, int32_t n, int32_t mode) const +{ + return wasm_float::floatRootImpl(x, n, mode); +} + +std::expected +WasmHostFunctionsImpl::floatPower(Slice const& x, int32_t n, int32_t mode) const +{ + return wasm_float::floatPowerImpl(x, n, mode); +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/wasm/HostFuncImplGetter.cpp b/src/libxrpl/tx/wasm/HostFuncImplGetter.cpp new file mode 100644 index 0000000000..4ae0c72426 --- /dev/null +++ b/src/libxrpl/tx/wasm/HostFuncImplGetter.cpp @@ -0,0 +1,400 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +using FieldValue = std::variant; + +template +Bytes +getIntBytes(STBase const* obj) +{ + static_assert(std::is_integral_v, "Only integral types"); + XRPL_ASSERT(obj, "getIntBytes null pointer"); + + auto const* num(static_cast const*>(obj)); // NOLINT + T const data = adjustWasmEndianess(num->value()); + auto const* b = reinterpret_cast(&data); + return Bytes{b, b + sizeof(T)}; +} + +static std::expected +getAnyFieldData(STBase const* obj) +{ + if (obj == nullptr) + return std::unexpected(HostFunctionError::FieldNotFound); + + auto const stype = obj->getSType(); + switch (stype) + { + // LCOV_EXCL_START + case STI_UNKNOWN: + case STI_NOTPRESENT: + return std::unexpected(HostFunctionError::FieldNotFound); + // LCOV_EXCL_STOP + + case STI_OBJECT: + case STI_ARRAY: + case STI_VECTOR256: + return std::unexpected(HostFunctionError::NotLeafField); + + case STI_ACCOUNT: { + auto const* account(static_cast(obj)); // NOLINT + auto const& data = account->value(); + return Bytes{data.begin(), data.end()}; + } + + case STI_ISSUE: { + auto const* issue(static_cast(obj)); // NOLINT + Asset const& asset(issue->value()); + // XRP and IOU will be processed by serializer + if (asset.holds()) + { + auto const& mptIssue = asset.get(); + auto const& mptID = mptIssue.getMptID(); + return Bytes{mptID.cbegin(), mptID.cend()}; + } + break; // Use serializer + } + + case STI_VL: { + auto const* vl(static_cast(obj)); // NOLINT + auto const& data = vl->value(); + return Bytes{data.begin(), data.end()}; + } + + case STI_UINT16: + return getIntBytes(obj); + + case STI_UINT32: + return getIntBytes(obj); + + // LCOV_EXCL_START + case STI_UINT64: + return getIntBytes(obj); + + case STI_INT32: + return getIntBytes(obj); + + case STI_INT64: + return getIntBytes(obj); + // LCOV_EXCL_STOP + + case STI_UINT256: { + auto const* uint256Obj(static_cast(obj)); // NOLINT + auto const& data = uint256Obj->value(); + return Bytes{data.begin(), data.end()}; + } + + case STI_AMOUNT: + case STI_NUMBER: + default: + break; // Use serializer + } + + Serializer msg; + obj->add(msg); + return msg.getData(); +} + +static std::expected +getAnyFieldData(FieldValue const& variantObj) +{ + if (STBase const* const* obj = std::get_if(&variantObj)) + return getAnyFieldData(*obj); + + if (uint256 const* const* u = std::get_if(&variantObj)) + return Bytes((*u)->begin(), (*u)->end()); + + // Unreachable: the variant only holds the two alternatives above. If not, + // it's an xrpld bug -> tecINTERNAL (thrown, caught by HostFuncMain_wrap). + Throw(std::string(hfErrInternal)); // LCOV_EXCL_LINE +} + +static inline bool +noField(STBase const* field) +{ + return (field == nullptr) || (STI_NOTPRESENT == field->getSType()) || + (STI_UNKNOWN == field->getSType()); +} + +static std::expected +locateField(STObject const& obj, FieldLocator const& locator) +{ + STBase const* field = nullptr; + auto const& knownSFields = SField::getKnownCodeToField(); + + { + int32_t const sfieldCode = adjustWasmEndianess(locator[0]); + auto const it = knownSFields.find(sfieldCode); + if (it == knownSFields.end()) + return std::unexpected(HostFunctionError::InvalidField); + + auto const& fname(*it->second); + field = obj.peekAtPField(fname); + if (noField(field)) + return std::unexpected(HostFunctionError::FieldNotFound); + } + + for (unsigned i = 1; i < locator.size(); ++i) + { + int32_t const sfieldCode = adjustWasmEndianess(locator[i]); + + if (STI_ARRAY == field->getSType()) + { + auto const* arr = static_cast(field); // NOLINT + if (sfieldCode < 0 || std::cmp_greater_equal(sfieldCode, arr->size())) + return std::unexpected(HostFunctionError::IndexOutOfBounds); + field = &(arr->operator[](sfieldCode)); + } + else if (STI_OBJECT == field->getSType()) + { + auto const* o = static_cast(field); // NOLINT + + auto const it = knownSFields.find(sfieldCode); + if (it == knownSFields.end()) + return std::unexpected(HostFunctionError::InvalidField); + + auto const& fname(*it->second); + field = o->peekAtPField(fname); + } + else if (STI_VECTOR256 == field->getSType()) + { + auto const* v = static_cast(field); // NOLINT + if (sfieldCode < 0 || std::cmp_greater_equal(sfieldCode, v->size())) + return std::unexpected(HostFunctionError::IndexOutOfBounds); + return FieldValue(&(v->operator[](sfieldCode))); + } + else // simple field must be the last one + { + return std::unexpected(HostFunctionError::LocatorMalformed); + } + + if (noField(field)) + return std::unexpected(HostFunctionError::FieldNotFound); + } + + return FieldValue(field); +} + +static inline std::expected +getArrayLen(FieldValue const& variantField) +{ + if (STBase const* const* field = std::get_if(&variantField)) + { + if ((*field)->getSType() == STI_VECTOR256) + return static_cast(*field)->size(); // NOLINT + if ((*field)->getSType() == STI_ARRAY) + return static_cast(*field)->size(); // NOLINT + } + // uint256 is not an array so that variant should still return NO_ARRAY + + return std::unexpected(HostFunctionError::NoArray); // LCOV_EXCL_LINE +} + +std::expected +WasmHostFunctionsImpl::cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) +{ + auto const& keylet = keylet::unchecked(objId); + if (cacheIdx < 0 || cacheIdx > maxCache) + return std::unexpected(HostFunctionError::SlotOutRange); + + if (cacheIdx == 0) + { + for (cacheIdx = 0; cacheIdx < maxCache; ++cacheIdx) + { + if (!cache_[cacheIdx]) + break; + } + } + else + { + cacheIdx--; // convert to 0-based index + } + + if (cacheIdx >= maxCache) + return std::unexpected(HostFunctionError::SlotsFull); + + cache_[cacheIdx] = ctx_.view().read(keylet); + if (!cache_[cacheIdx]) + return std::unexpected(HostFunctionError::LedgerObjNotFound); + return cacheIdx + 1; // return 1-based index +} + +// Subsection: top level getters + +std::expected +WasmHostFunctionsImpl::getTxField(SField const& fname) const +{ + return getAnyFieldData(ctx_.tx.peekAtPField(fname)); +} + +std::expected +WasmHostFunctionsImpl::getCurrentLedgerObjField(SField const& fname) const +{ + auto const sle = getCurrentLedgerObj(); + if (!sle.has_value()) + return std::unexpected(sle.error()); + return getAnyFieldData(sle.value()->peekAtPField(fname)); +} + +std::expected +WasmHostFunctionsImpl::getLedgerObjField(int32_t cacheIdx, SField const& fname) const +{ + auto const normalizedIdx = normalizeCacheIndex(cacheIdx); + if (!normalizedIdx.has_value()) + return std::unexpected(normalizedIdx.error()); + return getAnyFieldData(cache_[normalizedIdx.value()]->peekAtPField(fname)); +} + +// Subsection: nested getters + +std::expected +WasmHostFunctionsImpl::getTxNestedField(FieldLocator const& locator) const +{ + auto const r = locateField(ctx_.tx, locator); + if (!r) + return std::unexpected(r.error()); + + return getAnyFieldData(r.value()); +} + +std::expected +WasmHostFunctionsImpl::getCurrentLedgerObjNestedField(FieldLocator const& locator) const +{ + auto const sle = getCurrentLedgerObj(); + if (!sle.has_value()) + return std::unexpected(sle.error()); + + auto const r = locateField(*sle.value(), locator); + if (!r) + return std::unexpected(r.error()); + + return getAnyFieldData(r.value()); +} + +std::expected +WasmHostFunctionsImpl::getLedgerObjNestedField(int32_t cacheIdx, FieldLocator const& locator) const +{ + auto const normalizedIdx = normalizeCacheIndex(cacheIdx); + if (!normalizedIdx.has_value()) + return std::unexpected(normalizedIdx.error()); + + auto const r = locateField(*cache_[normalizedIdx.value()], locator); + if (!r) + return std::unexpected(r.error()); + + return getAnyFieldData(r.value()); +} + +// Subsection: array length getters + +std::expected +WasmHostFunctionsImpl::getTxArrayLen(SField const& fname) const +{ + if (fname.fieldType != STI_ARRAY && fname.fieldType != STI_VECTOR256) + return std::unexpected(HostFunctionError::NoArray); + + auto const* field = ctx_.tx.peekAtPField(fname); + if (noField(field)) + return std::unexpected(HostFunctionError::FieldNotFound); + + return getArrayLen(field); +} + +std::expected +WasmHostFunctionsImpl::getCurrentLedgerObjArrayLen(SField const& fname) const +{ + if (fname.fieldType != STI_ARRAY && fname.fieldType != STI_VECTOR256) + return std::unexpected(HostFunctionError::NoArray); + + auto const sle = getCurrentLedgerObj(); + if (!sle.has_value()) + return std::unexpected(sle.error()); + + auto const* field = sle.value()->peekAtPField(fname); + if (noField(field)) + return std::unexpected(HostFunctionError::FieldNotFound); + + return getArrayLen(field); +} + +std::expected +WasmHostFunctionsImpl::getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const +{ + if (fname.fieldType != STI_ARRAY && fname.fieldType != STI_VECTOR256) + return std::unexpected(HostFunctionError::NoArray); + + auto const normalizedIdx = normalizeCacheIndex(cacheIdx); + if (!normalizedIdx.has_value()) + return std::unexpected(normalizedIdx.error()); + + auto const* field = cache_[normalizedIdx.value()]->peekAtPField(fname); + if (noField(field)) + return std::unexpected(HostFunctionError::FieldNotFound); + + return getArrayLen(field); +} + +// Subsection: nested array length getters + +std::expected +WasmHostFunctionsImpl::getTxNestedArrayLen(FieldLocator const& locator) const +{ + auto const r = locateField(ctx_.tx, locator); + if (!r) + return std::unexpected(r.error()); + + auto const& field = r.value(); + return getArrayLen(field); +} + +std::expected +WasmHostFunctionsImpl::getCurrentLedgerObjNestedArrayLen(FieldLocator const& locator) const +{ + auto const sle = getCurrentLedgerObj(); + if (!sle.has_value()) + return std::unexpected(sle.error()); + auto const r = locateField(*sle.value(), locator); + if (!r) + return std::unexpected(r.error()); + + auto const& field = r.value(); + return getArrayLen(field); +} + +std::expected +WasmHostFunctionsImpl::getLedgerObjNestedArrayLen(int32_t cacheIdx, FieldLocator const& locator) + const +{ + auto const normalizedIdx = normalizeCacheIndex(cacheIdx); + if (!normalizedIdx.has_value()) + return std::unexpected(normalizedIdx.error()); + + auto const r = locateField(*cache_[normalizedIdx.value()], locator); + if (!r) + return std::unexpected(r.error()); + + auto const& field = r.value(); + return getArrayLen(field); +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/wasm/HostFuncImplKeylet.cpp b/src/libxrpl/tx/wasm/HostFuncImplKeylet.cpp new file mode 100644 index 0000000000..a16fc071a1 --- /dev/null +++ b/src/libxrpl/tx/wasm/HostFuncImplKeylet.cpp @@ -0,0 +1,222 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl { + +std::expected +WasmHostFunctionsImpl::accountKeylet(AccountID const& account) const +{ + if (!account) + return std::unexpected(HostFunctionError::InvalidAccount); + auto const keylet = keylet::account(account); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::ammKeylet(Asset const& issue1, Asset const& issue2) const +{ + if (issue1 == issue2) + return std::unexpected(HostFunctionError::InvalidParams); + + // note: this should be removed with the MPT DEX amendment + if (issue1.holds() || issue2.holds()) + return std::unexpected(HostFunctionError::InvalidParams); + + auto const keylet = keylet::amm(issue1, issue2); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::checkKeylet(AccountID const& account, std::uint32_t seq) const +{ + if (!account) + return std::unexpected(HostFunctionError::InvalidAccount); + auto const keylet = keylet::check(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::credentialKeylet( + AccountID const& subject, + AccountID const& issuer, + Slice const& credentialType) const +{ + if (!subject || !issuer) + return std::unexpected(HostFunctionError::InvalidAccount); + + if (credentialType.empty() || credentialType.size() > kMaxCredentialTypeLength) + return std::unexpected(HostFunctionError::InvalidParams); + + auto const keylet = keylet::credential(subject, issuer, credentialType); + + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::didKeylet(AccountID const& account) const +{ + if (!account) + return std::unexpected(HostFunctionError::InvalidAccount); + auto const keylet = keylet::did(account); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::delegateKeylet(AccountID const& account, AccountID const& authorize) const +{ + if (!account || !authorize) + return std::unexpected(HostFunctionError::InvalidAccount); + if (account == authorize) + return std::unexpected(HostFunctionError::InvalidParams); + auto const keylet = keylet::delegate(account, authorize); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::depositPreauthKeylet(AccountID const& account, AccountID const& authorize) + const +{ + if (!account || !authorize) + return std::unexpected(HostFunctionError::InvalidAccount); + if (account == authorize) + return std::unexpected(HostFunctionError::InvalidParams); + auto const keylet = keylet::depositPreauth(account, authorize); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::escrowKeylet(AccountID const& account, std::uint32_t seq) const +{ + if (!account) + return std::unexpected(HostFunctionError::InvalidAccount); + auto const keylet = keylet::escrow(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::trustLineKeylet( + AccountID const& account1, + AccountID const& account2, + Currency const& currency) const +{ + if (!account1 || !account2) + return std::unexpected(HostFunctionError::InvalidAccount); + if (account1 == account2) + return std::unexpected(HostFunctionError::InvalidParams); + if (currency.isZero()) + return std::unexpected(HostFunctionError::InvalidParams); + + auto const keylet = keylet::trustLine(account1, account2, currency); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::mptokenIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const +{ + if (!issuer) + return std::unexpected(HostFunctionError::InvalidAccount); + + auto const keylet = keylet::mptokenIssuance(seq, issuer); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::mptokenKeylet(MPTID const& mptid, AccountID const& holder) const +{ + if (!mptid) + return std::unexpected(HostFunctionError::InvalidParams); + if (!holder) + return std::unexpected(HostFunctionError::InvalidAccount); + + auto const keylet = keylet::mptoken(mptid, holder); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::nftokenOfferKeylet(AccountID const& account, std::uint32_t seq) const +{ + if (!account) + return std::unexpected(HostFunctionError::InvalidAccount); + auto const keylet = keylet::nftokenOffer(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::offerKeylet(AccountID const& account, std::uint32_t seq) const +{ + if (!account) + return std::unexpected(HostFunctionError::InvalidAccount); + auto const keylet = keylet::offer(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::oracleKeylet(AccountID const& account, std::uint32_t documentId) const +{ + if (!account) + return std::unexpected(HostFunctionError::InvalidAccount); + auto const keylet = keylet::oracle(account, documentId); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::paychannelKeylet( + AccountID const& account, + AccountID const& destination, + std::uint32_t seq) const +{ + if (!account || !destination) + return std::unexpected(HostFunctionError::InvalidAccount); + if (account == destination) + return std::unexpected(HostFunctionError::InvalidParams); + auto const keylet = keylet::payChannel(account, destination, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const +{ + if (!account) + return std::unexpected(HostFunctionError::InvalidAccount); + auto const keylet = keylet::permissionedDomain(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::signerListKeylet(AccountID const& account) const +{ + if (!account) + return std::unexpected(HostFunctionError::InvalidAccount); + auto const keylet = keylet::signerList(account); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::ticketKeylet(AccountID const& account, std::uint32_t seq) const +{ + if (!account) + return std::unexpected(HostFunctionError::InvalidAccount); + auto const keylet = keylet::ticket(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +std::expected +WasmHostFunctionsImpl::vaultKeylet(AccountID const& account, std::uint32_t seq) const +{ + if (!account) + return std::unexpected(HostFunctionError::InvalidAccount); + auto const keylet = keylet::vault(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/wasm/HostFuncImplLedgerHeader.cpp b/src/libxrpl/tx/wasm/HostFuncImplLedgerHeader.cpp new file mode 100644 index 0000000000..9def11dc95 --- /dev/null +++ b/src/libxrpl/tx/wasm/HostFuncImplLedgerHeader.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace xrpl { + +// ========================================================= +// SECTION: LEDGER HEADER FUNCTIONS +// ========================================================= + +std::expected +WasmHostFunctionsImpl::getLedgerSqn() const +{ + return ctx_.view().seq(); +} + +std::expected +WasmHostFunctionsImpl::getParentLedgerTime() const +{ + return ctx_.view().parentCloseTime().time_since_epoch().count(); +} + +std::expected +WasmHostFunctionsImpl::getParentLedgerHash() const +{ + return ctx_.view().header().parentHash; +} + +std::expected +WasmHostFunctionsImpl::getBaseFee() const +{ + return ctx_.view().fees().base.drops(); +} + +std::expected +WasmHostFunctionsImpl::isAmendmentEnabled(uint256 const& amendmentId) const +{ + return ctx_.view().rules().enabled(amendmentId); +} + +std::expected +WasmHostFunctionsImpl::isAmendmentEnabled(std::string_view const& amendmentName) const +{ + auto const& table = ctx_.registry.get().getAmendmentTable(); + auto const amendment = table.find(std::string(amendmentName)); + return ctx_.view().rules().enabled(amendment); +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/wasm/HostFuncImplNFT.cpp b/src/libxrpl/tx/wasm/HostFuncImplNFT.cpp new file mode 100644 index 0000000000..fdbf7d2e2d --- /dev/null +++ b/src/libxrpl/tx/wasm/HostFuncImplNFT.cpp @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl { + +// ========================================================= +// SECTION: NFT UTILS +// ========================================================= + +std::expected +WasmHostFunctionsImpl::getNFT(AccountID const& account, uint256 const& nftId) const +{ + if (!account) + return std::unexpected(HostFunctionError::InvalidAccount); + + if (!nftId) + return std::unexpected(HostFunctionError::InvalidParams); + + auto obj = nft::findToken(ctx_.view(), account, nftId); + if (!obj) + return std::unexpected(HostFunctionError::LedgerObjNotFound); + + auto objUri = obj->at(~sfURI); + if (!objUri) + return std::unexpected(HostFunctionError::FieldNotFound); + + Slice const s = objUri->value(); + return Bytes(s.begin(), s.end()); +} + +std::expected +WasmHostFunctionsImpl::getNFTIssuer(uint256 const& nftId) const +{ + auto const issuer = nft::getIssuer(nftId); + if (!issuer) + return std::unexpected(HostFunctionError::InvalidParams); + + return Bytes{issuer.begin(), issuer.end()}; +} + +std::expected +WasmHostFunctionsImpl::getNFTTaxon(uint256 const& nftId) const +{ + return nft::toUInt32(nft::getTaxon(nftId)); +} + +std::expected +WasmHostFunctionsImpl::getNFTFlags(uint256 const& nftId) const +{ + return nft::getFlags(nftId); +} + +std::expected +WasmHostFunctionsImpl::getNFTTransferFee(uint256 const& nftId) const +{ + return nft::getTransferFee(nftId); +} + +std::expected +WasmHostFunctionsImpl::getNFTSequence(uint256 const& nftId) const +{ + return nft::getSequence(nftId); +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/wasm/HostFuncImplTrace.cpp b/src/libxrpl/tx/wasm/HostFuncImplTrace.cpp new file mode 100644 index 0000000000..4e4058ea39 --- /dev/null +++ b/src/libxrpl/tx/wasm/HostFuncImplTrace.cpp @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#ifdef _DEBUG +// #define DEBUG_OUTPUT 1 +#endif + +namespace xrpl { + +std::expected +WasmHostFunctionsImpl::trace(std::string_view const& msg, Slice const& data, bool asHex) const +{ + if (!asHex) + { + log(msg, [&data] { + return std::string_view(reinterpret_cast(data.data()), data.size()); + }); + } + else + { + log(msg, [&data] { + std::string hex; + hex.reserve(data.size() * 2); + boost::algorithm::hex(data.begin(), data.end(), std::back_inserter(hex)); + return hex; + }); + } + + return 0; +} + +std::expected +WasmHostFunctionsImpl::traceNum(std::string_view const& msg, int64_t data) const +{ + log(msg, [data] { return data; }); + return 0; +} + +std::expected +WasmHostFunctionsImpl::traceAccount(std::string_view const& msg, AccountID const& account) const +{ + log(msg, [&account] { return toBase58(account); }); + return 0; +} + +std::expected +WasmHostFunctionsImpl::traceFloat(std::string_view const& msg, Slice const& data) const +{ + log(msg, [&data] { return wasm_float::floatToString(data); }); + return 0; +} + +std::expected +WasmHostFunctionsImpl::traceAmount(std::string_view const& msg, STAmount const& amount) const +{ + log(msg, [&amount] { return amount.getFullText(); }); + return 0; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/wasm/HostFuncWrapper.cpp b/src/libxrpl/tx/wasm/HostFuncWrapper.cpp new file mode 100644 index 0000000000..ea5caec6cc --- /dev/null +++ b/src/libxrpl/tx/wasm/HostFuncWrapper.cpp @@ -0,0 +1,1873 @@ +#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 { + +using SFieldCRef = std::reference_wrapper; + +constexpr int64_t unalignedGas = 50; + +// Charge `delta` gas; returns the remaining gas. Out-of-gas throws hfErrOutOfGas +// (-> tecOUT_OF_GAS); a failed setGas is an xrpld bug, throws hfErrInternal +// (-> tecINTERNAL). HostFuncMain_wrap turns both into traps. +static inline std::int64_t +checkGas(WasmRuntimeWrapper& rt, int64_t delta) +{ + int64_t const gas = rt.getGas(); + if (delta == 0) + return gas; + + int64_t const x = gas >= delta ? gas - delta : 0; + + if (rt.setGas(x) < 0) + Throw(std::string(hfErrInternal)); // LCOV_EXCL_LINE + + if (gas < delta) + Throw(std::string(hfErrOutOfGas)); + + return x; +} + +// Transfer limit is a separate soft budget: exceeding it is a normal guest-facing +// return code, not a trap. Only a failed setTransferLimit (an xrpld bug) throws. +static inline std::expected +checkTransfer(WasmRuntimeWrapper& rt, int64_t delta) +{ + auto const transLimit = rt.getTransferLimit(); + int64_t const x = transLimit >= delta ? transLimit - delta : 0; + + if (rt.setTransferLimit(x) < 0) + Throw(std::string(hfErrInternal)); // LCOV_EXCL_LINE + + if (transLimit < delta) + return std::unexpected(HostFunctionError::OutOfTransferLimit); + + return x; +} + +// On any failure here a C++ exception is thrown; HostFuncMain_wrap's catch-all +// turns it into tecINTERNAL. These conditions are all xrpld-side invariants. +static std::tuple +mainCheck(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (env == nullptr) + Throw(std::string(hfErrInternal)); // LCOV_EXCL_LINE + + if (params == nullptr) + Throw(std::string(hfErrInternal)); // LCOV_EXCL_LINE + + if (results == nullptr) + Throw(std::string(hfErrInternal)); // LCOV_EXCL_LINE + + WasmUserData const* udata = reinterpret_cast(env); + HostFunctions& hf = udata->first; + WasmRuntimeWrapper& rt = hf.getRT(); + WasmImportFunc const& impFunc = udata->second; + + // Charge the per-call gas. Throws (and terminates) if out of gas. + checkGas(rt, impFunc.gas); + + return std::tie(hf, impFunc); +} + +//---------------------------------------------------------------------------------------------------------------------- + +static int32_t +setData( + WasmRuntimeWrapper& runtime, + int32_t dst, + int32_t dstSize, + uint8_t const* src, + int32_t srcSize) +{ + if (srcSize == 0) + return 0; // LCOV_EXCL_LINE + + if (dst < 0 || dstSize < 0 || (src == nullptr) || srcSize < 0) + return hfErrorToInt(HostFunctionError::InvalidParams); + + if (srcSize > kMaxWasmDataLength) + return hfErrorToInt(HostFunctionError::DataFieldTooLarge); + + auto const memory = runtime.getMem(); + + // LCOV_EXCL_START + if (memory.s == 0u) + return hfErrorToInt(HostFunctionError::NoMemExported); + // LCOV_EXCL_STOP + if (std::cmp_greater((int64_t)dst + dstSize, memory.s)) + return hfErrorToInt(HostFunctionError::PointerOutOfBounds); + if (srcSize > dstSize) + return hfErrorToInt(HostFunctionError::BufferTooSmall); + + if (auto t = checkTransfer(runtime, srcSize); !t) + return hfErrorToInt(t.error()); + + memcpy(memory.p + dst, src, srcSize); + + return srcSize; +} + +static std::expected +getDataSlice(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int32_t& i) +{ + int64_t const ptr = params->data[i].of.i32; + int64_t const size = params->data[i + 1].of.i32; + i += 2; + if (ptr < 0 || size < 0) + return std::unexpected(HostFunctionError::InvalidParams); + + if (size == 0) + return Slice(); + + if (size > kMaxWasmDataLength) + return std::unexpected(HostFunctionError::DataFieldTooLarge); + + auto const memory = runtime.getMem(); + // LCOV_EXCL_START + if (memory.s == 0u) + return std::unexpected(HostFunctionError::NoMemExported); + // LCOV_EXCL_STOP + + if (std::cmp_greater(ptr + size, memory.s)) + return std::unexpected(HostFunctionError::PointerOutOfBounds); + + Slice const data(memory.p + ptr, size); + return data; +} + +static std::expected +getDataInt32(WasmRuntimeWrapper const&, wasm_val_vec_t const* params, int32_t& i) +{ + auto const result = params->data[i].of.i32; + i++; + return result; +} + +static std::expected +getDataInt64(WasmRuntimeWrapper const&, wasm_val_vec_t const* params, int32_t& i) +{ + auto const result = params->data[i].of.i64; + i++; + return result; +} + +template +static std::expected +getDataUnsigned(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int32_t& i) +{ + static_assert(std::is_unsigned_v); + auto const r = getDataSlice(runtime, params, i); + if (!r) + return std::unexpected(r.error()); + if (r->size() != sizeof(T)) + return std::unexpected(HostFunctionError::InvalidParams); + + T x; + auto const p = reinterpret_cast(r->data()); + if (p & (alignof(T) - 1)) // unaligned + { + memcpy(&x, r->data(), sizeof(T)); + } + else + { + x = *reinterpret_cast(r->data()); + } + x = adjustWasmEndianess(x); + + return x; +} + +static std::expected +getDataUInt32(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int32_t& i) +{ + return getDataUnsigned(runtime, params, i); +} + +static std::expected +getDataUInt64(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int32_t& i) +{ + return getDataUnsigned(runtime, params, i); +} + +static std::expected +getDataSField(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const& m = SField::getKnownCodeToField(); + auto const it = m.find(params->data[i].of.i32); + i++; + if (it == m.end()) + return std::unexpected(HostFunctionError::InvalidField); + + return *it->second; +} + +static std::expected +getDataUInt256(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const slice = getDataSlice(runtime, params, i); + if (!slice) + return std::unexpected(slice.error()); + + if (slice->size() != uint256::size()) + return std::unexpected(HostFunctionError::InvalidParams); + + if (auto t = checkTransfer(runtime, uint256::size()); !t) + return std::unexpected(t.error()); + + return uint256::fromVoid(slice->data()); +} + +static std::expected +getDataAccountID(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const slice = getDataSlice(runtime, params, i); + if (!slice) + return std::unexpected(slice.error()); + + if (slice->size() != AccountID::size()) + return std::unexpected(HostFunctionError::InvalidParams); + + if (auto t = checkTransfer(runtime, AccountID::size()); !t) + return std::unexpected(t.error()); + + return AccountID::fromVoid(slice->data()); +} + +static std::expected +getDataCurrency(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const slice = getDataSlice(runtime, params, i); + if (!slice) + return std::unexpected(slice.error()); + + if (slice->size() != Currency::size()) + return std::unexpected(HostFunctionError::InvalidParams); + + if (auto t = checkTransfer(runtime, Currency::size()); !t) + return std::unexpected(t.error()); + + return Currency::fromVoid(slice->data()); +} + +static std::expected +getDataAsset(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const slice = getDataSlice(runtime, params, i); + if (!slice) + return std::unexpected(slice.error()); + + if (slice->size() == MPTID::size()) + { + if (auto t = checkTransfer(runtime, slice->size()); !t) + return std::unexpected(t.error()); + + auto const mptid = MPTID::fromVoid(slice->data()); + return Asset{mptid}; + } + + if (slice->size() == Currency::size()) + { + if (auto t = checkTransfer(runtime, slice->size()); !t) + return std::unexpected(t.error()); + + auto const currency = Currency::fromVoid(slice->data()); + auto const issue = Issue{currency, xrpAccount()}; + if (!issue.native()) + return std::unexpected(HostFunctionError::InvalidParams); + + return Asset{issue}; + } + + if (slice->size() == (Currency::size() + AccountID::size())) + { + if (auto t = checkTransfer(runtime, slice->size()); !t) + return std::unexpected(t.error()); + + auto const issue = Issue( + Currency::fromVoid(slice->data()), + AccountID::fromVoid(slice->data() + Currency::size())); + + if (issue.native()) + return std::unexpected(HostFunctionError::InvalidParams); + + return Asset{issue}; + } + + return std::unexpected(HostFunctionError::InvalidParams); +} + +static std::expected +getDataString(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const slice = getDataSlice(runtime, params, i); + if (!slice) + return std::unexpected(slice.error()); + + return std::string_view(reinterpret_cast(slice->data()), slice->size()); +} + +static std::expected +getDataLocator(WasmRuntimeWrapper& runtime, wasm_val_vec_t const* params, int32_t& i) +{ + static_assert(kMaxWasmDataLength % sizeof(int32_t) == 0); + + auto const slice = getDataSlice(runtime, params, i); + if (!slice) + return std::unexpected(slice.error()); + if (slice->empty() || ((slice->size() & 3) != 0u)) // must be multiple of 4 + return std::unexpected(HostFunctionError::LocatorMalformed); + + uint32_t const locSize = slice->size() / sizeof(int32_t); + auto const p = reinterpret_cast(slice->data()); + + if ((p & (alignof(int32_t) - 1)) != 0u) + { // unaligned + + // Use gas and transfer limit for copying. checkGas throws (and + // terminates execution) if out of gas; checkTransfer keeps returning a + // guest-facing code when the transfer limit is exceeded. + checkGas(runtime, unalignedGas); + if (auto t = checkTransfer(runtime, slice->size()); !t) + return std::unexpected(t.error()); + + std::vector locBuf(locSize); + memcpy(&locBuf[0], slice->data(), slice->size()); + FieldLocator locator(std::move(locBuf)); + + return locator; + } + + auto const* locPtr = reinterpret_cast(slice->data()); + return FieldLocator(locPtr, locSize); +} + +static inline std::nullptr_t +hfResult(wasm_val_vec_t* results, int32_t value) +{ + results->data[0] = WASM_I32_VAL(value); + // results->size = 1; + return nullptr; +} + +static inline std::nullptr_t +hfResult(wasm_val_vec_t* results, HostFunctionError value) +{ + results->data[0] = WASM_I32_VAL(hfErrorToInt(value)); + // results->size = 1; + return nullptr; +} + +template +static std::nullptr_t +returnResult( + WasmRuntimeWrapper& runtime, + wasm_val_vec_t const* params, + wasm_val_vec_t* results, + std::expected const& res, + int32_t index) +{ + if (!res) + return hfResult(results, res.error()); + + if constexpr (std::is_same_v) + { + if (index < 0 || index + 1 >= params->size) + Throw(std::string(hfErrInternal)); // LCOV_EXCL_LINE + + auto const dataResult = setData( + runtime, + params->data[index].of.i32, + params->data[index + 1].of.i32, + res->data(), + res->size()); + return hfResult(results, dataResult); + } + else if constexpr (std::is_same_v) + { + if (index < 0 || index + 1 >= params->size) + Throw(std::string(hfErrInternal)); // LCOV_EXCL_LINE + + auto const dataResult = setData( + runtime, + params->data[index].of.i32, + params->data[index + 1].of.i32, + res->data(), + res->size()); + return hfResult(results, dataResult); + } + else if constexpr (std::is_same_v) + { + return hfResult(results, res.value()); + } + else if constexpr (std::is_same_v) + { + if (index < 0 || index + 1 >= params->size) + Throw(std::string(hfErrInternal)); // LCOV_EXCL_LINE + + auto const resultValue = adjustWasmEndianess(res.value()); + auto const dataResult = setData( + runtime, + params->data[index].of.i32, + params->data[index + 1].of.i32, + reinterpret_cast(&resultValue), + static_cast(sizeof(resultValue))); + return hfResult(results, dataResult); + } + else if constexpr (std::is_same_v) + { + if (index < 0 || index + 1 >= params->size) + Throw(std::string(hfErrInternal)); // LCOV_EXCL_LINE + + auto const resultValue = adjustWasmEndianess(res.value()); + auto const dataResult = setData( + runtime, + params->data[index].of.i32, + params->data[index + 1].of.i32, + reinterpret_cast(&resultValue), + static_cast(sizeof(resultValue))); + return hfResult(results, dataResult); + } + else if constexpr (std::is_same_v) + { + if (index < 0 || index + 3 >= params->size) + Throw(std::string(hfErrInternal)); // LCOV_EXCL_LINE + + auto const mantissa = adjustWasmEndianess(res->first); + auto const r1 = setData( + runtime, + params->data[index].of.i32, + params->data[index + 1].of.i32, + reinterpret_cast(&mantissa), + static_cast(sizeof(mantissa))); + if (r1 < 0) + return hfResult(results, r1); + + index += 2; + auto const exponent = adjustWasmEndianess(res->second); + auto const r2 = setData( + runtime, + params->data[index].of.i32, + params->data[index + 1].of.i32, + reinterpret_cast(&exponent), + static_cast(sizeof(exponent))); + if (r2 < 0) + return hfResult(results, r2); + + return hfResult(results, r1 + r2); // 12 bytes + } + else + { + static_assert([] { return false; }(), "Unhandled return type in returnResult"); + } +} + +//---------------------------------------------------------------------------------------------------------------------- + +wasm_trap_t* +HostFuncMain_wrap(WASM_CB_PARAMS_LIST) +{ + [[maybe_unused]] std::string_view hfName; + + try + { + auto [hf, impFunc] = mainCheck(env, params, results); + hfName = impFunc.name; + auto* fWrap = reinterpret_cast(impFunc.wrap); + return fWrap(hf, params, results); + } + catch (std::exception const& e) + { +#ifdef DEBUG_OUTPUT + std::cerr << "Hostfunction " << hfName << " exception: " << e.what() << std::endl; +#endif + // Normalize to the two boundary signals: explicit out-of-gas, else any + // exception (including stray ones from helpers) is an internal fault. + bool const oog = std::string_view(e.what()) == hfErrOutOfGas; + wasm_trap_t* trap = reinterpret_cast( // NOLINT + WasmEngine::instance().newTrap(std::string(oog ? hfErrOutOfGas : hfErrInternal))); + return trap; + } + catch (...) + { +#ifdef DEBUG_OUTPUT + std::cerr << "Hostfunction " << hfName << " unknown exception." << std::endl; +#endif + wasm_trap_t* trap = reinterpret_cast( // NOLINT + WasmEngine::instance().newTrap(std::string(hfErrInternal))); // LCOV_EXCL_LINE + return trap; + } + + return nullptr; // LCOV_EXCL_LINE +} + +//---------------------------------------------------------------------------------------------------------------------- +wasm_trap_t* +getLedgerSqn_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int const index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + return returnResult(runtime, params, results, hf.getLedgerSqn(), index); +} + +wasm_trap_t* +getParentLedgerTime_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int const index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + return returnResult(runtime, params, results, hf.getParentLedgerTime(), index); +} + +wasm_trap_t* +getParentLedgerHash_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int const index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + return returnResult(runtime, params, results, hf.getParentLedgerHash(), index); +} + +wasm_trap_t* +getBaseFee_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int const index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + return returnResult(runtime, params, results, hf.getBaseFee(), index); +} + +wasm_trap_t* +isAmendmentEnabled_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const slice = getDataSlice(runtime, params, index); + if (!slice) + return hfResult(results, slice.error()); + + if (slice->size() == uint256::size()) + { + if (auto const ret = hf.isAmendmentEnabled(uint256::fromVoid(slice->data())); + ret && *ret == 1) + return returnResult(runtime, params, results, ret, index); + // Fall through to string lookup — the 32 bytes may be an amendment name + } + + if (slice->size() > 64) + return hfResult(results, HostFunctionError::DataFieldTooLarge); + + auto const str = std::string_view(reinterpret_cast(slice->data()), slice->size()); + return returnResult(runtime, params, results, hf.isAmendmentEnabled(str), index); +} + +wasm_trap_t* +cacheLedgerObj_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const id = getDataUInt256(runtime, params, index); + if (!id) + return hfResult(results, id.error()); + + auto const cache = getDataInt32(runtime, params, index); + if (!cache) + return hfResult(results, cache.error()); // LCOV_EXCL_LINE + + return returnResult(runtime, params, results, hf.cacheLedgerObj(*id, *cache), index); +} + +wasm_trap_t* +getTxField_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const fname = getDataSField(runtime, params, index); + if (!fname) + return hfResult(results, fname.error()); + + return returnResult(runtime, params, results, hf.getTxField(*fname), index); +} + +wasm_trap_t* +getCurrentLedgerObjField_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const fname = getDataSField(runtime, params, index); + if (!fname) + return hfResult(results, fname.error()); + + return returnResult(runtime, params, results, hf.getCurrentLedgerObjField(*fname), index); +} + +wasm_trap_t* +getLedgerObjField_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const cache = getDataInt32(runtime, params, index); + if (!cache) + return hfResult(results, cache.error()); // LCOV_EXCL_LINE + + auto const fname = getDataSField(runtime, params, index); + if (!fname) + return hfResult(results, fname.error()); + + return returnResult(runtime, params, results, hf.getLedgerObjField(*cache, *fname), index); +} + +wasm_trap_t* +getTxNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const locator = getDataLocator(runtime, params, index); + if (!locator) + return hfResult(results, locator.error()); + + return returnResult(runtime, params, results, hf.getTxNestedField(*locator), index); +} + +wasm_trap_t* +getCurrentLedgerObjNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const locator = getDataLocator(runtime, params, index); + if (!locator) + return hfResult(results, locator.error()); + + return returnResult( + runtime, params, results, hf.getCurrentLedgerObjNestedField(*locator), index); +} + +wasm_trap_t* +getLedgerObjNestedField_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const cache = getDataInt32(runtime, params, index); + if (!cache) + return hfResult(results, cache.error()); // LCOV_EXCL_LINE + + auto const locator = getDataLocator(runtime, params, index); + if (!locator) + return hfResult(results, locator.error()); + + return returnResult( + runtime, params, results, hf.getLedgerObjNestedField(*cache, *locator), index); +} + +wasm_trap_t* +getTxArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const fname = getDataSField(runtime, params, index); + if (!fname) + return hfResult(results, fname.error()); + + return returnResult(runtime, params, results, hf.getTxArrayLen(*fname), index); +} + +wasm_trap_t* +getCurrentLedgerObjArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const fname = getDataSField(runtime, params, index); + if (!fname) + return hfResult(results, fname.error()); + + return returnResult(runtime, params, results, hf.getCurrentLedgerObjArrayLen(*fname), index); +} + +wasm_trap_t* +getLedgerObjArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const cache = getDataInt32(runtime, params, index); + if (!cache) + return hfResult(results, cache.error()); // LCOV_EXCL_LINE + + auto const fname = getDataSField(runtime, params, index); + if (!fname) + return hfResult(results, fname.error()); + + return returnResult(runtime, params, results, hf.getLedgerObjArrayLen(*cache, *fname), index); +} + +wasm_trap_t* +getTxNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const locator = getDataLocator(runtime, params, index); + if (!locator) + return hfResult(results, locator.error()); + + return returnResult(runtime, params, results, hf.getTxNestedArrayLen(*locator), index); +} + +wasm_trap_t* +getCurrentLedgerObjNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const locator = getDataLocator(runtime, params, index); + if (!locator) + return hfResult(results, locator.error()); + + return returnResult( + runtime, params, results, hf.getCurrentLedgerObjNestedArrayLen(*locator), index); +} +wasm_trap_t* +getLedgerObjNestedArrayLen_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const cache = getDataInt32(runtime, params, index); + if (!cache) + return hfResult(results, cache.error()); // LCOV_EXCL_LINE + + auto const locator = getDataLocator(runtime, params, index); + if (!locator) + return hfResult(results, locator.error()); + + return returnResult( + runtime, params, results, hf.getLedgerObjNestedArrayLen(*cache, *locator), index); +} + +wasm_trap_t* +updateData_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const bytes = getDataSlice(runtime, params, index); + if (!bytes) + return hfResult(results, bytes.error()); + + return returnResult(runtime, params, results, hf.updateData(*bytes), index); +} + +wasm_trap_t* +checkSignature_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const message = getDataSlice(runtime, params, index); + if (!message) + return hfResult(results, message.error()); + + auto const signature = getDataSlice(runtime, params, index); + if (!signature) + return hfResult(results, signature.error()); + + auto const pubkey = getDataSlice(runtime, params, index); + if (!pubkey) + return hfResult(results, pubkey.error()); + + return returnResult( + runtime, params, results, hf.checkSignature(*message, *signature, *pubkey), index); +} + +wasm_trap_t* +computeSha512HalfHash_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const bytes = getDataSlice(runtime, params, index); + if (!bytes) + return hfResult(results, bytes.error()); + + return returnResult(runtime, params, results, hf.computeSha512HalfHash(*bytes), index); +} + +wasm_trap_t* +accountKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + return hfResult(results, acc.error()); + + return returnResult(runtime, params, results, hf.accountKeylet(*acc), index); +} + +wasm_trap_t* +ammKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const issue1 = getDataAsset(runtime, params, index); + if (!issue1) + return hfResult(results, issue1.error()); + + auto const issue2 = getDataAsset(runtime, params, index); + if (!issue2) + return hfResult(results, issue2.error()); + + return returnResult( + runtime, params, results, hf.ammKeylet(issue1.value(), issue2.value()), index); +} + +wasm_trap_t* +checkKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + return hfResult(results, acc.error()); + + auto const seq = getDataUInt32(runtime, params, index); + if (!seq) + return hfResult(results, seq.error()); + + return returnResult(runtime, params, results, hf.checkKeylet(acc.value(), *seq), index); +} + +wasm_trap_t* +credentialKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const subj = getDataAccountID(runtime, params, index); + if (!subj) + return hfResult(results, subj.error()); + + auto const iss = getDataAccountID(runtime, params, index); + if (!iss) + return hfResult(results, iss.error()); + + auto const credType = getDataSlice(runtime, params, index); + if (!credType) + return hfResult(results, credType.error()); + + return returnResult( + runtime, params, results, hf.credentialKeylet(*subj, *iss, *credType), index); +} + +wasm_trap_t* +delegateKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + return hfResult(results, acc.error()); + + auto const authorize = getDataAccountID(runtime, params, index); + if (!authorize) + return hfResult(results, authorize.error()); + + return returnResult( + runtime, params, results, hf.delegateKeylet(acc.value(), authorize.value()), index); +} + +wasm_trap_t* +depositPreauthKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + return hfResult(results, acc.error()); + + auto const authorize = getDataAccountID(runtime, params, index); + if (!authorize) + return hfResult(results, authorize.error()); + + return returnResult( + runtime, params, results, hf.depositPreauthKeylet(acc.value(), authorize.value()), index); +} + +wasm_trap_t* +didKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + return hfResult(results, acc.error()); + + return returnResult(runtime, params, results, hf.didKeylet(acc.value()), index); +} + +wasm_trap_t* +escrowKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + return hfResult(results, acc.error()); + + auto const seq = getDataUInt32(runtime, params, index); + if (!seq) + return hfResult(results, seq.error()); + + return returnResult(runtime, params, results, hf.escrowKeylet(*acc, *seq), index); +} + +wasm_trap_t* +trustLineKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const acc1 = getDataAccountID(runtime, params, index); + if (!acc1) + return hfResult(results, acc1.error()); + + auto const acc2 = getDataAccountID(runtime, params, index); + if (!acc2) + return hfResult(results, acc2.error()); + + auto const currency = getDataCurrency(runtime, params, index); + if (!currency) + return hfResult(results, currency.error()); + + return returnResult( + runtime, + params, + results, + hf.trustLineKeylet(acc1.value(), acc2.value(), currency.value()), + index); +} + +wasm_trap_t* +mptokenIssuanceKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + return hfResult(results, acc.error()); + + auto const seq = getDataUInt32(runtime, params, index); + if (!seq) + return hfResult(results, seq.error()); + + return returnResult( + runtime, params, results, hf.mptokenIssuanceKeylet(acc.value(), seq.value()), index); +} + +wasm_trap_t* +mptokenKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const slice = getDataSlice(runtime, params, index); + if (!slice) + return hfResult(results, slice.error()); + + if (slice->size() != MPTID::size()) + return hfResult(results, HostFunctionError::InvalidParams); + auto const mptid = MPTID::fromVoid(slice->data()); + + auto const holder = getDataAccountID(runtime, params, index); + if (!holder) + return hfResult(results, holder.error()); + + return returnResult(runtime, params, results, hf.mptokenKeylet(mptid, holder.value()), index); +} + +wasm_trap_t* +nftokenOfferKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + return hfResult(results, acc.error()); + + auto const seq = getDataUInt32(runtime, params, index); + if (!seq) + return hfResult(results, seq.error()); + + return returnResult( + runtime, params, results, hf.nftokenOfferKeylet(acc.value(), seq.value()), index); +} + +wasm_trap_t* +offerKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + return hfResult(results, acc.error()); + + auto const seq = getDataUInt32(runtime, params, index); + if (!seq) + return hfResult(results, seq.error()); + + return returnResult(runtime, params, results, hf.offerKeylet(acc.value(), seq.value()), index); +} + +wasm_trap_t* +oracleKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + return hfResult(results, acc.error()); + + auto const documentId = getDataUInt32(runtime, params, index); + if (!documentId) + return hfResult(results, documentId.error()); + + return returnResult(runtime, params, results, hf.oracleKeylet(*acc, *documentId), index); +} + +wasm_trap_t* +paychannelKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + return hfResult(results, acc.error()); + + auto const dest = getDataAccountID(runtime, params, index); + if (!dest) + return hfResult(results, dest.error()); + + auto const seq = getDataUInt32(runtime, params, index); + if (!seq) + return hfResult(results, seq.error()); + + return returnResult( + runtime, + params, + results, + hf.paychannelKeylet(acc.value(), dest.value(), seq.value()), + index); +} + +wasm_trap_t* +permissionedDomainKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + return hfResult(results, acc.error()); + + auto const seq = getDataUInt32(runtime, params, index); + if (!seq) + return hfResult(results, seq.error()); + + return returnResult( + runtime, params, results, hf.permissionedDomainKeylet(acc.value(), seq.value()), index); +} + +wasm_trap_t* +signerListKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + return hfResult(results, acc.error()); + + return returnResult(runtime, params, results, hf.signerListKeylet(acc.value()), index); +} + +wasm_trap_t* +ticketKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + return hfResult(results, acc.error()); + + auto const seq = getDataUInt32(runtime, params, index); + if (!seq) + return hfResult(results, seq.error()); + + return returnResult(runtime, params, results, hf.ticketKeylet(acc.value(), seq.value()), index); +} + +wasm_trap_t* +vaultKeylet_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + return hfResult(results, acc.error()); + + auto const seq = getDataUInt32(runtime, params, index); + if (!seq) + return hfResult(results, seq.error()); + + return returnResult(runtime, params, results, hf.vaultKeylet(acc.value(), seq.value()), index); +} + +wasm_trap_t* +getNFT_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + return hfResult(results, acc.error()); + + auto const nftId = getDataUInt256(runtime, params, index); + if (!nftId) + return hfResult(results, nftId.error()); + + return returnResult(runtime, params, results, hf.getNFT(*acc, *nftId), index); +} + +wasm_trap_t* +getNFTIssuer_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const nftId = getDataUInt256(runtime, params, index); + if (!nftId) + return hfResult(results, nftId.error()); + + return returnResult(runtime, params, results, hf.getNFTIssuer(*nftId), index); +} + +wasm_trap_t* +getNFTTaxon_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const nftId = getDataUInt256(runtime, params, index); + if (!nftId) + return hfResult(results, nftId.error()); + + return returnResult(runtime, params, results, hf.getNFTTaxon(*nftId), index); +} + +wasm_trap_t* +getNFTFlags_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const nftId = getDataUInt256(runtime, params, index); + if (!nftId) + return hfResult(results, nftId.error()); + + return returnResult(runtime, params, results, hf.getNFTFlags(*nftId), index); +} + +wasm_trap_t* +getNFTTransferFee_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const nftId = getDataUInt256(runtime, params, index); + if (!nftId) + return hfResult(results, nftId.error()); + + return returnResult(runtime, params, results, hf.getNFTTransferFee(*nftId), index); +} + +wasm_trap_t* +getNFTSequence_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const nftId = getDataUInt256(runtime, params, index); + if (!nftId) + return hfResult(results, nftId.error()); + + return returnResult(runtime, params, results, hf.getNFTSequence(*nftId), index); +} + +wasm_trap_t* +trace_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const msg = getDataString(runtime, params, index); + if (!msg) + return hfResult(results, msg.error()); + + auto const data = getDataSlice(runtime, params, index); + if (!data) + return hfResult(results, data.error()); + + if (msg->size() + data->size() > kMaxWasmDataLength) + return hfResult(results, HostFunctionError::DataFieldTooLarge); + + auto const asHex = getDataInt32(runtime, params, index); + if (!asHex) + return hfResult(results, asHex.error()); // LCOV_EXCL_LINE + + if (*asHex != 0 && *asHex != 1) + return hfResult(results, HostFunctionError::InvalidParams); + + return returnResult(runtime, params, results, hf.trace(*msg, *data, *asHex != 0), index); +} + +wasm_trap_t* +traceNum_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int index = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const msg = getDataString(runtime, params, index); + if (!msg) + return hfResult(results, msg.error()); + + auto const number = getDataInt64(runtime, params, index); + if (!number) + return hfResult(results, number.error()); // LCOV_EXCL_LINE + + return returnResult(runtime, params, results, hf.traceNum(*msg, *number), index); +} + +wasm_trap_t* +traceAccount_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int i = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const msg = getDataString(runtime, params, i); + if (!msg) + return hfResult(results, msg.error()); + + auto const account = getDataAccountID(runtime, params, i); + if (!account) + return hfResult(results, account.error()); + + return returnResult(runtime, params, results, hf.traceAccount(*msg, *account), i); +} + +wasm_trap_t* +traceFloat_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int i = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const msg = getDataString(runtime, params, i); + if (!msg) + return hfResult(results, msg.error()); + + auto const number = getDataSlice(runtime, params, i); + if (!number) + return hfResult(results, number.error()); + + return returnResult(runtime, params, results, hf.traceFloat(*msg, *number), i); +} + +wasm_trap_t* +traceAmount_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int i = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const msg = getDataString(runtime, params, i); + if (!msg) + return hfResult(results, msg.error()); + + auto const amountSliceOpt = getDataSlice(runtime, params, i); + if (!amountSliceOpt) + return hfResult(results, amountSliceOpt.error()); + + auto const amountSlice = amountSliceOpt.value(); + auto serialIter = SerialIter(amountSlice); + + std::optional amount; + try + { + amount = STAmount(serialIter, sfGeneric); + } + catch (std::exception const&) + { + amount = std::nullopt; + } + + if (!amount) + { + return hfResult(results, HostFunctionError::InvalidParams); + } + + return returnResult(runtime, params, results, hf.traceAmount(*msg, *amount), i); +} + +wasm_trap_t* +floatFromInt_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int i = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const x = getDataInt64(runtime, params, i); + if (!x) + return hfResult(results, x.error()); // LCOV_EXCL_LINE + + i = 3; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 1; + return returnResult(runtime, params, results, hf.floatFromInt(*x, *rounding), i); +} + +wasm_trap_t* +floatFromUint_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int i = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const x = getDataUInt64(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + i = 4; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 2; + return returnResult(runtime, params, results, hf.floatFromUint(*x, *rounding), i); +} + +wasm_trap_t* +floatFromSTAmount_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int i = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + auto serialIter = SerialIter(*x); + std::optional amount; + try + { + amount = STAmount(serialIter, sfGeneric); + } + catch (std::exception const&) + { + amount = std::nullopt; + } + if (!amount) + return hfResult(results, HostFunctionError::InvalidParams); + + i = 4; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 2; + return returnResult(runtime, params, results, hf.floatFromSTAmount(*amount, *rounding), i); +} + +wasm_trap_t* +floatFromSTNumber_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int i = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + auto serialIter = SerialIter(*x); + std::optional num; + try + { + num = STNumber(serialIter, sfGeneric); + } + catch (std::exception const&) + { + num = std::nullopt; + } + if (!num) + return hfResult(results, HostFunctionError::InvalidParams); + + i = 4; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 2; + return returnResult(runtime, params, results, hf.floatFromSTNumber(*num, *rounding), i); +} + +wasm_trap_t* +floatToInt_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int i = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + i = 4; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 2; + return returnResult(runtime, params, results, hf.floatToInt(*x, *rounding), i); +} + +wasm_trap_t* +floatToMantExp_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int i = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + i = 2; + return returnResult(runtime, params, results, hf.floatToMantExp(*x), i); +} + +wasm_trap_t* +floatFromMantExp_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int i = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const mant = getDataInt64(runtime, params, i); + if (!mant) + return hfResult(results, mant.error()); // LCOV_EXCL_LINE + + auto const exp = getDataInt32(runtime, params, i); + if (!exp) + return hfResult(results, exp.error()); // LCOV_EXCL_LINE + + i = 4; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 2; + return returnResult(runtime, params, results, hf.floatFromMantExp(*mant, *exp, *rounding), i); +} + +wasm_trap_t* +floatCompare_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int i = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + auto const y = getDataSlice(runtime, params, i); + if (!y) + return hfResult(results, y.error()); + + return returnResult(runtime, params, results, hf.floatCompare(*x, *y), i); +} + +wasm_trap_t* +floatAdd_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int i = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + auto const y = getDataSlice(runtime, params, i); + if (!y) + return hfResult(results, y.error()); + + i = 6; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 4; + return returnResult(runtime, params, results, hf.floatAdd(*x, *y, *rounding), i); +} + +wasm_trap_t* +floatSubtract_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int i = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + auto const y = getDataSlice(runtime, params, i); + if (!y) + return hfResult(results, y.error()); + + i = 6; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 4; + return returnResult(runtime, params, results, hf.floatSubtract(*x, *y, *rounding), i); +} + +wasm_trap_t* +floatMultiply_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int i = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + auto const y = getDataSlice(runtime, params, i); + if (!y) + return hfResult(results, y.error()); + + i = 6; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 4; + return returnResult(runtime, params, results, hf.floatMultiply(*x, *y, *rounding), i); +} + +wasm_trap_t* +floatDivide_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int i = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + auto const y = getDataSlice(runtime, params, i); + if (!y) + return hfResult(results, y.error()); + + i = 6; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 4; + return returnResult(runtime, params, results, hf.floatDivide(*x, *y, *rounding), i); +} + +wasm_trap_t* +floatRoot_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int i = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + auto const n = getDataInt32(runtime, params, i); + if (!n) + return hfResult(results, n.error()); // LCOV_EXCL_LINE + + i = 5; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 3; + return returnResult(runtime, params, results, hf.floatRoot(*x, *n, *rounding), i); +} + +wasm_trap_t* +floatPower_wrap(WASM_SECONDARY_CB_PARAMS_LIST) +{ + int i = 0; + WasmRuntimeWrapper& runtime = hf.getRT(); + + auto const x = getDataSlice(runtime, params, i); + if (!x) + return hfResult(results, x.error()); + + auto const n = getDataInt32(runtime, params, i); + if (!n) + return hfResult(results, n.error()); // LCOV_EXCL_LINE + + i = 5; + auto const rounding = getDataInt32(runtime, params, i); + if (!rounding) + return hfResult(results, rounding.error()); // LCOV_EXCL_LINE + + i = 3; + return returnResult(runtime, params, results, hf.floatPower(*x, *n, *rounding), i); +} + +// LCOV_EXCL_START +namespace test { + +class MockWasmRuntimeWrapper : public WasmRuntimeWrapper +{ + Wmem mem_; + + std::int64_t gas_ = 1'000'000; + std::int64_t transferLimit_ = kWasmTransferLimit; + +public: + MockWasmRuntimeWrapper(Wmem memory) : mem_(memory) + { + } + + // Mock methods to simulate the behavior of WasmRuntimeWrapper + [[nodiscard]] Wmem + getMem() override + { + return mem_; + } + + std::int64_t + getGas() override + { + return gas_; + } + + std::int64_t + setGas(std::int64_t gas) override + { + gas_ = gas; + return gas_; + } + + std::int64_t + getTransferLimit() override + { + return transferLimit_; + } + + std::int64_t + setTransferLimit(std::int64_t x) override + { + transferLimit_ = x; + return transferLimit_; + } +}; + +bool +testGetDataIncrement() +{ + wasm_val_t values[4]; + + std::array buffer = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}; + MockWasmRuntimeWrapper runtime(Wmem(buffer.data(), buffer.size())); + + { + // test int32_t + wasm_val_vec_t const params = {.size = 1, .data = &values[0]}; + + values[0] = WASM_I32_VAL(42); + + int index = 0; + auto const result = getDataInt32(runtime, ¶ms, index); + if (!result || result.value() != 42 || index != 1) + return false; + } + + { + // test int64_t + wasm_val_vec_t const params = {.size = 1, .data = &values[0]}; + + values[0] = WASM_I64_VAL(1234); + + int index = 0; + auto const result = getDataInt64(runtime, ¶ms, index); + if (!result || result.value() != 1234 || index != 1) + return false; + } + + { + // test SFieldCRef + wasm_val_vec_t const params = {.size = 1, .data = &values[0]}; + + values[0] = WASM_I32_VAL(sfAccount.getCode()); + + int index = 0; + auto const result = getDataSField(runtime, ¶ms, index); + if (!result || result.value().get() != sfAccount || index != 1) + return false; + } + + { + // test Slice + wasm_val_vec_t const params = {.size = 2, .data = &values[0]}; + + values[0] = WASM_I32_VAL(0); + values[1] = WASM_I32_VAL(3); + + int index = 0; + auto const result = getDataSlice(runtime, ¶ms, index); + if (!result || result.value() != Slice(buffer.data(), 3) || index != 2) + return false; + } + + { + // test string + wasm_val_vec_t const params = {.size = 2, .data = &values[0]}; + + values[0] = WASM_I32_VAL(0); + values[1] = WASM_I32_VAL(5); + + int index = 0; + auto const result = getDataString(runtime, ¶ms, index); + if (!result || + result.value() != std::string_view(reinterpret_cast(buffer.data()), 5) || + index != 2) + return false; + } + + { + // test account + AccountID const id( + calcAccountID(generateKeyPair(KeyType::Secp256k1, generateSeed("alice")).first)); + + wasm_val_vec_t const params = {.size = 2, .data = &values[0]}; + + values[0] = WASM_I32_VAL(0); + values[1] = WASM_I32_VAL(AccountID::size()); + memcpy(&buffer[0], id.data(), AccountID::size()); + + int index = 0; + auto const result = getDataAccountID(runtime, ¶ms, index); + if (!result || result.value() != id || index != 2) + return false; + } + + { + // test uint256 + + Hash h1 = sha512Half(Slice(buffer.data(), 8)); + wasm_val_vec_t const params = {.size = 2, .data = &values[0]}; + + values[0] = WASM_I32_VAL(0); + values[1] = WASM_I32_VAL(Hash::size()); + memcpy(&buffer[0], h1.data(), Hash::size()); + + int index = 0; + auto const result = getDataUInt256(runtime, ¶ms, index); + if (!result || result.value() != h1 || index != 2) + return false; + } + + { + // test Currency + + Currency const c = xrpCurrency(); + wasm_val_vec_t const params = {.size = 2, .data = &values[0]}; + + values[0] = WASM_I32_VAL(0); + values[1] = WASM_I32_VAL(Currency::size()); + memcpy(&buffer[0], c.data(), Currency::size()); + + int index = 0; + auto const result = getDataCurrency(runtime, ¶ms, index); + if (!result || result.value() != c || index != 2) + return false; + } + + return true; +} + +} // namespace test +// LCOV_EXCL_STOP + +} // namespace xrpl diff --git a/src/libxrpl/tx/wasm/WasmVM.cpp b/src/libxrpl/tx/wasm/WasmVM.cpp new file mode 100644 index 0000000000..7eda08f42b --- /dev/null +++ b/src/libxrpl/tx/wasm/WasmVM.cpp @@ -0,0 +1,219 @@ +#include + +#include +#include +#include // IWYU pragma: keep +#include +#include + +#include +#include +#include +#include +#ifdef _DEBUG +// #define DEBUG_OUTPUT 1 +#endif + +#include +#include + +#include + +namespace xrpl { +// WARNING: Per XLS-0102, the host functions registered here form a stable +// ABI. Their name, semantics, parameters, and return types must NEVER be +// changed, as there may always be a program that uses it. New host functions +// may be added and existing gas costs may be adjusted, but every such change +// must be gated by an amendment. +// See XLS-0102 §6.5 (Future-Proofing): +// https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0102-wasm-vm#65-future-proofing +static void +setCommonHostFunctions(HostFunctions& hfs, ImportVec& i) +{ + // clang-format off + WASM_IMPORT_FUNC2(i, getLedgerSqn, "ldgr_index", hfs, 60); + WASM_IMPORT_FUNC2(i, getParentLedgerTime, "parent_ldgr_time", hfs, 60); + WASM_IMPORT_FUNC2(i, getParentLedgerHash, "parent_ldgr_hash", hfs, 60); + WASM_IMPORT_FUNC2(i, getBaseFee, "base_fee", hfs, 60); + WASM_IMPORT_FUNC2(i, isAmendmentEnabled, "amendment_enabled", hfs, 100); + + WASM_IMPORT_FUNC2(i, cacheLedgerObj, "cache_le", hfs, 5'000); + WASM_IMPORT_FUNC2(i, getTxField, "tx_field", hfs, 70); + WASM_IMPORT_FUNC2(i, getCurrentLedgerObjField, "home_le_field", hfs, 70); + WASM_IMPORT_FUNC2(i, getLedgerObjField, "le_field", hfs, 70); + WASM_IMPORT_FUNC2(i, getTxNestedField, "tx_inner", hfs, 110); + WASM_IMPORT_FUNC2(i, getCurrentLedgerObjNestedField, "home_le_inner", hfs, 110); + WASM_IMPORT_FUNC2(i, getLedgerObjNestedField, "le_inner", hfs, 110); + WASM_IMPORT_FUNC2(i, getTxArrayLen, "tx_arr_len", hfs, 40); + WASM_IMPORT_FUNC2(i, getCurrentLedgerObjArrayLen, "home_le_arr_len", hfs, 40); + WASM_IMPORT_FUNC2(i, getLedgerObjArrayLen, "le_arr_len", hfs, 40); + WASM_IMPORT_FUNC2(i, getTxNestedArrayLen, "tx_inner_arr_len", hfs, 70); + WASM_IMPORT_FUNC2(i, getCurrentLedgerObjNestedArrayLen, "home_le_inner_arr_len", hfs, 70); + WASM_IMPORT_FUNC2(i, getLedgerObjNestedArrayLen, "le_inner_arr_len", hfs, 70); + + WASM_IMPORT_FUNC2(i, checkSignature, "check_sig", hfs, 300); + WASM_IMPORT_FUNC2(i, computeSha512HalfHash, "sha512_half", hfs, 2000); + + WASM_IMPORT_FUNC2(i, accountKeylet, "accountroot_id", hfs, 350); + WASM_IMPORT_FUNC2(i, ammKeylet, "amm_id", hfs, 450); + WASM_IMPORT_FUNC2(i, checkKeylet, "check_id", hfs, 350); + WASM_IMPORT_FUNC2(i, credentialKeylet, "credential_id", hfs, 350); + WASM_IMPORT_FUNC2(i, delegateKeylet, "delegate_id", hfs, 350); + WASM_IMPORT_FUNC2(i, depositPreauthKeylet, "deposit_preauth_id", hfs, 350); + WASM_IMPORT_FUNC2(i, didKeylet, "did_id", hfs, 350); + WASM_IMPORT_FUNC2(i, escrowKeylet, "escrow_id", hfs, 350); + WASM_IMPORT_FUNC2(i, trustLineKeylet, "trustline_id", hfs, 400); + WASM_IMPORT_FUNC2(i, mptokenIssuanceKeylet, "mpt_issuance_id", hfs, 350); + WASM_IMPORT_FUNC2(i, mptokenKeylet, "mptoken_id", hfs, 500); + WASM_IMPORT_FUNC2(i, nftokenOfferKeylet, "nft_offer_id", hfs, 350); + WASM_IMPORT_FUNC2(i, offerKeylet, "offer_id", hfs, 350); + WASM_IMPORT_FUNC2(i, oracleKeylet, "oracle_id", hfs, 350); + WASM_IMPORT_FUNC2(i, paychannelKeylet, "paychan_id", hfs, 350); + WASM_IMPORT_FUNC2(i, permissionedDomainKeylet, "permissioned_domain_id", hfs, 350); + WASM_IMPORT_FUNC2(i, signerListKeylet, "signers_id", hfs, 350); + WASM_IMPORT_FUNC2(i, ticketKeylet, "ticket_id", hfs, 350); + WASM_IMPORT_FUNC2(i, vaultKeylet, "vault_id", hfs, 350); + + WASM_IMPORT_FUNC2(i, getNFT, "nft_uri", hfs, 5'000); + WASM_IMPORT_FUNC2(i, getNFTIssuer, "nft_issuer", hfs, 70); + WASM_IMPORT_FUNC2(i, getNFTTaxon, "nft_taxon", hfs, 60); + WASM_IMPORT_FUNC2(i, getNFTFlags, "nft_flags", hfs, 60); + WASM_IMPORT_FUNC2(i, getNFTTransferFee, "nft_xfer_fee", hfs, 60); + WASM_IMPORT_FUNC2(i, getNFTSequence, "nft_serial", hfs, 60); + + WASM_IMPORT_FUNC (i, trace, hfs, 500); + WASM_IMPORT_FUNC2(i, traceNum, "trace_num", hfs, 500); + WASM_IMPORT_FUNC2(i, traceAccount, "trace_acct", hfs, 500); + WASM_IMPORT_FUNC2(i, traceFloat, "trace_xfloat", hfs, 500); + WASM_IMPORT_FUNC2(i, traceAmount, "trace_amt", hfs, 500); + + WASM_IMPORT_FUNC2(i, floatFromInt, "float_from_int", hfs, 100); + WASM_IMPORT_FUNC2(i, floatFromUint, "float_from_uint", hfs, 130); + WASM_IMPORT_FUNC2(i, floatFromSTAmount, "float_from_stamount", hfs, 150); + WASM_IMPORT_FUNC2(i, floatFromSTNumber, "float_from_stnumber", hfs, 150); + WASM_IMPORT_FUNC2(i, floatToInt, "float_to_int", hfs, 130); + WASM_IMPORT_FUNC2(i, floatToMantExp, "float_to_mant_exp", hfs, 130); + WASM_IMPORT_FUNC2(i, floatFromMantExp, "float_from_mant_exp", hfs, 100); + WASM_IMPORT_FUNC2(i, floatCompare, "float_cmp", hfs, 80); + WASM_IMPORT_FUNC2(i, floatAdd, "float_add", hfs, 160); + WASM_IMPORT_FUNC2(i, floatSubtract, "float_sub", hfs, 160); + WASM_IMPORT_FUNC2(i, floatMultiply, "float_mult", hfs, 300); + WASM_IMPORT_FUNC2(i, floatDivide, "float_div", hfs, 300); + WASM_IMPORT_FUNC2(i, floatRoot, "float_root", hfs, 5'500); + WASM_IMPORT_FUNC2(i, floatPower, "float_pow", hfs, 5'500); + // clang-format on +} + +ImportVec +createWasmImport(HostFunctions& hfs) +{ + ImportVec i; + + setCommonHostFunctions(hfs, i); + WASM_IMPORT_FUNC2(i, updateData, "set_data", hfs, 1000); + + return i; +} + +std::expected +runEscrowWasm( + Bytes const& wasmCode, + HostFunctions& hfs, + int64_t gasLimit, + std::string_view funcName, + std::vector const& params) +{ + // create VM and set cost limit + auto& vm = WasmEngine::instance(); + // vm.initMaxPages(MAX_PAGES); + + auto const ret = + vm.run(wasmCode, hfs, gasLimit, funcName, params, createWasmImport(hfs), hfs.getJournal()); + + if (!ret) + { +#ifdef DEBUG_OUTPUT + std::cout << ", error: " << ret.error().ter << std::endl; +#endif + // Carries the TER (tecOUT_OF_GAS / tecFAILED_PROCESSING / tecINTERNAL / + // temBAD_AMOUNT) and, when meaningful, the gas consumed. The caller is + // responsible for writing that gas to tx metadata. + return std::unexpected(ret.error()); + } + +#ifdef DEBUG_OUTPUT + std::cout << ", ret: " << ret->result << ", gas spent: " << ret->cost << std::endl; +#endif + return EscrowResult{.result = ret->result, .cost = ret->cost}; +} + +NotTEC +preflightEscrowWasm( + Bytes const& wasmCode, + HostFunctions& hfs, + std::string_view funcName, + std::vector const& params) +{ + // create VM and set cost limit + auto& vm = WasmEngine::instance(); + // vm.initMaxPages(MAX_PAGES); + + auto const ret = + vm.check(wasmCode, hfs, funcName, params, createWasmImport(hfs), hfs.getJournal()); + + return ret; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +WasmEngine::WasmEngine() : impl_(std::make_unique()) +{ +} + +WasmEngine& +WasmEngine::instance() +{ + static WasmEngine e; + return e; +} + +std::expected, WasmTER> +WasmEngine::run( + Bytes const& wasmCode, + HostFunctions& hfs, + int64_t gasLimit, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + beast::Journal j) +{ + return impl_->run(wasmCode, hfs, gasLimit, funcName, params, imports, j); +} + +NotTEC +WasmEngine::check( + Bytes const& wasmCode, + HostFunctions& hfs, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + beast::Journal j) +{ + return impl_->check(wasmCode, hfs, funcName, params, imports, j); +} + +void* +WasmEngine::newTrap(std::string const& msg) +{ + return impl_->newTrap(msg); +} + +// LCOV_EXCL_START +beast::Journal +WasmEngine::getJournal() const +{ + return impl_->getJournal(); +} +// LCOV_EXCL_STOP + +} // namespace xrpl diff --git a/src/libxrpl/tx/wasm/WasmiVM.cpp b/src/libxrpl/tx/wasm/WasmiVM.cpp new file mode 100644 index 0000000000..cfe54fccc2 --- /dev/null +++ b/src/libxrpl/tx/wasm/WasmiVM.cpp @@ -0,0 +1,958 @@ +#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 + +#ifdef _DEBUG +// #define DEBUG_OUTPUT 1 +#endif +// #define SHOW_CALL_TIME 1 + +namespace xrpl { + +wasm_trap_t* +HostFuncMain_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +namespace { + +void +printWasmError(std::string_view msg, wasm_trap_t* trap, beast::Journal jlog) +{ +#ifdef DEBUG_OUTPUT + auto& j = std::cerr; +#else + auto j = jlog.warn(); + if (jlog.active(beast::Severity::Warning)) +#endif + { + wasm_byte_vec_t errorMessage WASM_EMPTY_VEC; + + if (trap != nullptr) + wasm_trap_message(trap, &errorMessage); + + if (errorMessage.size != 0u) + { + j << "WASMI Error: " << msg << ", " + << std::string_view(errorMessage.data, errorMessage.size - 1); + } + else + { + j << "WASMI Error: " << msg; + } + + if (errorMessage.size != 0u) + wasm_byte_vec_delete(&errorMessage); + } + + if (trap != nullptr) + wasm_trap_delete(trap); + +#ifdef DEBUG_OUTPUT + j << std::endl; +#endif +} +// LCOV_EXCL_STOP + +// Extract a trap's message into a std::string (the only signal the C API gives +// for classification; see the trap-signal constants in WasmCommon.h). Does not +// take ownership of `trap`. +std::string +trapMessage(wasm_trap_t* trap) +{ + if (trap == nullptr) + return {}; // LCOV_EXCL_LINE + wasm_byte_vec_t msg WASM_EMPTY_VEC; + wasm_trap_message(trap, &msg); + std::string out; + if (msg.size != 0u) + { + // wasm_trap_message NUL-terminates, so drop the trailing NUL. + out.assign(msg.data, msg.size - 1); + wasm_byte_vec_delete(&msg); + } + return out; +} + +} // namespace + +class WasmiRuntimeWrapper : public WasmRuntimeWrapper +{ + InstanceWrapper& iw_; + +public: + WasmiRuntimeWrapper(InstanceWrapper& iw) : iw_(iw) + { + } + + Wmem + getMem() override + { + return iw_.getMem(); + } + + std::int64_t + getGas() override + { + return iw_.getGas(); + } + + std::int64_t + setGas(std::int64_t gas) override + { + return iw_.setGas(gas); + } + + std::int64_t + getTransferLimit() override + { + return iw_.getTransferLimit(); + } + + std::int64_t + setTransferLimit(std::int64_t x) override + { + return iw_.setTransferLimit(x); + } +}; + +InstancePtr +InstanceWrapper::init( + StorePtr& s, + ModulePtr& m, + WasmExternVec& expt, + WasmExternVec const& imports, + beast::Journal j) +{ + wasm_trap_t* trap = nullptr; + InstancePtr mi = InstancePtr( + wasm_instance_new(s.get(), m.get(), imports.get(), &trap), &wasm_instance_delete); + + if (!mi || (trap != nullptr)) + { + printWasmError("can't create instance", trap, j); + Throw("can't create instance"); + } + wasm_instance_exports(mi.get(), expt.get()); + return mi; +} + +InstanceWrapper& +InstanceWrapper::operator=(InstanceWrapper&& o) +{ + if (this == &o) + return *this; // LCOV_EXCL_LINE + + store_ = o.store_; + o.store_ = nullptr; + exports_ = std::move(o.exports_); + memIdx_ = o.memIdx_; + o.memIdx_ = -1; + instance_ = std::move(o.instance_); + + j_ = o.j_; + + return *this; +} + +FuncInfo +InstanceWrapper::getFunc(std::string_view funcName, WasmExporttypeVec const& exportTypes) const +{ + wasm_func_t const* f = nullptr; + wasm_functype_t const* ft = nullptr; + + if (!instance_) + Throw("no instance"); // LCOV_EXCL_LINE + + if (exportTypes.empty()) + Throw("no export"); // LCOV_EXCL_LINE + if (exportTypes.size() != exports_.size()) + Throw("invalid export"); // LCOV_EXCL_LINE + + for (unsigned i = 0; i < exportTypes.size(); ++i) + { + auto const* expType(exportTypes[i]); + + wasm_name_t const* name = wasm_exporttype_name(expType); + wasm_externtype_t const* exnType = wasm_exporttype_type(expType); + if (wasm_externtype_kind(exnType) == WASM_EXTERN_FUNC) + { + if (funcName != std::string_view(name->data, name->size)) + continue; + + auto const* exn(exports_[i]); + if (wasm_extern_kind(exn) != WASM_EXTERN_FUNC) + Throw("invalid export"); // LCOV_EXCL_LINE + + ft = wasm_externtype_as_functype_const(exnType); + f = wasm_extern_as_func_const(exn); + break; + } + } + + if ((f == nullptr) || (ft == nullptr)) + Throw("can't find function <" + std::string(funcName) + ">"); + + return {f, ft}; +} + +Wmem +InstanceWrapper::getMem() const +{ + if (memIdx_ >= 0) + { + auto* e(exports_[memIdx_]); + wasm_memory_t* mem = wasm_extern_as_memory(e); + return Wmem(wasm_memory_data(mem), wasm_memory_data_size(mem)); + } + + wasm_memory_t* mem = nullptr; + for (int i = 0; i < exports_.size(); ++i) + { + auto* e(exports_[i]); + if (wasm_extern_kind(e) == WASM_EXTERN_MEMORY) + { + memIdx_ = i; + mem = wasm_extern_as_memory(e); + break; + } + } + + if (mem == nullptr) + return {}; // LCOV_EXCL_LINE + + return Wmem(wasm_memory_data(mem), wasm_memory_data_size(mem)); +} + +std::int64_t +InstanceWrapper::getGas() const +{ + if (store_ == nullptr) + return -1; // LCOV_EXCL_LINE + std::uint64_t gas = 0; + wasm_store_get_fuel(store_, &gas); + return static_cast(gas); +} + +std::int64_t +InstanceWrapper::setGas(std::int64_t gas) const +{ + if (store_ == nullptr) + return -1; // LCOV_EXCL_LINE + + if (gas < 0) + gas = std::numeric_limits::max(); + wasmi_error_t* err = wasm_store_set_fuel(store_, static_cast(gas)); + if (err != nullptr) + { + // LCOV_EXCL_START + printWasmError("Can't set instance gas", nullptr, j_); + wasmi_error_delete(err); + return -1; + // LCOV_EXCL_STOP + } + + return gas; +} + +std::int64_t +InstanceWrapper::getTransferLimit() const +{ + if (store_ == nullptr) + return -1; // LCOV_EXCL_LINE + + return transferLimit_; +} + +std::int64_t +InstanceWrapper::setTransferLimit(std::int64_t x) +{ + if (store_ == nullptr) + return -1; // LCOV_EXCL_LINE + if (x < 0) + { + transferLimit_ = std::numeric_limits::max(); + } + else + { + transferLimit_ = x; + } + + return transferLimit_; +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +ModulePtr +ModuleWrapper::init(StorePtr& s, Bytes const& wasmBin, beast::Journal j) +{ + wasm_byte_vec_t const code{ + .size = wasmBin.size(), + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) + .data = const_cast(reinterpret_cast(wasmBin.data()))}; + ModulePtr m = ModulePtr(wasm_module_new(s.get(), &code), &wasm_module_delete); + if (!m) + throw std::runtime_error("can't create module"); + + return m; +} + +ModuleWrapper::ModuleWrapper( + StorePtr& s, + Bytes const& wasmBin, + bool instantiate, + ImportVec const& imports, + beast::Journal j) + : module_(init(s, wasmBin, j)), j_(j) +{ + wasm_module_exports(module_.get(), exportTypes_.get()); + auto wimports = buildImports(s, imports); + if (instantiate) + { + addInstance(s, wimports); + } +} + +// LCOV_EXCL_START +ModuleWrapper& +ModuleWrapper::operator=(ModuleWrapper&& o) +{ + if (this == &o) + return *this; + + module_ = std::move(o.module_); + instanceWrap_ = std::move(o.instanceWrap_); + exportTypes_ = std::move(o.exportTypes_); + j_ = o.j_; + + return *this; +} + +// LCOV_EXCL_STOP + +static WasmValtypeVec +makeImpParams(WasmImportFunc const& imp) +{ + auto const paramSize = imp.params.size(); + if (paramSize == 0u) + return {}; + + WasmValtypeVec v(paramSize); + + for (unsigned i = 0; i < paramSize; ++i) + { + auto const vt = imp.params[i]; + switch (vt) + { + case WasmTypes::WtI32: + v[i] = wasm_valtype_new_i32(); + break; + case WasmTypes::WtI64: + v[i] = wasm_valtype_new_i64(); + break; + // LCOV_EXCL_START + default: + throw std::runtime_error("invalid import type"); + // LCOV_EXCL_STOP + } + } + return v; +} + +static WasmValtypeVec +makeImpReturn(WasmImportFunc const& imp) +{ + if (!imp.result) + return {}; // LCOV_EXCL_LINE + + WasmValtypeVec v(1); + switch (*imp.result) + { + case WasmTypes::WtI32: + v[0] = wasm_valtype_new_i32(); + break; + // LCOV_EXCL_START + case WasmTypes::WtI64: + v[0] = wasm_valtype_new_i64(); + break; + default: + throw std::runtime_error("invalid return type"); + // LCOV_EXCL_STOP + } + return v; +} + +WasmExternVec +ModuleWrapper::buildImports(StorePtr& s, ImportVec const& imports) const +{ + WasmImporttypeVec importTypes; + wasm_module_imports(module_.get(), importTypes.get()); + + if (importTypes.empty()) + return {}; + if (imports.empty()) + Throw("Empty imports"); + + WasmExternVec wimports(importTypes.size()); + + unsigned impCnt = 0; + for (unsigned i = 0; i < importTypes.size(); ++i) + { + wasm_importtype_t const* importType = importTypes[i]; + + // wasm_name_t const* mn = wasm_importtype_module(importtype); + // auto modName = std::string_view(mn->data, mn->num_elems); + wasm_name_t const* fn = wasm_importtype_name(importType); + auto fieldName = std::string_view(fn->data, fn->size); + + wasm_externkind_t const itype = wasm_externtype_kind(wasm_importtype_type(importType)); + if (itype != WASM_EXTERN_FUNC) + { + Throw( + "Invalid import type " + std::to_string(itype)); // LCOV_EXCL_LINE + } + + // for multi-module support + // if ((W_ENV != modName) && (W_HOST_LIB != modName)) + // continue; + + auto const it = imports.find(fieldName); + if (it == imports.end()) + { + printWasmError("Import not found: " + std::string(fieldName), nullptr, j_); + continue; // print all missed import + } + + WasmUserData const& obj = it->second; + WasmImportFunc const& imp = obj.second; + + WasmValtypeVec params(makeImpParams(imp)); + WasmValtypeVec results(makeImpReturn(imp)); + + std::unique_ptr const ftype( + wasm_functype_new(params.get(), results.get()), &wasm_functype_delete); + + params.release(); + results.release(); + + wasm_func_t* func = + wasm_func_new_with_env(s.get(), ftype.get(), HostFuncMain_wrap, (void*)&obj, nullptr); + if (func == nullptr) + { + Throw( + "can't create import function " + std::string(imp.name)); // LCOV_EXCL_LINE + } + + wimports[i] = wasm_func_as_extern(func); + ++impCnt; + } + + if (impCnt != importTypes.size()) + { + printWasmError( + std::string("Imports not finished: ") + std::to_string(impCnt) + "/" + + std::to_string(importTypes.size()), + nullptr, + j_); + Throw("Missing imports"); + } + + return wimports; +} + +wasm_functype_t const* +ModuleWrapper::getFuncType(std::string_view funcName) const +{ + for (size_t i = 0; i < exportTypes_.size(); i++) + { + auto const* expType(exportTypes_[i]); + wasm_name_t const* name = wasm_exporttype_name(expType); + wasm_externtype_t const* exnType = wasm_exporttype_type(expType); + if (wasm_externtype_kind(exnType) == WASM_EXTERN_FUNC && + funcName == std::string_view(name->data, name->size)) + { + return wasm_externtype_as_functype_const(exnType); + } + } + + throw std::runtime_error("can't find function <" + std::string(funcName) + ">"); +} + +// int +// my_module_t::delInstance(int i) +// { +// if (i >= mod_inst.size()) +// return -1; +// if (!mod_inst[i]) +// mod_inst[i] = my_mod_inst_t(); +// return i; +// } + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// void +// WasmiEngine::clearModules() +// { +// modules.clear(); +// store.reset(); // to free the memory before creating new store +// store = {wasm_store_new(engine.get()), &wasm_store_delete}; +// } + +std::unique_ptr +WasmiEngine::init() +{ + wasm_config_t* config = wasm_config_new(); + if (config == nullptr) + { + return std::unique_ptr{ + nullptr, &wasm_engine_delete}; // LCOV_EXCL_LINE + } + wasmi_config_consume_fuel_set(config, true); + wasmi_config_ignore_custom_sections_set(config, true); + wasmi_config_wasm_mutable_globals_set(config, false); + wasmi_config_wasm_multi_value_set(config, false); + wasmi_config_wasm_sign_extension_set(config, false); + wasmi_config_wasm_saturating_float_to_int_set(config, false); + wasmi_config_wasm_bulk_memory_set(config, false); + wasmi_config_wasm_reference_types_set(config, false); + wasmi_config_wasm_tail_call_set(config, false); + wasmi_config_wasm_extended_const_set(config, false); + wasmi_config_floats_set(config, false); + wasmi_config_wasm_multi_memory_set(config, false); + wasmi_config_wasm_custom_page_sizes_set(config, false); + wasmi_config_wasm_memory64_set(config, false); + wasmi_config_wasm_wide_arithmetic_set(config, false); + + return std::unique_ptr( + wasm_engine_new_with_config(config), &wasm_engine_delete); +} + +int +WasmiEngine::addModule( + Bytes const& wasmCode, + bool instantiate, + ImportVec const& imports, + int64_t gas) +{ + moduleWrap_.reset(); + store_.reset(); // to free the memory before creating new store + store_ = {wasm_store_new_with_memory_max_pages(engine_.get(), maxPages), &wasm_store_delete}; + + if (gas < 0) + gas = std::numeric_limits::max(); + wasmi_error_t* err = wasm_store_set_fuel(store_.get(), static_cast(gas)); + if (err != nullptr) + { + // LCOV_EXCL_START + printWasmError("Error setting gas", nullptr, j_); + wasmi_error_delete(err); + throw std::runtime_error("can't set gas"); + // LCOV_EXCL_STOP + } + + moduleWrap_ = std::make_unique(store_, wasmCode, instantiate, imports, j_); + + if (!moduleWrap_) + throw std::runtime_error("can't create module wrapper"); // LCOV_EXCL_LINE + + return moduleWrap_ ? 0 : -1; +} + +// int +// WasmiEngine::addInstance() +// { +// return module->addInstance(store.get()); +// } + +std::vector +WasmiEngine::convertParams(std::vector const& params) +{ + std::vector v; + v.reserve(params.size()); + for (auto const& p : params) + { + switch (p.type) + { + case WasmTypes::WtI32: + v.push_back(WASM_I32_VAL(p.of.i32)); + break; + // LCOV_EXCL_START + case WasmTypes::WtI64: + v.push_back(WASM_I64_VAL(p.of.i64)); + break; + default: + throw std::runtime_error( + "unknown parameter type: " + std::to_string(static_cast(p.type))); + break; + // LCOV_EXCL_STOP + } + } + + return v; +} + +int +WasmiEngine::compareParamTypes(wasm_valtype_vec_t const* ftp, std::vector const& p) +{ + if (ftp->size != p.size()) + return std::min(ftp->size, p.size()); + + for (unsigned i = 0; i < ftp->size; ++i) + { + auto const t1 = wasm_valtype_kind(ftp->data[i]); + auto const t2 = p[i].kind; + if (t1 != t2) + return i; + } + + return -1; +} + +// LCOV_EXCL_START +void +WasmiEngine::addParam(std::vector& in, int32_t p) +{ + in.emplace_back(); + auto& el(in.back()); + memset(&el, 0, sizeof(el)); + el = WASM_I32_VAL(p); // WASM_I32; +} + +// LCOV_EXCL_STOP + +void +WasmiEngine::addParam(std::vector& in, int64_t p) +{ + in.emplace_back(); + auto& el(in.back()); + el = WASM_I64_VAL(p); +} + +template +WasmiResult +WasmiEngine::call(std::string_view func, Types&&... args) +{ + // Lookup our export function + auto f = getFunc(func); + return call(f, std::forward(args)...); +} + +template +WasmiResult +WasmiEngine::call(FuncInfo const& f, Types&&... args) +{ + std::vector in; + return call(f, in, std::forward(args)...); +} + +#ifdef SHOW_CALL_TIME +static inline uint64_t +usecs() +{ + uint64_t x = std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()) + .count(); + return x; +} +#endif + +template +WasmiResult +WasmiEngine::call(FuncInfo const& f, std::vector& in) +{ + WasmiResult ret(NR); + wasm_val_vec_t const inv = in.empty() ? wasm_val_vec_t WASM_EMPTY_VEC + : wasm_val_vec_t{.size = in.size(), .data = in.data()}; + +#ifdef SHOW_CALL_TIME + auto const start = usecs(); +#endif + + wasm_trap_t* trap = wasm_func_call(f.first, &inv, ret.r.get()); + +#ifdef SHOW_CALL_TIME + auto const finish = usecs(); + auto const delta_ms = (finish - start) / 1000; + std::cout << "wasm_func_call: " << delta_ms << "ms" << std::endl; +#endif + + if (trap) + { + // Classify the trap into a TER by matching tokens as substrings of the + // message (see the trap-signal constants in WasmCommon.h for why). + std::string const msg = trapMessage(trap); + auto const has = [&msg](std::string_view token) { return msg.contains(token); }; + if (has(hfErrInternal)) + { + ret.ter = tecINTERNAL; + } + else if (has(hfErrOutOfGas) || has(wasmiTrapOutOfFuel)) + { + ret.ter = tecOUT_OF_GAS; + } + else + { + ret.ter = tecFAILED_PROCESSING; + } + printWasmError("failure to call func", trap, j_); + } + + return ret; +} + +template +WasmiResult +WasmiEngine::call(FuncInfo const& f, std::vector& in, std::int32_t p, Types&&... args) +{ + addParam(in, p); + return call(f, in, std::forward(args)...); +} + +template +WasmiResult +WasmiEngine::call(FuncInfo const& f, std::vector& in, std::int64_t p, Types&&... args) +{ + addParam(in, p); + return call(f, in, std::forward(args)...); +} + +template +WasmiResult +WasmiEngine::call(FuncInfo const& f, std::vector& in, Bytes const& p, Types&&... args) +{ + return call(f, in, p.data(), p.size(), std::forward(args)...); +} + +static inline void +checkImports(ImportVec const& imports, HostFunctions* hfs) +{ + for (auto const& obj : imports) + { + if (hfs != &obj.second.first.get()) + Throw("Imports hf unsync"); + } +} + +std::expected, WasmTER> +WasmiEngine::run( + Bytes const& wasmCode, + HostFunctions& hfs, + int64_t gas, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + beast::Journal j) +{ + if (gas <= 0) + return std::unexpected(WasmTER{.ter = temBAD_AMOUNT, .cost = std::nullopt}); + + try + { + checkImports(imports, &hfs); + return runHlp(wasmCode, hfs, gas, funcName, params, imports, j); + } + catch (std::exception const& e) + { + printWasmError(std::string("exception: ") + e.what(), nullptr, j); + } + // LCOV_EXCL_START + catch (...) + { + printWasmError(std::string("exception: unknown"), nullptr, j); + } + // LCOV_EXCL_STOP + // An exception escaping the engine is an xrpld-side fault -> tecINTERNAL, + // no gas. Genuine wasm faults don't throw; they surface as traps in runHlp. + return std::unexpected(WasmTER{.ter = tecINTERNAL, .cost = std::nullopt}); +} + +std::expected, WasmTER> +WasmiEngine::runHlp( + Bytes const& wasmCode, + HostFunctions& hfs, + int64_t gas, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + beast::Journal j) +{ + // currently only 1 module support, possible parallel UT run + std::scoped_lock const lg(m_); + j_ = j; + + if (wasmCode.empty()) + throw std::runtime_error("empty module"); + if (!hfs.checkSelf()) + throw std::runtime_error("hfs isn't clean"); + + // Create and instantiate the module. + [[maybe_unused]] int const m = addModule(wasmCode, true, imports, gas); + + if (!moduleWrap_ || !moduleWrap_->getInstance()) + throw std::runtime_error("no instance"); // LCOV_EXCL_LINE + + auto clearRT = [](HostFunctions* p) { p->resetRT(); }; + std::unique_ptr const clearGuard(&hfs, clearRT); + WasmiRuntimeWrapper iw(getRT()); + hfs.setRT(iw); + + // Call main + auto const f = getFunc(!funcName.empty() ? funcName : "_start"); + auto const* ftp = wasm_functype_params(f.second); + + // not const because passed directly to VM function (which accept non + // const) + auto p = convertParams(params); + + if (int const comp = compareParamTypes(ftp, p); comp >= 0) + throw std::runtime_error("invalid parameter type #" + std::to_string(comp)); + + auto const res = call<1>(f, p); + + if (gas == -1) + gas = std::numeric_limits::max(); + + if (res.ter.has_value()) + { + // call() already classified the trap (see WasmiEngine::call). + // tecINTERNAL is an xrpld-side bug: report no gas. + if (*res.ter == tecINTERNAL) + return std::unexpected(WasmTER{.ter = tecINTERNAL, .cost = std::nullopt}); + + // Out-of-gas / wasm faults report gas (caller writes it to metadata). + // Force fuel to 0 on out-of-gas so cost is the full limit (wasmi leaves + // nonzero leftover fuel on its own out-of-fuel trap). + if (*res.ter == tecOUT_OF_GAS) + iw.setGas(0); + + return std::unexpected(WasmTER{.ter = *res.ter, .cost = gas - moduleWrap_->getGas()}); + } + + if (res.r.empty()) + { + Throw( + "<" + std::string(funcName) + "> return nothing"); // LCOV_EXCL_LINE + } + + if (res.r[0].kind != WASM_I32) + { + Throw( + "<" + std::string(funcName) + + "> return type mismatch, ret: " + std::to_string(static_cast(res.r[0].kind))); + } + + WasmResult const ret{.result = res.r[0].of.i32, .cost = gas - moduleWrap_->getGas()}; + + // #ifdef DEBUG_OUTPUT + // auto& j = std::cerr; + // #else + // auto j = j_.debug(); + // #endif + // j << "WASMI Res: " << ret.result << " cost: " << ret.cost << std::endl; + + return ret; +} + +NotTEC +WasmiEngine::check( + Bytes const& wasmCode, + HostFunctions& hfs, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + beast::Journal j) +{ + try + { + checkImports(imports, &hfs); + return checkHlp(wasmCode, hfs, funcName, params, imports, j); + } + catch (std::exception const& e) + { + printWasmError(std::string("exception: ") + e.what(), nullptr, j); + } + // LCOV_EXCL_START + catch (...) + { + printWasmError(std::string("exception: unknown"), nullptr, j); + } + // LCOV_EXCL_STOP + + return temBAD_WASM; +} + +NotTEC +WasmiEngine::checkHlp( + Bytes const& wasmCode, + HostFunctions& hfs, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + beast::Journal j) +{ + // currently only 1 module support, possible parallel UT run + std::scoped_lock const lg(m_); + j_ = j; + + // Create and instantiate the module. + if (wasmCode.empty()) + throw std::runtime_error("empty module"); + + int const m = addModule(wasmCode, false, imports, -1); + if ((m < 0) || !moduleWrap_) + throw std::runtime_error("no module"); // LCOV_EXCL_LINE + + // Looking for a func and compare parameter types + auto const f = moduleWrap_->getFuncType(!funcName.empty() ? funcName : "_start"); + auto const* ftp = wasm_functype_params(f); + auto const p = convertParams(params); + + if (int const comp = compareParamTypes(ftp, p); comp >= 0) + throw std::runtime_error("invalid parameter type #" + std::to_string(comp)); + + return tesSUCCESS; +} + +wasm_trap_t* +WasmiEngine::newTrap(std::string const& txt) +{ + static char empty[1] = {0}; + wasm_message_t msg = {.size = 1, .data = empty}; + + if (!txt.empty()) + wasm_name_new(&msg, txt.size() + 1, txt.c_str()); // include 0 + + wasm_trap_t* trap = wasm_trap_new(store_.get(), &msg); // NOLINT + + if (!txt.empty()) + wasm_byte_vec_delete(&msg); + + return trap; +} + +} // namespace xrpl diff --git a/src/test/app/HostFuncImpl_test.cpp b/src/test/app/HostFuncImpl_test.cpp new file mode 100644 index 0000000000..8311aae638 --- /dev/null +++ b/src/test/app/HostFuncImpl_test.cpp @@ -0,0 +1,6252 @@ + +#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 Bytes +toBytes(std::uint8_t value) +{ + return {value}; +} + +static Bytes +toBytes(std::uint16_t value) +{ + auto const* b = reinterpret_cast(&value); + auto const* e = reinterpret_cast(&value + 1); + return Bytes{b, e}; +} + +static Bytes +toBytes(std::uint32_t value) +{ + auto const* b = reinterpret_cast(&value); + auto const* e = reinterpret_cast(&value + 1); + return Bytes{b, e}; +} + +static Bytes +toBytes(uint256 const& value) +{ + return Bytes{value.begin(), value.end()}; +} + +static Bytes +toBytes(Issue const& issue) +{ + Serializer s; + s.addBitString(issue.currency); + if (!isXRP(issue.currency)) + s.addBitString(issue.account); + auto const data = s.getData(); + return data; +} + +static Bytes +toBytes(Asset const& asset) +{ + if (asset.holds()) + return toBytes(asset.get()); + + auto const& mptIssue = asset.get(); + auto const& mptID = mptIssue.getMptID(); + return Bytes{mptID.cbegin(), mptID.cend()}; +} + +static Bytes +toBytes(STAmount const& amount) +{ + Serializer msg; + amount.add(msg); + auto const data = msg.getData(); + + return data; +} + +static Bytes +toBytes(STNumber const& number) +{ + Serializer msg; + number.add(msg); + auto const data = msg.getData(); + + return data; +} + +static ApplyContext +createApplyContext( + test::jtx::Env& env, + OpenView& ov, + beast::Journal j, + STTx const& tx = STTx(ttESCROW_FINISH, [](STObject&) {})) +{ + ApplyContext ac{env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, TapNone, j}; + return ac; +} + +static ApplyContext +createApplyContext( + test::jtx::Env& env, + OpenView& ov, + STTx const& tx = STTx(ttESCROW_FINISH, [](STObject&) {})) +{ + return createApplyContext(env, ov, env.journal, tx); +} + +class VirtualRuntime : public WasmRuntimeWrapper +{ + Bytes buffer_; + std::int64_t gas_ = 1'000'000; + 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 +} + +constexpr int64_t min64 = std::numeric_limits::min(); +constexpr int64_t max64 = std::numeric_limits::max(); +constexpr int32_t floatSize = 12; + +struct HostFuncImpl_test : public beast::unit_test::Suite +{ + void + testGetLedgerSqn() + { + testcase("getLedgerSqn"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + VirtualRuntime vrt; + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + { + // hfs.getLedgerSqn(); + WasmValVec params(2), result(1); + auto* trap = ww(&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); + } + } + + void + testGetParentLedgerTime() + { + testcase("getParentLedgerTime"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + VirtualRuntime vrt; + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + { + // hfs.getParentLedgerTime(); + WasmValVec params(2), result(1); + auto* trap = + ww(&import.at("parent_ldgr_time"), params, result, 0, sizeof(std::uint32_t)); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == sizeof(std::uint32_t)) && + BEAST_EXPECT( + vrt.getUint32(params, 0) == + env.current()->parentCloseTime().time_since_epoch().count()); + } + } + + void + testGetParentLedgerHash() + { + testcase("getParentLedgerHash"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + VirtualRuntime vrt; + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + { + // hfs.getParentLedgerHash(); + WasmValVec params(2), result(1); + auto* trap = ww(&import.at("parent_ldgr_hash"), params, result, 0, uint256::size()); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == uint256::size()); + auto const resultBytes = vrt.getBytes(params, 0); + auto const expectedHash = env.current()->header().parentHash; + BEAST_EXPECT( + resultBytes.size() == uint256::size() && + std::memcmp(resultBytes.data(), expectedHash.data(), uint256::size()) == 0); + } + } + + void + testGetBaseFee() + { + testcase("getBaseFee"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + VirtualRuntime vrt; + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + // hfs.getBaseFee(); + { + WasmValVec params(2), result(1); + auto* trap = ww(&import.at("base_fee"), params, result, 0, sizeof(std::uint32_t)); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == sizeof(std::uint32_t)) && + BEAST_EXPECT(vrt.getUint32(params, 0) == env.current()->fees().base.drops()); + } + } + + void + testIsAmendmentEnabled() + { + testcase("isAmendmentEnabled"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + VirtualRuntime vrt; + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + // Use featureTokenEscrow for testing + auto const amendmentId = featureTokenEscrow; + + // hfs.isAmendmentEnabled(amendmentId); + { + WasmValVec params(2), result(1); + vrt.setBytes(0, amendmentId.data(), uint256::size()); + auto* trap = ww(&import.at("amendment_enabled"), params, result, 0, uint256::size()); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 1); + } + + std::string const amendmentName = "TokenEscrow"; + // hfs.isAmendmentEnabled(amendmentName); + { + WasmValVec params(2), result(1); + vrt.setBytes(0, amendmentName.data(), amendmentName.size()); + auto* trap = + ww(&import.at("amendment_enabled"), params, result, 0, amendmentName.size()); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 1); + } + + uint256 const fakeId; + // hfs.isAmendmentEnabled(fakeId); + { + WasmValVec params(2), result(1); + vrt.setBytes(0, fakeId.data(), uint256::size()); + auto* trap = ww(&import.at("amendment_enabled"), params, result, 0, uint256::size()); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 0); + } + + std::string const fakeName = "FakeAmendment"; + // hfs.isAmendmentEnabled(fakeName); + { + WasmValVec params(2), result(1); + vrt.setBytes(0, fakeName.data(), fakeName.size()); + auto* trap = ww(&import.at("amendment_enabled"), params, result, 0, fakeName.size()); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 0); + } + } + + void + testCacheLedgerObj() + { + testcase("cacheLedgerObj"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, 2); + auto const accountKeylet = keylet::account(env.master); + { + VirtualRuntime vrt; + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + // hfs.cacheLedgerObj(accountKeylet.key, -1); + { + WasmValVec params(3), result(1); + vrt.setBytes(0, accountKeylet.key.data(), uint256::size()); + auto* trap = ww(&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 == static_cast(HostFunctionError::SlotOutRange)); + } + + // hfs.cacheLedgerObj(accountKeylet.key, 257); + { + WasmValVec params(3), result(1); + vrt.setBytes(0, accountKeylet.key.data(), uint256::size()); + auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 257); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT( + result[0].of.i32 == static_cast(HostFunctionError::SlotOutRange)); + } + + // hfs.cacheLedgerObj(dummyEscrow.key, 0); + { + WasmValVec params(3), result(1); + vrt.setBytes(0, dummyEscrow.key.data(), uint256::size()); + auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 0); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT( + result[0].of.i32 == + static_cast(HostFunctionError::LedgerObjNotFound)); + } + + // hfs.cacheLedgerObj(accountKeylet.key, 0); + { + WasmValVec params(3), result(1); + vrt.setBytes(0, accountKeylet.key.data(), uint256::size()); + auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 0); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 1); + } + + vrt.setGas(2'000'000); + for (int i = 1; i <= 256; ++i) + { + // hfs.cacheLedgerObj(accountKeylet.key, i); + WasmValVec params(3), result(1); + vrt.setBytes(0, accountKeylet.key.data(), uint256::size()); + auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), i); + + if (!(BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECTS( + result[0].of.i32 == i, + "result: " + std::to_string(result[0].of.i32) + + ", expected: " + std::to_string(i)))) + break; + } + + // hfs.cacheLedgerObj(accountKeylet.key, 0); + { + WasmValVec params(3), result(1); + vrt.setBytes(0, accountKeylet.key.data(), uint256::size()); + auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 0); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT( + result[0].of.i32 == static_cast(HostFunctionError::SlotsFull)); + } + } + + { + VirtualRuntime vrt; + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + vrt.setGas(2'000'000); + for (int i = 1; i <= 256; ++i) + { + // hfs.cacheLedgerObj(accountKeylet.key, 0); + WasmValVec params(3), result(1); + vrt.setBytes(0, accountKeylet.key.data(), uint256::size()); + auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 0); + + if (!(BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECTS( + result[0].of.i32 == i, + "result: " + std::to_string(result[0].of.i32) + + ", expected: " + std::to_string(i)))) + break; + } + + // hfs.cacheLedgerObj(accountKeylet.key, 0); + { + WasmValVec params(3), result(1); + vrt.setBytes(0, accountKeylet.key.data(), uint256::size()); + auto* trap = ww(&import.at("cache_le"), params, result, 0, uint256::size(), 0); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT( + result[0].of.i32 == static_cast(HostFunctionError::SlotsFull)); + } + } + } + + void + testGetTxField() + { + testcase("getTxField"); + using namespace test::jtx; + + std::string const credIdHex = + "0011223344556677889900112233445566778899001122334455667788990011"; + uint256 credId; + BEAST_EXPECT(credId.parseHex(credIdHex)); + + Env env{*this}; + OpenView ov{*env.current()}; + STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) { + obj.setAccountID(sfAccount, env.master.id()); + obj.setAccountID(sfOwner, env.master.id()); + obj.setFieldU32(sfOfferSequence, env.seq(env.master)); + obj.setFieldArray(sfMemos, STArray{}); + STVector256 credIds; + credIds.pushBack(credId); + obj.setFieldV256(sfCredentialIDs, credIds); + }); + ApplyContext ac = createApplyContext(env, ov, stx); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + + { + VirtualRuntime vrt; + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + // hfs.getTxField(sfAccount); + { + WasmValVec params(3), result(1); + auto* trap = + ww(&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 + testGetCurrentLedgerObjField() + { + testcase("getCurrentLedgerObjField"); + using namespace test::jtx; + using namespace std::chrono; + + Env env{*this}; + + // Fund the account and create an escrow so the ledger object exists + env(escrow::create(env.master, env.master, XRP(100)), escrow::kFinishTime(env.now() + 1s)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + // Find the escrow ledger object + auto const escrowKeylet = keylet::escrow(env.master, env.seq(env.master) - 1); + BEAST_EXPECT(env.le(escrowKeylet)); + + VirtualRuntime vrt; + WasmHostFunctionsImpl hfs(ac, escrowKeylet); + + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + // hfs.getCurrentLedgerObjField(sfAccount); + { + WasmValVec params(3), result(1); + auto* trap = + ww(&import.at("home_le_field"), params, result, sfAccount.getCode(), 0, 256); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); + if (BEAST_EXPECTS(result[0].of.i32 > 0, std::to_string(result[0].of.i32))) + { + auto accountBytes = vrt.getBytes(params, 1); + accountBytes.resize(result[0].of.i32); + BEAST_EXPECT(std::ranges::equal(accountBytes, env.master.id())); + } + } + + // hfs.getCurrentLedgerObjField(sfAmount); + { + WasmValVec params(3), result(1); + auto* trap = + ww(&import.at("home_le_field"), params, result, sfAmount.getCode(), 0, 256); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); + if (BEAST_EXPECT(result[0].of.i32 > 0)) + { + auto amountBytes = vrt.getBytes(params, 1); + amountBytes.resize(result[0].of.i32); + BEAST_EXPECT(amountBytes == toBytes(XRP(100))); + } + } + + // hfs.getCurrentLedgerObjField(sfPreviousTxnID); + { + WasmValVec params(3), result(1); + auto* trap = + ww(&import.at("home_le_field"), params, result, sfPreviousTxnID.getCode(), 0, 256); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32); + if (BEAST_EXPECT(result[0].of.i32 > 0)) + { + auto previousTxnIdBytes = vrt.getBytes(params, 1); + previousTxnIdBytes.resize(result[0].of.i32); + BEAST_EXPECT(previousTxnIdBytes == toBytes(env.tx()->getTransactionID())); + } + } + + // hfs.getCurrentLedgerObjField(sfOwner); + { + WasmValVec params(3), result(1); + auto* trap = ww(&import.at("home_le_field"), params, result, sfOwner.getCode(), 0, 256); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT( + result[0].of.i32 == static_cast(HostFunctionError::FieldNotFound)); + } + + { + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master) + 5); + VirtualRuntime vrt2; + WasmHostFunctionsImpl hfs2(ac, dummyEscrow); + + auto import2 = xrpl::createWasmImport(hfs2); + hfs2.setRT(vrt2); + + // hfs2.getCurrentLedgerObjField(sfAccount); + { + WasmValVec params(3), result(1); + auto* trap = + ww(&import2.at("home_le_field"), params, result, sfAccount.getCode(), 0, 256); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT( + result[0].of.i32 == + static_cast(HostFunctionError::LedgerObjNotFound)); + } + } + } + + void + testGetLedgerObjField() + { + testcase("getLedgerObjField"); + using namespace test::jtx; + using namespace std::chrono; + + Env env{*this}; + // Fund the account and create an escrow so the ledger object exists + env(escrow::create(env.master, env.master, XRP(100)), escrow::kFinishTime(env.now() + 1s)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const accountKeylet = keylet::account(env.master.id()); + auto const escrowKeylet = keylet::escrow(env.master.id(), env.seq(env.master) - 1); + VirtualRuntime vrt; + WasmHostFunctionsImpl hfs(ac, escrowKeylet); + + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + // hfs.cacheLedgerObj(accountKeylet.key, 1); + { + WasmValVec params(3), result(1); + vrt.setBytes(0, accountKeylet.key.data(), uint256::size()); + auto* trap = ww(&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, 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, 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, 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, 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, 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, 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, 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, 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, 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 + testUpdateData() + { + testcase("updateData"); + using namespace test::jtx; + + Env env{*this}; + env(escrow::create(env.master, env.master, XRP(100)), + escrow::kFinishTime(env.now() + std::chrono::seconds(1))); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const escrowKeylet = keylet::escrow(env.master, env.seq(env.master) - 1); + VirtualRuntime vrt; + WasmHostFunctionsImpl hfs(ac, escrowKeylet); + + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + // Should succeed for small data + Bytes data(10, 0x42); + // hfs.updateData(Slice(data.data(), data.size())); + { + vrt.setBytes(0, data.data(), data.size()); + WasmValVec params(2), result(1); + auto* trap = ww(&import.at("set_data"), params, result, 0, data.size()); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == data.size()); + BEAST_EXPECT(hfs.getData() && *hfs.getData() == data); + } + + // Should fail for too large data + Bytes bigData(kMaxWasmDataLength + 1, 0x42); + // hfs.updateData(Slice(bigData.data(), bigData.size())); + { + vrt.setBytes(0, bigData.data(), bigData.size()); + WasmValVec params(2), result(1); + auto* trap = ww(&import.at("set_data"), params, result, 0, bigData.size()); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT( + result[0].of.i32 == hfErrorToInt(HostFunctionError::DataFieldTooLarge)); + } + } + + void + testCheckSignature() + { + testcase("checkSignature"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + VirtualRuntime vrt; + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + // Generate a keypair and sign a message + auto const kp = generateKeyPair(KeyType::Secp256k1, randomSeed()); + PublicKey const& pk = kp.first; + SecretKey const& sk = kp.second; + std::string const& message = "hello signature"; + auto const sig = sign(pk, sk, Slice(message.data(), message.size())); + + // Should succeed for valid signature + { + // hfs.checkSignature( + // Slice(message.data(), message.size()), + // Slice(sig.data(), sig.size()), + // Slice(pk.data(), pk.size())); + vrt.setBytes(0, message.data(), message.size()); + vrt.setBytes(256, sig.data(), sig.size()); + vrt.setBytes(512, pk.data(), pk.size()); + WasmValVec params(6), result(1); + auto* trap = + ww(&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, 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 + testKeyletFunctions() + { + testcase("keylet functions"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + VirtualRuntime vrt; + + auto const usdIssue = env.master["USD"].issue(); + auto const masterID = env.master.id(); + auto const baseMpt = makeMptID(1, masterID); + + auto imp = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + // Lambda to compare a Bytes (std::vector) to a keylet + auto compareKeylet = [](std::vector const& bytes, Keylet const& kl) { + return std::ranges::equal(bytes, kl.key); + }; + + { + auto const expected = keylet::account(masterID); + WasmValVec params(4), result(1); + auto* trap = ww(&imp.at("accountroot_id"), params, result, masterID, 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 2); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = ww(&imp.at("accountroot_id"), params, result, xrpAccount(), 1024, 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + } + + { + auto const expected = keylet::amm(xrpIssue(), usdIssue); + WasmValVec params(6), result(1); + + auto* trap = ww(&imp.at("amm_id"), params, result, xrpIssue(), usdIssue, 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 4); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = ww(&imp.at("amm_id"), params, result, xrpIssue(), xrpIssue(), 1024, 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); + + auto* trap3 = ww(&imp.at("amm_id"), params, result, baseMpt, xrpIssue(), 1024, 32); + BEAST_EXPECT( + !trap3 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); + } + + { + auto const expected = keylet::check(masterID, 1u); + WasmValVec params(6), result(1); + auto* trap = ww(&imp.at("check_id"), params, result, masterID, toBytes(1u), 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 4); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = + ww(&imp.at("check_id"), params, result, xrpAccount(), toBytes(1u), 1024, 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + } + + std::string const credTypeStr = "test"; + Slice const credType(credTypeStr.data(), credTypeStr.size()); + Account const alice("alice"); + { + auto const expected = keylet::credential(masterID, masterID, credType); + WasmValVec params(8), result(1); + auto* trap = ww( + &imp.at("credential_id"), params, result, masterID, masterID, credType, 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 6); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + std::string_view constexpr longCredTypeStr = + "abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]" + "asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p"; + Slice const longCredType(longCredTypeStr.data(), longCredTypeStr.size()); + static_assert(longCredTypeStr.size() > kMaxCredentialTypeLength); + auto* trap2 = + ww(&imp.at("credential_id"), + params, + result, + masterID, + alice.id(), + longCredType, + 1024, + 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); + + auto* trap3 = + ww(&imp.at("credential_id"), + params, + result, + xrpAccount(), + alice.id(), + credType, + 1024, + 32); + BEAST_EXPECT( + !trap3 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + + auto* trap4 = + ww(&imp.at("credential_id"), + params, + result, + masterID, + xrpAccount(), + credType, + 1024, + 32); + BEAST_EXPECT( + !trap4 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + } + + { + auto const expected = keylet::did(masterID); + WasmValVec params(4), result(1); + auto* trap = ww(&imp.at("did_id"), params, result, masterID, 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 2); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = ww(&imp.at("did_id"), params, result, xrpAccount(), 1024, 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + } + + { + auto const expected = keylet::delegate(masterID, alice.id()); + WasmValVec params(6), result(1); + auto* trap = ww(&imp.at("delegate_id"), params, result, masterID, alice.id(), 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 4); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = ww(&imp.at("delegate_id"), params, result, masterID, masterID, 1024, 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); + + auto* trap3 = + ww(&imp.at("delegate_id"), params, result, masterID, xrpAccount(), 1024, 32); + BEAST_EXPECT( + !trap3 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + + auto* trap4 = + ww(&imp.at("delegate_id"), params, result, xrpAccount(), masterID, 1024, 32); + BEAST_EXPECT( + !trap4 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + } + + { + auto const expected = keylet::depositPreauth(masterID, alice.id()); + WasmValVec params(6), result(1); + auto* trap = + ww(&imp.at("deposit_preauth_id"), params, result, masterID, alice.id(), 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 4); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = + ww(&imp.at("deposit_preauth_id"), params, result, masterID, masterID, 1024, 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); + + auto* trap3 = + ww(&imp.at("deposit_preauth_id"), params, result, masterID, xrpAccount(), 1024, 32); + BEAST_EXPECT( + !trap3 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + + auto* trap4 = + ww(&imp.at("deposit_preauth_id"), params, result, xrpAccount(), masterID, 1024, 32); + BEAST_EXPECT( + !trap4 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + } + + { + auto const expected = keylet::escrow(masterID, 1u); + WasmValVec params(6), result(1); + auto* trap = ww(&imp.at("escrow_id"), params, result, masterID, toBytes(1u), 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 4); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = + ww(&imp.at("escrow_id"), params, result, xrpAccount(), toBytes(1u), 1024, 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + } + + Currency const usd = toCurrency("USD"); + { + auto const expected = keylet::trustLine(masterID, alice.id(), usd); + WasmValVec params(8), result(1); + auto* trap = + ww(&imp.at("trustline_id"), params, result, masterID, alice.id(), usd, 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 6); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = + ww(&imp.at("trustline_id"), params, result, masterID, masterID, usd, 1024, 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); + + auto* trap3 = + ww(&imp.at("trustline_id"), params, result, masterID, xrpAccount(), usd, 1024, 32); + BEAST_EXPECT( + !trap3 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + + auto* trap4 = + ww(&imp.at("trustline_id"), params, result, xrpAccount(), masterID, usd, 1024, 32); + BEAST_EXPECT( + !trap4 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + + auto* trap5 = + ww(&imp.at("trustline_id"), + params, + result, + masterID, + alice.id(), + toCurrency(""), + 1024, + 32); + BEAST_EXPECT( + !trap5 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); + } + + { + auto const expected = keylet::mptokenIssuance(1u, masterID); + WasmValVec params(6), result(1); + auto* trap = + ww(&imp.at("mpt_issuance_id"), params, result, masterID, toBytes(1u), 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 4); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = + ww(&imp.at("mpt_issuance_id"), params, result, xrpAccount(), toBytes(1u), 1024, 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + } + + { + auto const expected = keylet::mptoken(baseMpt, alice.id()); + WasmValVec params(6), result(1); + auto* trap = ww(&imp.at("mptoken_id"), params, result, baseMpt, alice.id(), 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 4); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = ww(&imp.at("mptoken_id"), params, result, MPTID{}, alice.id(), 1024, 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); + + auto* trap3 = + ww(&imp.at("mptoken_id"), params, result, baseMpt, xrpAccount(), 1024, 32); + BEAST_EXPECT( + !trap3 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + } + + { + auto const expected = keylet::nftokenOffer(masterID, 1u); + WasmValVec params(6), result(1); + auto* trap = + ww(&imp.at("nft_offer_id"), params, result, masterID, toBytes(1u), 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 4); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = + ww(&imp.at("nft_offer_id"), params, result, xrpAccount(), toBytes(1u), 1024, 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + } + + { + auto const expected = keylet::offer(masterID, 1u); + WasmValVec params(6), result(1); + auto* trap = ww(&imp.at("offer_id"), params, result, masterID, toBytes(1u), 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 4); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = + ww(&imp.at("offer_id"), params, result, xrpAccount(), toBytes(1u), 1024, 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + } + + { + auto const expected = keylet::oracle(masterID, 1u); + WasmValVec params(6), result(1); + auto* trap = ww(&imp.at("oracle_id"), params, result, masterID, toBytes(1u), 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 4); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = + ww(&imp.at("oracle_id"), params, result, xrpAccount(), toBytes(1u), 1024, 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + } + + { + auto const expected = keylet::payChannel(masterID, alice.id(), 1u); + WasmValVec params(8), result(1); + auto* trap = ww( + &imp.at("paychan_id"), params, result, masterID, alice.id(), toBytes(1u), 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 6); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = ww( + &imp.at("paychan_id"), params, result, masterID, masterID, toBytes(1u), 1024, 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidParams)); + + auto* trap3 = + ww(&imp.at("paychan_id"), + params, + result, + masterID, + xrpAccount(), + toBytes(1u), + 1024, + 32); + BEAST_EXPECT( + !trap3 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + + auto* trap4 = + ww(&imp.at("paychan_id"), + params, + result, + xrpAccount(), + masterID, + toBytes(1u), + 1024, + 32); + BEAST_EXPECT( + !trap4 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + } + + { + auto const expected = keylet::permissionedDomain(masterID, 1u); + WasmValVec params(6), result(1); + auto* trap = ww( + &imp.at("permissioned_domain_id"), params, result, masterID, toBytes(1u), 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 4); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = + ww(&imp.at("permissioned_domain_id"), + params, + result, + xrpAccount(), + toBytes(1u), + 1024, + 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + } + + { + auto const expected = keylet::signerList(masterID); + WasmValVec params(4), result(1); + auto* trap = ww(&imp.at("signers_id"), params, result, masterID, 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 2); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = ww(&imp.at("signers_id"), params, result, xrpAccount(), 1024, 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + } + + { + auto const expected = keylet::ticket(masterID, 1u); + WasmValVec params(6), result(1); + auto* trap = ww(&imp.at("ticket_id"), params, result, masterID, toBytes(1u), 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 4); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = + ww(&imp.at("ticket_id"), params, result, xrpAccount(), toBytes(1u), 1024, 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + } + + { + auto const expected = keylet::vault(masterID, 1u); + WasmValVec params(6), result(1); + auto* trap = ww(&imp.at("vault_id"), params, result, masterID, toBytes(1u), 1024, 32); + if (BEAST_EXPECT(!trap && result[0].kind == WASM_I32 && result[0].of.i32 == 32)) + { + auto const actual = vrt.getBytes(params, 4); + BEAST_EXPECT(compareKeylet(actual, expected)); + } + + auto* trap2 = + ww(&imp.at("vault_id"), params, result, xrpAccount(), toBytes(1u), 1024, 32); + BEAST_EXPECT( + !trap2 && result[0].kind == WASM_I32 && + result[0].of.i32 == static_cast(HostFunctionError::InvalidAccount)); + } + } + + void + testGetNFT() + { + testcase("getNFT"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + env.fund(XRP(1000), alice); + env.close(); + + // Mint NFT for alice + uint256 const nftId = token::getNextID(env, alice, 0u, 0u); + std::string const uri = "https://example.com/nft"; + env(token::mint(alice), token::Uri(uri)); + env.close(); + uint256 const nftId2 = token::getNextID(env, alice, 0u, 0u); + env(token::mint(alice)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = keylet::escrow(alice, env.seq(alice)); + VirtualRuntime vrt; + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + // Should succeed for valid NFT + { + // hfs.getNFT(alice.id(), nftId); + vrt.setBytes(0, alice.id().data(), AccountID::size()); + vrt.setBytes(256, nftId.data(), uint256::size()); + WasmValVec params(6), result(1); + auto* trap = + ww(&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, 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, 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, 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, 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, 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, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + VirtualRuntime vrt; + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + std::string const msg = "test trace"; + std::string data = "abc"; + auto const slice = Slice(data.data(), data.size()); + + // hfs.trace(msg, slice, false); + { + vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); + vrt.setBytes(256, slice.data(), slice.size()); + WasmValVec params(5), result(1); + auto* trap = + ww(&import.at("trace"), params, result, 0, msg.size(), 256, slice.size(), 0); + + if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 0)) + { + auto const messages = sink.messages().str(); + BEAST_EXPECT(messages.contains(msg)); + } + } + + // hfs.trace(msg, slice, true); + { + vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); + vrt.setBytes(256, slice.data(), slice.size()); + WasmValVec params(5), result(1); + auto* trap = + ww(&import.at("trace"), params, result, 0, msg.size(), 256, slice.size(), 1); + + if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 0)) + { + auto const messages = sink.messages().str(); + std::string hex; + hex.reserve(data.size() * 2); + boost::algorithm::hex(data.begin(), data.end(), std::back_inserter(hex)); + BEAST_EXPECT(messages.contains(msg)); + BEAST_EXPECT(messages.contains(hex)); + } + } + } + + { + // logs disabled (trace < error) + Env env(*this); + OpenView ov{*env.current()}; + test::StreamSink sink{beast::Severity::Error}; + beast::Journal const jlog{sink}; + ApplyContext ac = createApplyContext(env, ov, jlog); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + VirtualRuntime vrt; + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + std::string const msg = "test trace"; + std::string data = "abc"; + auto const slice = Slice(data.data(), data.size()); + + // hfs.trace(msg, slice, false); + vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); + vrt.setBytes(256, slice.data(), slice.size()); + WasmValVec params(5), result(1); + auto* trap = + ww(&import.at("trace"), params, result, 0, msg.size(), 256, slice.size(), 0); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 0); + auto const messages = sink.messages().str(); + BEAST_EXPECT(messages.empty()); + } + } + + void + testTraceNum() + { + testcase("traceNum"); + using namespace test::jtx; + + { + Env env(*this); + OpenView ov{*env.current()}; + test::StreamSink sink{beast::Severity::Trace}; + beast::Journal const jlog{sink}; + ApplyContext ac = createApplyContext(env, ov, jlog); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + VirtualRuntime vrt; + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + std::string const msg = "trace number"; + int64_t const num = 123456789; + + // hfs.traceNum(msg, num); + vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); + WasmValVec params(3), result(1); + auto* trap = ww(&import.at("trace_num"), params, result, 0, msg.size(), num); + + if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 0)) + { + auto const messages = sink.messages().str(); + BEAST_EXPECT(messages.contains(msg)); + BEAST_EXPECT(messages.contains(std::to_string(num))); + } + } + + { + // logs disabled + Env env(*this); + OpenView ov{*env.current()}; + test::StreamSink sink{beast::Severity::Error}; + beast::Journal const jlog{sink}; + ApplyContext ac = createApplyContext(env, ov, jlog); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + VirtualRuntime vrt; + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + std::string const msg = "trace number"; + int64_t const num = 123456789; + + // hfs.traceNum(msg, num); + vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); + WasmValVec params(3), result(1); + auto* trap = ww(&import.at("trace_num"), params, result, 0, msg.size(), num); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 0); + auto const messages = sink.messages().str(); + BEAST_EXPECT(messages.empty()); + } + } + + void + testTraceAccount() + { + testcase("traceAccount"); + using namespace test::jtx; + + { + Env env(*this); + OpenView ov{*env.current()}; + test::StreamSink sink{beast::Severity::Trace}; + beast::Journal const jlog{sink}; + ApplyContext ac = createApplyContext(env, ov, jlog); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + VirtualRuntime vrt; + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + std::string const msg = "trace account"; + auto const& accountId = env.master.id(); + + // hfs.traceAccount(msg, env.master.id()); + vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); + vrt.setBytes(256, accountId.data(), accountId.size()); + WasmValVec params(4), result(1); + auto* trap = + ww(&import.at("trace_acct"), params, result, 0, msg.size(), 256, accountId.size()); + + if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 0)) + { + auto const messages = sink.messages().str(); + BEAST_EXPECT(messages.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, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + VirtualRuntime vrt; + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + std::string msg = "trace account"; + auto const& accountId = env.master.id(); + + // hfs.traceAccount(msg, env.master.id()); + vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); + vrt.setBytes(256, accountId.data(), accountId.size()); + WasmValVec params(4), result(1); + auto* trap = + ww(&import.at("trace_acct"), params, result, 0, msg.size(), 256, accountId.size()); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 0); + auto const messages = sink.messages().str(); + BEAST_EXPECT(messages.empty()); + } + } + + void + testTraceAmount() + { + testcase("traceAmount"); + using namespace test::jtx; + + { + Env env(*this); + OpenView ov{*env.current()}; + test::StreamSink sink{beast::Severity::Trace}; + beast::Journal const jlog{sink}; + ApplyContext ac = createApplyContext(env, ov, jlog); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + VirtualRuntime vrt; + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + std::string const msg = "trace amount"; + STAmount const amount = XRP(12345); + { + // hfs.traceAmount(msg, amount); + Bytes amountBytes = toBytes(amount); + vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); + vrt.setBytes(256, amountBytes.data(), amountBytes.size()); + WasmValVec params(4), result(1); + auto* trap = + ww(&import.at("trace_amt"), + params, + result, + 0, + msg.size(), + 256, + amountBytes.size()); + + if (BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 0)) + { + auto const messages = sink.messages().str(); + BEAST_EXPECT(messages.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); + { + // hfs.traceAmount(msg, iouAmount); + Bytes amountBytes = toBytes(iouAmount); + vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); + vrt.setBytes(256, amountBytes.data(), amountBytes.size()); + WasmValVec params(4), result(1); + auto* trap = + ww(&import.at("trace_amt"), + params, + result, + 0, + msg.size(), + 256, + amountBytes.size()); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 0); + } + + // MPT amount + { + auto const mptId = makeMptID(42, env.master.id()); + Asset const mptAsset = Asset(mptId); + STAmount const mptAmount(mptAsset, 123456); + + // hfs.traceAmount(msg, mptAmount); + Bytes amountBytes = toBytes(mptAmount); + vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); + vrt.setBytes(256, amountBytes.data(), amountBytes.size()); + WasmValVec params(4), result(1); + auto* trap = + ww(&import.at("trace_amt"), + params, + result, + 0, + msg.size(), + 256, + amountBytes.size()); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 0); + } + } + + { + // logs disabled + Env env(*this); + OpenView ov{*env.current()}; + test::StreamSink sink{beast::Severity::Error}; + beast::Journal const jlog{sink}; + ApplyContext ac = createApplyContext(env, ov, jlog); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + VirtualRuntime vrt; + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + std::string const msg = "trace amount"; + STAmount const amount = XRP(12345); + + // hfs.traceAmount(msg, amount); + Bytes amountBytes = toBytes(amount); + vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); + vrt.setBytes(256, amountBytes.data(), amountBytes.size()); + WasmValVec params(4), result(1); + auto* trap = + ww(&import.at("trace_amt"), params, result, 0, msg.size(), 256, amountBytes.size()); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 0); + auto const messages = sink.messages().str(); + BEAST_EXPECT(messages.empty()); + } + } + + // clang-format off + + int const normalExp = 18; + + Bytes const floatIntMin = {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, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + VirtualRuntime vrt; + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + std::string const msg = "trace float"; + + { + // hfs.traceFloat(msg, makeSlice(invalid)); + vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); + vrt.setBytes(256, reinterpret_cast(invalid.data()), invalid.size()); + WasmValVec params(4), result(1); + auto* trap = ww( + &import.at("trace_xfloat"), params, result, 0, msg.size(), 256, invalid.size()); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 0); + } + + { + // hfs.traceFloat(msg, makeSlice(floatMaxExp)); + vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); + vrt.setBytes(256, floatMaxExp.data(), floatMaxExp.size()); + WasmValVec params(4), result(1); + auto* trap = + ww(&import.at("trace_xfloat"), + params, + result, + 0, + msg.size(), + 256, + floatMaxExp.size()); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 0); + } + } + + { + // logs disabled + Env env(*this); + OpenView ov{*env.current()}; + test::StreamSink sink{beast::Severity::Error}; + beast::Journal const jlog{sink}; + ApplyContext ac = createApplyContext(env, ov, jlog); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + VirtualRuntime vrt; + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + std::string const msg = "trace float"; + + // hfs.traceFloat(msg, makeSlice(invalid)); + vrt.setBytes(0, reinterpret_cast(msg.data()), msg.size()); + vrt.setBytes(256, reinterpret_cast(invalid.data()), invalid.size()); + WasmValVec params(4), result(1); + auto* trap = + ww(&import.at("trace_xfloat"), params, result, 0, msg.size(), 256, invalid.size()); + + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 0); + auto const messages = sink.messages().str(); + BEAST_EXPECT(messages.empty()); + } + } + + void + testFloatFromInt() + { + testcase("floatFromInt"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + VirtualRuntime vrt; + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + { + // hfs.floatFromInt(min64, -1); + WasmValVec params(4), result(1); + auto* trap = ww(&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, 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, 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, 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, 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, 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, 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, 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, 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, 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, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + testcase("float non-canonical"); + + { // non-canonical mantissa 100000e-4 + Bytes const y = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x86, 0xA0, 0xFF, 0xFF, 0xFF, 0xFC}; + auto const result = hfs.floatCompare(makeSlice(y), makeSlice(float10)); + BEAST_EXPECT(result && *result == 0); + } + } + + void + testFloatFromSTAmount() + { + testcase("floatFromSTAmount"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + VirtualRuntime vrt; + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto import = xrpl::createWasmImport(hfs); + hfs.setRT(vrt); + + { + // hfs.floatFromSTAmount(amount, -1); + STAmount const amount = XRP(100); + Bytes amountBytes = toBytes(amount); + vrt.setBytes(0, amountBytes.data(), amountBytes.size()); + WasmValVec params(5), result(1); + auto* trap = + ww(&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, 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, 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, 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, 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, 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(1); + // trace(msg_ptr, msg_len, data_ptr, data_len, asHex) + auto* trap = ww(&import.at("trace"), params, result, 0, testMsg.size(), 100, 5, 0); + + // Should succeed even though message is >10 bytes, because trace only uses slices + // (no transfer limit check in getDataSlice) + BEAST_EXPECT(!trap) && BEAST_EXPECT(result[0].kind == WASM_I32) && + BEAST_EXPECT(result[0].of.i32 == 0); + } + + // setData should return when transfer limit is exhausted + // After trace consumed overhead (1024 bytes), we have 10 - 1024 = negative limit left + { + 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/test/app/TestHostFunctions.h b/src/test/app/TestHostFunctions.h new file mode 100644 index 0000000000..a3ded89f33 --- /dev/null +++ b/src/test/app/TestHostFunctions.h @@ -0,0 +1,538 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace xrpl::test { + +class TestLedgerDataProvider : public HostFunctions +{ + jtx::Env& env_; + +public: + TestLedgerDataProvider(jtx::Env& env) : HostFunctions(env.journal), env_(env) + { + } + + [[nodiscard]] std::expected + getLedgerSqn() const override + { + return env_.current()->seq(); + } +}; + +class TestHostFunctions : public HostFunctions +{ +protected: + test::jtx::Env& env_; + AccountID accountID_; + Bytes data_; + +public: + TestHostFunctions(test::jtx::Env& env) : HostFunctions(env.journal), env_(env) + { + accountID_ = env.master.id(); + std::string t = "10000"; + data_ = Bytes{t.begin(), t.end()}; + } + + [[nodiscard]] std::expected + getLedgerSqn() const override + { + return 12345; + } + + [[nodiscard]] std::expected + getParentLedgerTime() const override + { + return 67890; + } + + [[nodiscard]] std::expected + getParentLedgerHash() const override + { + return env_.current()->header().parentHash; + } + + [[nodiscard]] std::expected + getBaseFee() const override + { + return 10; + } + + [[nodiscard]] std::expected + isAmendmentEnabled(uint256 const& amendmentId) const override + { + return 1; + } + + [[nodiscard]] std::expected + isAmendmentEnabled(std::string_view const& amendmentName) const override + { + return 1; + } + + std::expected + cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) override + { + return 1; + } + + [[nodiscard]] std::expected + getTxField(SField const& fname) const override + { + if (fname == sfAccount) + return Bytes(accountID_.begin(), accountID_.end()); + + if (fname == sfFee) + { + int64_t x = 235; + auto const* p = reinterpret_cast(&x); + return Bytes{p, p + sizeof(x)}; + } + + if (fname == sfSequence) + { + auto const x = getLedgerSqn(); + if (!x) + return std::unexpected(x.error()); + std::uint32_t const data = x.value(); + auto const* b = reinterpret_cast(&data); + auto const* e = reinterpret_cast(&data + 1); + return Bytes{b, e}; + } + + return Bytes(); + } + + [[nodiscard]] std::expected + getCurrentLedgerObjField(SField const& fname) const override + { + auto const& sn = fname.getName(); + if (sn == "Destination" || sn == "Account") + return Bytes(accountID_.begin(), accountID_.end()); + if (sn == "Data") + return data_; + if (sn == "FinishAfter") + { + auto t = env_.current()->parentCloseTime().time_since_epoch().count(); + std::string s = std::to_string(t); + return Bytes{s.begin(), s.end()}; + } + + return std::unexpected(HostFunctionError::Unimplemented); + } + + [[nodiscard]] std::expected + getLedgerObjField(int32_t, SField const& fname) const override + { + if (fname == sfBalance) + { + int64_t x = 10'000; + auto const* p = reinterpret_cast(&x); + return Bytes{p, p + sizeof(x)}; + } + + if (fname == sfAccount) + return Bytes(accountID_.begin(), accountID_.end()); + + return data_; + } + + [[nodiscard]] std::expected + getTxNestedField(FieldLocator const& locator) const override + { + if (locator.size() == 1) + { + int32_t const* l = locator.data(); + int32_t const sfield = l[0]; + if (sfield == sfAccount.getCode()) + return Bytes(accountID_.begin(), accountID_.end()); + } + + uint8_t const a[] = {0x2b, 0x6a, 0x23, 0x2a, 0xa4, 0xc4, 0xbe, 0x41, 0xbf, 0x49, 0xd2, + 0x45, 0x9f, 0xa4, 0xa0, 0x34, 0x7e, 0x1b, 0x54, 0x3a, 0x4c, 0x92, + 0xfc, 0xee, 0x08, 0x21, 0xc0, 0x20, 0x1e, 0x2e, 0x9a, 0x00}; + return Bytes(&a[0], &a[sizeof(a)]); + } + + [[nodiscard]] std::expected + getCurrentLedgerObjNestedField(FieldLocator const& locator) const override + { + if (locator.size() == 1) + { + int32_t const* l = locator.data(); + int32_t const sfield = l[0]; + if (sfield == sfAccount.getCode()) + return Bytes(accountID_.begin(), accountID_.end()); + } + + uint8_t const a[] = {0x2b, 0x6a, 0x23, 0x2a, 0xa4, 0xc4, 0xbe, 0x41, 0xbf, 0x49, 0xd2, + 0x45, 0x9f, 0xa4, 0xa0, 0x34, 0x7e, 0x1b, 0x54, 0x3a, 0x4c, 0x92, + 0xfc, 0xee, 0x08, 0x21, 0xc0, 0x20, 0x1e, 0x2e, 0x9a, 0x00}; + return Bytes(&a[0], &a[sizeof(a)]); + } + + [[nodiscard]] std::expected + getLedgerObjNestedField(int32_t cacheIdx, FieldLocator const& locator) const override + { + if (locator.size() == 1) + { + int32_t const* l = locator.data(); + int32_t const sfield = l[0]; + if (sfield == sfAccount.getCode()) + return Bytes(accountID_.begin(), accountID_.end()); + } + + uint8_t const a[] = {0x2b, 0x6a, 0x23, 0x2a, 0xa4, 0xc4, 0xbe, 0x41, 0xbf, 0x49, 0xd2, + 0x45, 0x9f, 0xa4, 0xa0, 0x34, 0x7e, 0x1b, 0x54, 0x3a, 0x4c, 0x92, + 0xfc, 0xee, 0x08, 0x21, 0xc0, 0x20, 0x1e, 0x2e, 0x9a, 0x00}; + return Bytes(&a[0], &a[sizeof(a)]); + } + + [[nodiscard]] std::expected + getTxArrayLen(SField const& fname) const override + { + return 32; + } + + [[nodiscard]] std::expected + getCurrentLedgerObjArrayLen(SField const& fname) const override + { + return 32; + } + + [[nodiscard]] std::expected + getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const override + { + return 32; + } + + [[nodiscard]] std::expected + getTxNestedArrayLen(FieldLocator const& locator) const override + { + return 32; + } + + [[nodiscard]] std::expected + getCurrentLedgerObjNestedArrayLen(FieldLocator const& locator) const override + { + return 32; + } + + [[nodiscard]] std::expected + getLedgerObjNestedArrayLen(int32_t cacheIdx, FieldLocator const& locator) const override + { + return 32; + } + + std::expected + updateData(Slice const& data) override + { + return data.size(); + } + + [[nodiscard]] std::expected + checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey) const override + { + return 1; + } + + [[nodiscard]] std::expected + computeSha512HalfHash(Slice const& data) const override + { + return env_.current()->header().parentHash; + } + + [[nodiscard]] std::expected + accountKeylet(AccountID const& account) const override + { + if (!account) + return std::unexpected(HostFunctionError::InvalidAccount); + auto const keylet = keylet::account(account); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + [[nodiscard]] std::expected + ammKeylet(Asset const& issue1, Asset const& issue2) const override + { + if (issue1 == issue2) + return std::unexpected(HostFunctionError::InvalidParams); + if (issue1.holds() || issue2.holds()) + return std::unexpected(HostFunctionError::InvalidParams); + auto const keylet = keylet::amm(issue1, issue2); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + [[nodiscard]] std::expected + checkKeylet(AccountID const& account, std::uint32_t seq) const override + { + if (!account) + return std::unexpected(HostFunctionError::InvalidAccount); + auto const keylet = keylet::check(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + [[nodiscard]] std::expected + credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType) + const override + { + if (!subject || !issuer || credentialType.empty() || + credentialType.size() > kMaxCredentialTypeLength) + return std::unexpected(HostFunctionError::InvalidAccount); + auto const keylet = keylet::credential(subject, issuer, credentialType); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + [[nodiscard]] std::expected + escrowKeylet(AccountID const& account, std::uint32_t seq) const override + { + if (!account) + return std::unexpected(HostFunctionError::InvalidAccount); + auto const keylet = keylet::escrow(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + [[nodiscard]] std::expected + oracleKeylet(AccountID const& account, std::uint32_t documentId) const override + { + if (!account) + return std::unexpected(HostFunctionError::InvalidAccount); + auto const keylet = keylet::oracle(account, documentId); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + [[nodiscard]] std::expected + getNFT(AccountID const& account, uint256 const& nftId) const override + { + if (!account || !nftId) + return std::unexpected(HostFunctionError::InvalidParams); + + std::string s = "https://ripple.com"; + return Bytes(s.begin(), s.end()); + } + + [[nodiscard]] std::expected + getNFTIssuer(uint256 const& nftId) const override + { + return Bytes(accountID_.begin(), accountID_.end()); + } + + [[nodiscard]] std::expected + getNFTTaxon(uint256 const& nftId) const override + { + return 4; + } + + [[nodiscard]] std::expected + getNFTFlags(uint256 const& nftId) const override + { + return 8; + } + + [[nodiscard]] std::expected + getNFTTransferFee(uint256 const& nftId) const override + { + return 10; + } + + [[nodiscard]] std::expected + getNFTSequence(uint256 const& nftId) const override + { + return 4; + } + + template + void + log(std::string_view const& msg, F&& dataFn) const + { +#ifdef DEBUG_OUTPUT + auto& j = std::cerr; +#else + if (!getJournal().active(beast::Severity::Trace)) + return; + auto j = getJournal().trace(); +#endif + j << "WasmTrace: " << msg << " " << dataFn(); + +#ifdef DEBUG_OUTPUT + j << std::endl; +#endif + } + + [[nodiscard]] std::expected + trace(std::string_view const& msg, Slice const& data, bool asHex) const override + { + if (!asHex) + { + log(msg, [&data] { + return std::string_view(reinterpret_cast(data.data()), data.size()); + }); + } + else + { + log(msg, [&data] { + std::string hex; + hex.reserve(data.size() * 2); + boost::algorithm::hex(data.begin(), data.end(), std::back_inserter(hex)); + return hex; + }); + } + + return 0; + } + + [[nodiscard]] std::expected + traceNum(std::string_view const& msg, int64_t data) const override + { + log(msg, [data] { return data; }); + return 0; + } + + [[nodiscard]] std::expected + traceAccount(std::string_view const& msg, AccountID const& account) const override + { + log(msg, [&account] { return toBase58(account); }); + return 0; + } + + [[nodiscard]] std::expected + traceFloat(std::string_view const& msg, Slice const& data) const override + { + log(msg, [&data] { return wasm_float::floatToString(data); }); + return 0; + } + + [[nodiscard]] std::expected + traceAmount(std::string_view const& msg, STAmount const& amount) const override + { + log(msg, [&amount] { return amount.getFullText(); }); + return 0; + } + + [[nodiscard]] std::expected + floatFromInt(int64_t x, int32_t mode) const override + { + return wasm_float::floatFromIntImpl(x, mode); + } + + [[nodiscard]] std::expected + floatFromUint(uint64_t x, int32_t mode) const override + { + return wasm_float::floatFromUintImpl(x, mode); + } + + [[nodiscard]] std::expected + floatFromSTAmount(STAmount const& x, int32_t mode) const override + { + return wasm_float::floatFromSTAmountImpl(x, mode); + } + + [[nodiscard]] std::expected + floatFromSTNumber(STNumber const& x, int32_t mode) const override + { + return wasm_float::floatFromSTNumberImpl(x, mode); + } + + [[nodiscard]] std::expected + floatToInt(Slice const& x, int32_t mode) const override + { + return wasm_float::floatToIntImpl(x, mode); + } + + [[nodiscard]] std::expected + floatToMantExp(Slice const& x) const override + { + return wasm_float::floatToMantExpImpl(x); + } + + [[nodiscard]] std::expected + floatFromMantExp(int64_t mantissa, int32_t exponent, int32_t mode) const override + { + return wasm_float::floatFromMantExpImpl(mantissa, exponent, mode); + } + + [[nodiscard]] std::expected + floatCompare(Slice const& x, Slice const& y) const override + { + return wasm_float::floatCompareImpl(x, y); + } + + [[nodiscard]] std::expected + floatAdd(Slice const& x, Slice const& y, int32_t mode) const override + { + return wasm_float::floatAddImpl(x, y, mode); + } + + [[nodiscard]] std::expected + floatSubtract(Slice const& x, Slice const& y, int32_t mode) const override + { + return wasm_float::floatSubtractImpl(x, y, mode); + } + + [[nodiscard]] std::expected + floatMultiply(Slice const& x, Slice const& y, int32_t mode) const override + { + return wasm_float::floatMultiplyImpl(x, y, mode); + } + + [[nodiscard]] std::expected + floatDivide(Slice const& x, Slice const& y, int32_t mode) const override + { + return wasm_float::floatDivideImpl(x, y, mode); + } + + [[nodiscard]] std::expected + floatRoot(Slice const& x, int32_t n, int32_t mode) const override + { + return wasm_float::floatRootImpl(x, n, mode); + } + + [[nodiscard]] std::expected + floatPower(Slice const& x, int32_t n, int32_t mode) const override + { + return wasm_float::floatPowerImpl(x, n, mode); + } +}; + +class TestHostFunctionsSink : public TestHostFunctions +{ + test::StreamSink sink_; + +public: + explicit TestHostFunctionsSink(test::jtx::Env& env) + : TestHostFunctions(env), sink_(beast::Severity::Debug) + { + j_ = beast::Journal(sink_); + } + + test::StreamSink& + getSink() + { + return sink_; + } +}; + +} // namespace xrpl::test diff --git a/src/test/app/Wasm_test.cpp b/src/test/app/Wasm_test.cpp new file mode 100644 index 0000000000..3a9f541153 --- /dev/null +++ b/src/test/app/Wasm_test.cpp @@ -0,0 +1,469 @@ +#include +#ifdef _DEBUG +// #define DEBUG_OUTPUT 1 +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include // IWYU pragma: keep +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +namespace xrpl::test { + +bool +testGetDataIncrement(); + +using Add_proto = int32_t(int32_t, int32_t); +static wasm_trap_t* +add(HostFunctions&, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + int32_t const val1 = params->data[0].of.i32; + int32_t const val2 = params->data[1].of.i32; + // printf("Host function \"Add\": %d + %d\n", Val1, Val2); + results->data[0] = WASM_I32_VAL(val1 + val2); + return nullptr; +} + +std::vector +hexToBytes(std::string const& hex) +{ + auto const ws = boost::algorithm::unhex(hex); + return Bytes(ws.begin(), ws.end()); +} + +struct Wasm_test : public beast::unit_test::Suite +{ + void + checkResult( + std::expected, WasmTER> re, + int32_t expectedResult, + int64_t expectedCost, + std::source_location const location = std::source_location::current()) + { + auto const lineStr = " (" + std::to_string(location.line()) + ")"; + if (BEAST_EXPECTS(re.has_value(), transToken(re.error().ter) + lineStr)) + { + BEAST_EXPECTS(re->result == expectedResult, std::to_string(re->result) + lineStr); + BEAST_EXPECTS(re->cost == expectedCost, std::to_string(re->cost) + lineStr); + } + } + + void + testGetDataHelperFunctions() + { + testcase("getData helper functions"); + BEAST_EXPECT(testGetDataIncrement()); + } + + void + testWasmLib() + { + testcase("wasm lib test"); + // clang-format off + /* The WASM module buffer. */ + Bytes const wasm = {/* WASM header */ + 0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, + /* Type section */ + 0x01, 0x07, 0x01, + /* function type {i32, i32} -> {i32} */ + 0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F, + /* Import section */ + 0x02, 0x13, 0x01, + /* module name: "extern" */ + 0x06, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6E, + /* extern name: "func-add" */ + 0x08, 0x66, 0x75, 0x6E, 0x63, 0x2D, 0x61, 0x64, 0x64, + /* import desc: func 0 */ + 0x00, 0x00, + /* Function section */ + 0x03, 0x02, 0x01, 0x00, + /* Export section */ + 0x07, 0x0A, 0x01, + /* export name: "addTwo" */ + 0x06, 0x61, 0x64, 0x64, 0x54, 0x77, 0x6F, + /* export desc: func 0 */ + 0x00, 0x01, + /* Code section */ + 0x0A, 0x0A, 0x01, + /* code body */ + 0x08, 0x00, 0x20, 0x00, 0x20, 0x01, 0x10, 0x00, 0x0B}; + // clang-format on + auto& vm = WasmEngine::instance(); + + HostFunctions hfs; + ImportVec imports; + WasmImpFunc(imports, "func-add", add, hfs); + + auto re = vm.run(wasm, hfs, 10'000'000, "addTwo", wasmParams(1234, 5678), imports); + + // if (res) printf("invokeAdd get the result: %d\n", res.value()); + + checkResult(re, 6'912, 59); + } + + void + testBadWasm() + { + testcase("bad wasm test"); + + using namespace test::jtx; + + Env const env{*this}; + HostFunctions hfs(env.journal); + + { + auto wasm = hexToBytes("00000000"); + std::string const funcName("mock_escrow"); + + auto re = runEscrowWasm(wasm, hfs, 15, funcName, {}); + BEAST_EXPECT(!re); + } + + { + auto wasm = hexToBytes("00112233445566778899AA"); + std::string const funcName("mock_escrow"); + + auto const re = preflightEscrowWasm(wasm, hfs, funcName); + BEAST_EXPECT(!isTesSuccess(re)); + } + + { + // FinishFunction wrong function name + // pub fn bad() -> bool { + // unsafe { host_lib::getLedgerSqn() >= 5 } + // } + auto const badWasm = hexToBytes( + "0061736d010000000105016000017f02190108686f73745f6c69620c6765" + "744c656467657253716e00000302010005030100100611027f00418080c0" + "000b7f00418080c0000b072b04066d656d6f727902000362616400010a5f" + "5f646174615f656e6403000b5f5f686561705f6261736503010a09010700" + "100041044a0b004d0970726f64756365727302086c616e67756167650104" + "52757374000c70726f6365737365642d6279010572757374631d312e3835" + "2e31202834656231363132353020323032352d30332d31352900490f7461" + "726765745f6665617475726573042b0f6d757461626c652d676c6f62616c" + "732b087369676e2d6578742b0f7265666572656e63652d74797065732b0a" + "6d756c746976616c7565"); + + auto const re = preflightEscrowWasm(badWasm, hfs, escrowFunctionName); + BEAST_EXPECT(!isTesSuccess(re)); + } + } + + void + testWasmLedgerSqn() + { + testcase("Wasm get ledger sequence"); + + auto ledgerSqnWasm = hexToBytes(kLedgerSqnWasmHex); + + using namespace test::jtx; + + Env env{*this}; + TestLedgerDataProvider hfs(env); + ImportVec imports; + WASM_IMPORT_FUNC2(imports, getLedgerSqn, "ldgr_index", hfs, 33); + auto& engine = WasmEngine::instance(); + + auto re = + engine.run(ledgerSqnWasm, hfs, 1'000'000, escrowFunctionName, {}, imports, env.journal); + + checkResult(re, 0, 440); + + env.close(); + env.close(); + + // empty module, throwing exception + re = engine.run({}, hfs, 1'000'000, escrowFunctionName, {}, imports, env.journal); + BEAST_EXPECT(!re); + env.close(); + } + + void + testHFCost() + { + testcase("wasm test host functions cost"); + + using namespace test::jtx; + + Env env(*this); + { + auto const allHostFuncWasm = hexToBytes(kAllHostFunctionsWasmHex); + + auto& engine = WasmEngine::instance(); + + TestHostFunctions hfs(env); + auto imp = createWasmImport(hfs); + for (auto& i : imp) + i.second.second.gas = 0; + + auto re = engine.run( + allHostFuncWasm, hfs, 1'000'000, escrowFunctionName, {}, imp, env.journal); + + checkResult(re, 1, 27'617); + + env.close(); + } + + env.close(); + env.close(); + env.close(); + env.close(); + env.close(); + + { + auto const allHostFuncWasm = hexToBytes(kAllHostFunctionsWasmHex); + + auto& engine = WasmEngine::instance(); + + TestHostFunctions hfs(env); + auto const imp = createWasmImport(hfs); + + auto re = engine.run( + allHostFuncWasm, hfs, 1'000'000, escrowFunctionName, {}, imp, env.journal); + + checkResult(re, 1, 70'877); + + env.close(); + } + + // not enough gas + { + auto const allHostFuncWasm = hexToBytes(kAllHostFunctionsWasmHex); + + auto& engine = WasmEngine::instance(); + + TestHostFunctions hfs(env); + auto const imp = createWasmImport(hfs); + + auto re = + engine.run(allHostFuncWasm, hfs, 200, escrowFunctionName, {}, imp, env.journal); + + if (BEAST_EXPECT(!re)) + { + // Running out of gas now terminates with tecOUT_OF_GAS (was + // previously collapsed into tecFAILED_PROCESSING). + BEAST_EXPECTS( + re.error().ter == tecOUT_OF_GAS, std::to_string(TERtoInt(re.error().ter))); + } + + env.close(); + } + } + + void + testEscrowWasmDN() + { + testcase("escrow wasm devnet test"); + + auto const allHFWasm = hexToBytes(kAllHostFunctionsWasmHex); + + using namespace test::jtx; + Env env{*this}; + { + TestHostFunctions hfs(env); + auto re = runEscrowWasm(allHFWasm, hfs, 100'000, escrowFunctionName, {}); + checkResult(re, 1, 70'877); + } + + { + // Invalid gas limit (0) should be rejected (boundary condition) + TestHostFunctions hfs(env); + auto re = runEscrowWasm(allHFWasm, hfs, -1, escrowFunctionName, {}); + BEAST_EXPECT(!re.has_value()); + BEAST_EXPECT(re.error().ter == temBAD_AMOUNT); + } + + { + // Invalid gas limit (-1) should be rejected + TestHostFunctions hfs(env); + auto re = runEscrowWasm(allHFWasm, hfs, 0, escrowFunctionName, {}); + BEAST_EXPECT(!re.has_value()); + BEAST_EXPECT(re.error().ter == temBAD_AMOUNT); + } + + { + // max() gas + TestHostFunctions hfs(env); + auto re = runEscrowWasm( + allHFWasm, hfs, std::numeric_limits::max(), escrowFunctionName, {}); + checkResult(re, 1, 70'877); + } + + { // fail because trying to access nonexistent field + struct FieldNotFoundHostFunctions : public TestHostFunctions + { + explicit FieldNotFoundHostFunctions(Env& env) : TestHostFunctions(env) + { + } + [[nodiscard]] std::expected + getTxField(SField const& fname) const override + { + return std::unexpected(HostFunctionError::FieldNotFound); + } + }; + + FieldNotFoundHostFunctions hfs(env); + auto re = runEscrowWasm(allHFWasm, hfs, 100'000, escrowFunctionName, {}); + checkResult(re, -201, 29'502); + } + + { // fail because trying to allocate more than MAX_PAGES memory + struct OversizedFieldHostFunctions : public TestHostFunctions + { + explicit OversizedFieldHostFunctions(Env& env) : TestHostFunctions(env) + { + } + [[nodiscard]] std::expected + getTxField(SField const& fname) const override + { + return Bytes((128 + 1) * 64 * 1024, 1); + } + }; + + OversizedFieldHostFunctions hfs(env); + auto re = runEscrowWasm(allHFWasm, hfs, 100'000, escrowFunctionName, {}); + checkResult(re, -201, 29'502); + } + } + + void + testCodecovWasm() + { + testcase("Codecov wasm test"); + + using namespace test::jtx; + + Env env{*this}; + + auto const codecovWasm = hexToBytes(kCodecovTestsWasmHex); + TestHostFunctions hfs(env); + + auto const allowance = 204'624; + auto re = runEscrowWasm(codecovWasm, hfs, allowance, escrowFunctionName, {}); + + checkResult(re, 1, allowance); + } + + void + testBadAlign() + { + testcase("Wasm Bad Align"); + + // bad_align.c + auto const badAlignWasm = hexToBytes(kBadAlignWasmHex); + + using namespace test::jtx; + + Env env{*this}; + TestHostFunctions hfs(env); + auto imports = createWasmImport(hfs); + + { // Calls float_from_uint with bad alignment. + // Can be checked through codecov + auto& engine = WasmEngine::instance(); + + auto re = engine.run(badAlignWasm, hfs, 1'000'000, "test", {}, imports, env.journal); + if (BEAST_EXPECTS(re, transToken(re.error().ter))) + { + BEAST_EXPECTS(re->result == 0x47308594, std::to_string(re->result)); + } + } + + env.close(); + } + + void + testSwapBytes() + { + testcase("Wasm swap bytes"); + + uint64_t const swapDataU64 = 0x123456789abcdeffull; + uint64_t const reverseSwapDataU64 = 0xffdebc9a78563412ull; + int64_t const swapDataI64 = 0x123456789abcdeffll; + int64_t const reverseSwapDataI64 = 0xffdebc9a78563412ll; + + uint32_t const swapDataU32 = 0x12789aff; + uint32_t const reverseSwapDataU32 = 0xff9a7812; + int32_t const swapDataI32 = 0x12789aff; + int32_t const reverseSwapDataI32 = 0xff9a7812; + + uint16_t const swapDataU16 = 0x12ff; + uint16_t const reverseSwapDataU16 = 0xff12; + int16_t const swapDataI16 = 0x12ff; + int16_t const reverseSwapDataI16 = 0xff12; + + uint64_t b1 = swapDataU64; + int64_t b2 = swapDataI64; + b1 = adjustWasmEndianessHlp(b1); + b2 = adjustWasmEndianessHlp(b2); + BEAST_EXPECT(b1 == reverseSwapDataU64); + BEAST_EXPECT(b2 == reverseSwapDataI64); + b1 = adjustWasmEndianessHlp(b1); + b2 = adjustWasmEndianessHlp(b2); + BEAST_EXPECT(b1 == swapDataU64); + BEAST_EXPECT(b2 == swapDataI64); + + uint32_t b3 = swapDataU32; + int32_t b4 = swapDataI32; + b3 = adjustWasmEndianessHlp(b3); + b4 = adjustWasmEndianessHlp(b4); + BEAST_EXPECT(b3 == reverseSwapDataU32); + BEAST_EXPECT(b4 == reverseSwapDataI32); + b3 = adjustWasmEndianessHlp(b3); + b4 = adjustWasmEndianessHlp(b4); + BEAST_EXPECT(b3 == swapDataU32); + BEAST_EXPECT(b4 == swapDataI32); + + uint16_t b5 = swapDataU16; + int16_t b6 = swapDataI16; + b5 = adjustWasmEndianessHlp(b5); + b6 = adjustWasmEndianessHlp(b6); + BEAST_EXPECT(b5 == reverseSwapDataU16); + BEAST_EXPECT(b6 == reverseSwapDataI16); + b5 = adjustWasmEndianessHlp(b5); + b6 = adjustWasmEndianessHlp(b6); + BEAST_EXPECT(b5 == swapDataU16); + BEAST_EXPECT(b6 == swapDataI16); + } + + void + run() override + { + using namespace test::jtx; + + testGetDataHelperFunctions(); + testWasmLib(); + testBadWasm(); + testWasmLedgerSqn(); + + testHFCost(); + testEscrowWasmDN(); + + testCodecovWasm(); + + testBadAlign(); + testSwapBytes(); + } +}; + +BEAST_DEFINE_TESTSUITE(Wasm, app, xrpl); + +} // namespace xrpl::test diff --git a/src/test/app/wasm_fixtures/.gitignore b/src/test/app/wasm_fixtures/.gitignore new file mode 100644 index 0000000000..08b2e8a256 --- /dev/null +++ b/src/test/app/wasm_fixtures/.gitignore @@ -0,0 +1,3 @@ +**/target +**/debug +*.wasm diff --git a/src/test/app/wasm_fixtures/all_host_functions/Cargo.lock b/src/test/app/wasm_fixtures/all_host_functions/Cargo.lock new file mode 100644 index 0000000000..48771d1506 --- /dev/null +++ b/src/test/app/wasm_fixtures/all_host_functions/Cargo.lock @@ -0,0 +1,171 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "all_host_functions" +version = "0.1.0" +dependencies = [ + "xrpl-wasm-stdlib", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "xrpl-macros" +version = "0.1.0" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=renames#9822d645870908a79d87a57b0244caa6359cb9cf" +dependencies = [ + "bs58", + "quote", + "sha2", + "syn", +] + +[[package]] +name = "xrpl-wasm-stdlib" +version = "0.8.0" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=renames#9822d645870908a79d87a57b0244caa6359cb9cf" +dependencies = [ + "xrpl-macros", +] diff --git a/src/test/app/wasm_fixtures/all_host_functions/Cargo.toml b/src/test/app/wasm_fixtures/all_host_functions/Cargo.toml new file mode 100644 index 0000000000..fb0c44562a --- /dev/null +++ b/src/test/app/wasm_fixtures/all_host_functions/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "all_host_functions" +version = "0.1.0" +edition = "2024" + +# This empty workspace definition keeps this project independent of the parent workspace +[workspace] + +[lib] +crate-type = ["cdylib"] + +[dependencies] +xrpl-std = { git = "https://github.com/ripple/xrpl-wasm-stdlib.git", package = "xrpl-wasm-stdlib", branch = "renames" } + +[profile.dev] +panic = "abort" + +[profile.release] +panic = "abort" +opt-level = "z" +lto = true diff --git a/src/test/app/wasm_fixtures/all_host_functions/src/lib.rs b/src/test/app/wasm_fixtures/all_host_functions/src/lib.rs new file mode 100644 index 0000000000..a40aa91d6a --- /dev/null +++ b/src/test/app/wasm_fixtures/all_host_functions/src/lib.rs @@ -0,0 +1,799 @@ +#![cfg_attr(target_arch = "wasm32", no_std)] + +#[cfg(not(target_arch = "wasm32"))] +extern crate std; + +// +// Host Functions Test +// Tests 26 host functions (across 7 categories) +// +// With craft you can run this test with: +// craft test --project host_functions_test --test-case host_functions_test +// +// Amount Format Update: +// - XRP amounts now return as 8-byte serialized rippled objects +// - IOU and MPT amounts return in variable-length serialized format +// - Format details: https://xrpl.org/docs/references/protocol/binary-format#amount-fields +// +// Error Code Ranges: +// -100 to -199: Ledger Header Functions (3 functions) +// -200 to -299: Transaction Data Functions (5 functions) +// -300 to -399: Current Ledger Object Functions (4 functions) +// -400 to -499: Any Ledger Object Functions (5 functions) +// -500 to -599: Keylet Generation Functions (4 functions) +// -600 to -699: Utility Functions (4 functions) +// -700 to -799: Data Update Functions (1 function) +// + +use xrpl_std::core::current_tx::escrow_finish::EscrowFinish; +use xrpl_std::core::current_tx::traits::TransactionCommonFields; +use xrpl_std::host; +use xrpl_std::host::trace::{trace, trace_account_buf, trace_data, trace_num, DataRepr}; +use xrpl_std::sfield; + +#[unsafe(no_mangle)] +pub extern "C" fn escrow_finish() -> i32 { + let _ = trace("=== HOST FUNCTIONS TEST ==="); + let _ = trace("Testing 26 host functions"); + + // Category 1: Ledger Header Data Functions (3 functions) + // Error range: -100 to -199 + match test_ledger_header_functions() { + 0 => (), + err => return err, + } + + // Category 2: Transaction Data Functions (5 functions) + // Error range: -200 to -299 + match test_transaction_data_functions() { + 0 => (), + err => return err, + } + + // Category 3: Current Ledger Object Functions (4 functions) + // Error range: -300 to -399 + match test_current_ledger_object_functions() { + 0 => (), + err => return err, + } + + // Category 4: Any Ledger Object Functions (5 functions) + // Error range: -400 to -499 + match test_any_ledger_object_functions() { + 0 => (), + err => return err, + } + + // Category 5: Keylet Generation Functions (4 functions) + // Error range: -500 to -599 + match test_keylet_generation_functions() { + 0 => (), + err => return err, + } + + // Category 6: Utility Functions (4 functions) + // Error range: -600 to -699 + match test_utility_functions() { + 0 => (), + err => return err, + } + + // Category 7: Data Update Functions (1 function) + // Error range: -700 to -799 + match test_data_update_functions() { + 0 => (), + err => return err, + } + + let _ = trace("SUCCESS: All host function tests passed!"); + 1 // Success return code for WASM finish function +} + +/// Test Category 1: Ledger Header Data Functions (3 functions) +/// - get_ledger_sqn() - Get ledger sequence number +/// - get_parent_ledger_time() - Get parent ledger timestamp +/// - get_parent_ledger_hash() - Get parent ledger hash +fn test_ledger_header_functions() -> i32 { + let _ = trace("--- Category 1: Ledger Header Functions ---"); + + // Test 1.1: get_ledger_sqn() - should return current ledger sequence number + let mut sqn_buffer = [0u8; 4]; + let sqn_result = unsafe { host::ldgr_index(sqn_buffer.as_mut_ptr(), sqn_buffer.len()) }; + + if sqn_result <= 0 { + let _ = trace_num("ERROR: get_ledger_sqn failed:", sqn_result as i64); + return -101; // Ledger sequence number test failed + } + let ledger_sqn = u32::from_be_bytes(sqn_buffer); + let _ = trace_num("Ledger sequence number:", ledger_sqn as i64); + + // Test 1.2: get_parent_ledger_time() - should return parent ledger timestamp + let mut time_buffer = [0u8; 4]; + let time_result = + unsafe { host::parent_ldgr_time(time_buffer.as_mut_ptr(), time_buffer.len()) }; + + if time_result <= 0 { + let _ = trace_num("ERROR: get_parent_ledger_time failed:", time_result as i64); + return -102; // Parent ledger time test failed + } + let parent_ledger_time = u32::from_be_bytes(time_buffer); + let _ = trace_num("Parent ledger time:", parent_ledger_time as i64); + + // Test 1.3: get_parent_ledger_hash() - should return parent ledger hash (32 bytes) + let mut hash_buffer = [0u8; 32]; + let hash_result = + unsafe { host::parent_ldgr_hash(hash_buffer.as_mut_ptr(), hash_buffer.len()) }; + + if hash_result != 32 { + let _ = trace_num( + "ERROR: get_parent_ledger_hash wrong length:", + hash_result as i64, + ); + return -103; // Parent ledger hash test failed - should be exactly 32 bytes + } + let _ = trace_data("Parent ledger hash:", &hash_buffer, DataRepr::AsHex); + + let _ = trace("SUCCESS: Ledger header functions"); + 0 +} + +/// Test Category 2: Transaction Data Functions (5 functions) +/// Tests all functions for accessing current transaction data +fn test_transaction_data_functions() -> i32 { + let _ = trace("--- Category 2: Transaction Data Functions ---"); + + // Test 2.1: get_tx_field() - Basic transaction field access + // Test with Account field (required, 20 bytes) + let mut account_buffer = [0u8; 20]; + let account_len = unsafe { + host::tx_field( + sfield::Account.into(), + account_buffer.as_mut_ptr(), + account_buffer.len(), + ) + }; + + if account_len != 20 { + let _ = trace_num( + "ERROR: get_tx_field(Account) wrong length:", + account_len as i64, + ); + return -201; // Basic transaction field test failed + } + let _ = trace_account_buf("Transaction Account:", &account_buffer); + + // Test with Fee field (XRP amount - 8 bytes in new serialized format) + // New format: XRP amounts are always 8 bytes (positive: value | cPositive flag, negative: just value) + let mut fee_buffer = [0u8; 8]; + let fee_len = unsafe { + host::tx_field( + sfield::Fee.into(), + fee_buffer.as_mut_ptr(), + fee_buffer.len(), + ) + }; + + if fee_len != 8 { + let _ = trace_num( + "ERROR: get_tx_field(Fee) wrong length (expected 8 bytes for XRP):", + fee_len as i64, + ); + return -202; // Fee field test failed - XRP amounts should be exactly 8 bytes + } + let _ = trace_num("Transaction Fee length:", fee_len as i64); + let _ = trace_data( + "Transaction Fee (serialized XRP amount):", + &fee_buffer, + DataRepr::AsHex, + ); + + // Test with Sequence field (required, 4 bytes uint32) + let mut seq_buffer = [0u8; 4]; + let seq_len = unsafe { + host::tx_field( + sfield::Sequence.into(), + seq_buffer.as_mut_ptr(), + seq_buffer.len(), + ) + }; + + if seq_len != 4 { + let _ = trace_num( + "ERROR: get_tx_field(Sequence) wrong length:", + seq_len as i64, + ); + return -203; // Sequence field test failed + } + let _ = trace_data("Transaction Sequence:", &seq_buffer, DataRepr::AsHex); + + // NOTE: get_tx_field2() through get_tx_field6() have been deprecated. + // Use get_tx_field() with appropriate parameters for all transaction field access. + + // Test 2.2: get_tx_nested_field() - Nested field access with locator + let locator = [ + 0x01_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, + ]; // Two int32s in little-endian: [1, 0] + let mut nested_buffer = [0u8; 32]; + let nested_result = unsafe { + host::tx_inner( + locator.as_ptr(), + locator.len(), + nested_buffer.as_mut_ptr(), + nested_buffer.len(), + ) + }; + + if nested_result < 0 { + let _ = trace_num( + "INFO: get_tx_nested_field not applicable:", + nested_result as i64, + ); + // Expected - locator may not match transaction structure + } else { + let _ = trace_num("Nested field length:", nested_result as i64); + let _ = trace_data( + "Nested field:", + &nested_buffer[..nested_result as usize], + DataRepr::AsHex, + ); + } + + // Test 2.3: get_tx_array_len() - Get array length + let signers_len = unsafe { host::tx_arr_len(sfield::Signers.into()) }; + let _ = trace_num("Signers array length:", signers_len as i64); + + let memos_len = unsafe { host::tx_arr_len(sfield::Memos.into()) }; + let _ = trace_num("Memos array length:", memos_len as i64); + + // Test 2.4: get_tx_nested_array_len() - Get nested array length with locator + let nested_array_len = unsafe { host::tx_inner_arr_len(locator.as_ptr(), locator.len()) }; + + if nested_array_len < 0 { + let _ = trace_num( + "INFO: get_tx_nested_array_len not applicable:", + nested_array_len as i64, + ); + } else { + let _ = trace_num("Nested array length:", nested_array_len as i64); + } + + let _ = trace("SUCCESS: Transaction data functions"); + 0 +} + +/// Test Category 3: Current Ledger Object Functions (4 functions) +/// Tests functions that access the current ledger object being processed +fn test_current_ledger_object_functions() -> i32 { + let _ = trace("--- Category 3: Current Ledger Object Functions ---"); + + // Test 3.1: get_current_ledger_obj_field() - Access field from current ledger object + // Test with Balance field (XRP amount - 8 bytes in new serialized format) + let mut balance_buffer = [0u8; 8]; + let balance_result = unsafe { + host::home_le_field( + sfield::Balance.into(), + balance_buffer.as_mut_ptr(), + balance_buffer.len(), + ) + }; + + if balance_result <= 0 { + let _ = trace_num( + "INFO: get_current_ledger_obj_field(Balance) failed (may be expected):", + balance_result as i64, + ); + // This might fail if current ledger object doesn't have balance field + } else if balance_result == 8 { + let _ = trace_num( + "Current object balance length (XRP amount):", + balance_result as i64, + ); + let _ = trace_data( + "Current object balance (serialized XRP amount):", + &balance_buffer, + DataRepr::AsHex, + ); + } else { + let _ = trace_num( + "Current object balance length (non-XRP amount):", + balance_result as i64, + ); + let _ = trace_data( + "Current object balance:", + &balance_buffer[..balance_result as usize], + DataRepr::AsHex, + ); + } + + // Test with Account field + let mut current_account_buffer = [0u8; 20]; + let current_account_result = unsafe { + host::home_le_field( + sfield::Account.into(), + current_account_buffer.as_mut_ptr(), + current_account_buffer.len(), + ) + }; + + if current_account_result <= 0 { + let _ = trace_num( + "INFO: get_current_ledger_obj_field(Account) failed:", + current_account_result as i64, + ); + } else { + let _ = trace_account_buf("Current ledger object account:", ¤t_account_buffer); + } + + // Test 3.2: get_current_ledger_obj_nested_field() - Nested field access + let locator = [ + 0x01_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, + ]; // Two int32s in little-endian: [1, 0] + let mut current_nested_buffer = [0u8; 32]; + let current_nested_result = unsafe { + host::home_le_inner( + locator.as_ptr(), + locator.len(), + current_nested_buffer.as_mut_ptr(), + current_nested_buffer.len(), + ) + }; + + if current_nested_result < 0 { + let _ = trace_num( + "INFO: get_current_ledger_obj_nested_field not applicable:", + current_nested_result as i64, + ); + } else { + let _ = trace_num("Current nested field length:", current_nested_result as i64); + let _ = trace_data( + "Current nested field:", + ¤t_nested_buffer[..current_nested_result as usize], + DataRepr::AsHex, + ); + } + + // Test 3.3: get_current_ledger_obj_array_len() - Array length in current object + let current_array_len = unsafe { host::home_le_arr_len(sfield::Signers.into()) }; + let _ = trace_num( + "Current object Signers array length:", + current_array_len as i64, + ); + + // Test 3.4: get_current_ledger_obj_nested_array_len() - Nested array length + let current_nested_array_len = + unsafe { host::home_le_inner_arr_len(locator.as_ptr(), locator.len()) }; + + if current_nested_array_len < 0 { + let _ = trace_num( + "INFO: get_current_ledger_obj_nested_array_len not applicable:", + current_nested_array_len as i64, + ); + } else { + let _ = trace_num( + "Current nested array length:", + current_nested_array_len as i64, + ); + } + + let _ = trace("SUCCESS: Current ledger object functions"); + 0 +} + +/// Test Category 4: Any Ledger Object Functions (5 functions) +/// Tests functions that work with cached ledger objects +fn test_any_ledger_object_functions() -> i32 { + let _ = trace("--- Category 4: Any Ledger Object Functions ---"); + + // First we need to cache a ledger object to test the other functions + // Get the account from transaction and generate its keylet + let escrow_finish = EscrowFinish; + let account_id = escrow_finish.get_account().unwrap(); + + // Test 4.1: cache_ledger_obj() - Cache a ledger object + let mut keylet_buffer = [0u8; 32]; + let keylet_result = unsafe { + host::accountroot_id( + account_id.0.as_ptr(), + account_id.0.len(), + keylet_buffer.as_mut_ptr(), + keylet_buffer.len(), + ) + }; + + if keylet_result != 32 { + let _ = trace_num( + "ERROR: accountroot_id failed for caching test:", + keylet_result as i64, + ); + return -401; // Keylet generation failed for caching test + } + + let cache_result = unsafe { host::cache_le(keylet_buffer.as_ptr(), keylet_result as usize, 0) }; + + if cache_result <= 0 { + let _ = trace_num( + "INFO: cache_ledger_obj failed (expected with test fixtures):", + cache_result as i64, + ); + // Test fixtures may not contain the account object - this is expected + // We'll test the interface but expect failures + + // Test 4.2-4.5 with invalid slot (should fail gracefully) + let mut test_buffer = [0u8; 32]; + + // Test get_ledger_obj_field with invalid slot + let field_result = unsafe { + host::le_field( + 1, + sfield::Balance.into(), + test_buffer.as_mut_ptr(), + test_buffer.len(), + ) + }; + if field_result < 0 { + let _ = trace_num( + "INFO: get_ledger_obj_field failed as expected (no cached object):", + field_result as i64, + ); + } + + // Test get_ledger_obj_nested_field with invalid slot + let locator = [ + 0x01_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, + ]; // Two int32s in little-endian: [1, 0] + let nested_result = unsafe { + host::le_inner( + 1, + locator.as_ptr(), + locator.len(), + test_buffer.as_mut_ptr(), + test_buffer.len(), + ) + }; + if nested_result < 0 { + let _ = trace_num( + "INFO: get_ledger_obj_nested_field failed as expected:", + nested_result as i64, + ); + } + + // Test get_ledger_obj_array_len with invalid slot + let array_result = unsafe { host::le_arr_len(1, sfield::Signers.into()) }; + if array_result < 0 { + let _ = trace_num( + "INFO: get_ledger_obj_array_len failed as expected:", + array_result as i64, + ); + } + + // Test get_ledger_obj_nested_array_len with invalid slot + let nested_array_result = + unsafe { host::le_inner_arr_len(1, locator.as_ptr(), locator.len()) }; + if nested_array_result < 0 { + let _ = trace_num( + "INFO: get_ledger_obj_nested_array_len failed as expected:", + nested_array_result as i64, + ); + } + + let _ = trace("SUCCESS: Any ledger object functions (interface tested)"); + return 0; + } + + // If we successfully cached an object, test the access functions + let slot = cache_result; + let _ = trace_num("Successfully cached object in slot:", slot as i64); + + // Test 4.2: get_ledger_obj_field() - Access field from cached object + let mut cached_balance_buffer = [0u8; 8]; + let cached_balance_result = unsafe { + host::le_field( + slot, + sfield::Balance.into(), + cached_balance_buffer.as_mut_ptr(), + cached_balance_buffer.len(), + ) + }; + + if cached_balance_result <= 0 { + let _ = trace_num( + "INFO: get_ledger_obj_field(Balance) failed:", + cached_balance_result as i64, + ); + } else if cached_balance_result == 8 { + let _ = trace_num( + "Cached object balance length (XRP amount):", + cached_balance_result as i64, + ); + let _ = trace_data( + "Cached object balance (serialized XRP amount):", + &cached_balance_buffer, + DataRepr::AsHex, + ); + } else { + let _ = trace_num( + "Cached object balance length (non-XRP amount):", + cached_balance_result as i64, + ); + let _ = trace_data( + "Cached object balance:", + &cached_balance_buffer[..cached_balance_result as usize], + DataRepr::AsHex, + ); + } + + // Test 4.3: get_ledger_obj_nested_field() - Nested field from cached object + let locator = [ + 0x01_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, 0x00_u8, + ]; // Two int32s in little-endian: [1, 0] + let mut cached_nested_buffer = [0u8; 32]; + let cached_nested_result = unsafe { + host::le_inner( + slot, + locator.as_ptr(), + locator.len(), + cached_nested_buffer.as_mut_ptr(), + cached_nested_buffer.len(), + ) + }; + + if cached_nested_result < 0 { + let _ = trace_num( + "INFO: get_ledger_obj_nested_field not applicable:", + cached_nested_result as i64, + ); + } else { + let _ = trace_num("Cached nested field length:", cached_nested_result as i64); + let _ = trace_data( + "Cached nested field:", + &cached_nested_buffer[..cached_nested_result as usize], + DataRepr::AsHex, + ); + } + + // Test 4.4: get_ledger_obj_array_len() - Array length from cached object + let cached_array_len = unsafe { host::le_arr_len(slot, sfield::Signers.into()) }; + let _ = trace_num( + "Cached object Signers array length:", + cached_array_len as i64, + ); + + // Test 4.5: get_ledger_obj_nested_array_len() - Nested array length from cached object + let cached_nested_array_len = + unsafe { host::le_inner_arr_len(slot, locator.as_ptr(), locator.len()) }; + + if cached_nested_array_len < 0 { + let _ = trace_num( + "INFO: get_ledger_obj_nested_array_len not applicable:", + cached_nested_array_len as i64, + ); + } else { + let _ = trace_num( + "Cached nested array length:", + cached_nested_array_len as i64, + ); + } + + let _ = trace("SUCCESS: Any ledger object functions"); + 0 +} + +/// Test Category 5: Keylet Generation Functions (4 functions) +/// Tests keylet generation functions for different ledger entry types +fn test_keylet_generation_functions() -> i32 { + let _ = trace("--- Category 5: Keylet Generation Functions ---"); + + let escrow_finish = EscrowFinish; + let account_id = escrow_finish.get_account().unwrap(); + + // Test 5.1: accountroot_id() - Generate keylet for account + let mut accountroot_id_buffer = [0u8; 32]; + let accountroot_id_result = unsafe { + host::accountroot_id( + account_id.0.as_ptr(), + account_id.0.len(), + accountroot_id_buffer.as_mut_ptr(), + accountroot_id_buffer.len(), + ) + }; + + if accountroot_id_result != 32 { + let _ = trace_num( + "ERROR: accountroot_id failed:", + accountroot_id_result as i64, + ); + return -501; // Account keylet generation failed + } + let _ = trace_data("Account keylet:", &accountroot_id_buffer, DataRepr::AsHex); + + // Test 5.2: credential_keylet() - Generate keylet for credential + let mut credential_keylet_buffer = [0u8; 32]; + let credential_keylet_result = unsafe { + host::credential_id( + account_id.0.as_ptr(), // Subject + account_id.0.len(), + account_id.0.as_ptr(), // Issuer - same account for test + account_id.0.len(), + b"TestType".as_ptr(), // Credential type + 9usize, // Length of "TestType" + credential_keylet_buffer.as_mut_ptr(), + credential_keylet_buffer.len(), + ) + }; + + if credential_keylet_result <= 0 { + let _ = trace_num( + "INFO: credential_keylet failed (expected - interface issue):", + credential_keylet_result as i64, + ); + // This is expected to fail due to unusual parameter types + } else { + let _ = trace_data( + "Credential keylet:", + &credential_keylet_buffer[..credential_keylet_result as usize], + DataRepr::AsHex, + ); + } + + // Test 5.3: escrow_keylet() - Generate keylet for escrow + let mut escrow_keylet_buffer = [0u8; 32]; + let sequence_number: i32 = 1000; + let sequence_number_bytes = sequence_number.to_be_bytes(); + let escrow_keylet_result = unsafe { + host::escrow_id( + account_id.0.as_ptr(), + account_id.0.len(), + sequence_number_bytes.as_ptr(), + sequence_number_bytes.len(), + escrow_keylet_buffer.as_mut_ptr(), + escrow_keylet_buffer.len(), + ) + }; + + if escrow_keylet_result != 32 { + let _ = trace_num("ERROR: escrow_keylet failed:", escrow_keylet_result as i64); + return -503; // Escrow keylet generation failed + } + let _ = trace_data("Escrow keylet:", &escrow_keylet_buffer, DataRepr::AsHex); + + // Test 5.4: oracle_keylet() - Generate keylet for oracle + let mut oracle_keylet_buffer = [0u8; 32]; + let document_id: i32 = 42; + let document_id_bytes = document_id.to_be_bytes(); + let oracle_keylet_result = unsafe { + host::oracle_id( + account_id.0.as_ptr(), + account_id.0.len(), + document_id_bytes.as_ptr(), + document_id_bytes.len(), + oracle_keylet_buffer.as_mut_ptr(), + oracle_keylet_buffer.len(), + ) + }; + + if oracle_keylet_result != 32 { + let _ = trace_num("ERROR: oracle_keylet failed:", oracle_keylet_result as i64); + return -504; // Oracle keylet generation failed + } + let _ = trace_data("Oracle keylet:", &oracle_keylet_buffer, DataRepr::AsHex); + + let _ = trace("SUCCESS: Keylet generation functions"); + 0 +} + +/// Test Category 6: Utility Functions (4 functions) +/// Tests utility functions for hashing, NFT access, and tracing +fn test_utility_functions() -> i32 { + let _ = trace("--- Category 6: Utility Functions ---"); + + // Test 6.1: compute_sha512_half() - SHA512 hash computation (first 32 bytes) + let test_data = b"Hello, XRPL WASM world!"; + let mut hash_output = [0u8; 32]; + let hash_result = unsafe { + host::sha512_half( + test_data.as_ptr(), + test_data.len(), + hash_output.as_mut_ptr(), + hash_output.len(), + ) + }; + + if hash_result != 32 { + let _ = trace_num("ERROR: compute_sha512_half failed:", hash_result as i64); + return -601; // SHA512 half computation failed + } + let _ = trace_data("Input data:", test_data, DataRepr::AsHex); + let _ = trace_data("SHA512 half hash:", &hash_output, DataRepr::AsHex); + + // Test 6.2: get_nft() - NFT data retrieval + let escrow_finish = EscrowFinish; + let account_id = escrow_finish.get_account().unwrap(); + let nft_id = [0u8; 32]; // Dummy NFT ID for testing + let mut nft_buffer = [0u8; 256]; + let nft_result = unsafe { + host::nft_uri( + account_id.0.as_ptr(), + account_id.0.len(), + nft_id.as_ptr(), + nft_id.len(), + nft_buffer.as_mut_ptr(), + nft_buffer.len(), + ) + }; + + if nft_result <= 0 { + let _ = trace_num( + "INFO: get_nft failed (expected - no such NFT):", + nft_result as i64, + ); + // This is expected - test account likely doesn't own the dummy NFT + } else { + let _ = trace_num("NFT data length:", nft_result as i64); + let _ = trace_data( + "NFT data:", + &nft_buffer[..nft_result as usize], + DataRepr::AsHex, + ); + } + + // Test 6.3: trace() - Debug logging with data + let trace_message = b"Test trace message"; + let trace_data_payload = b"payload"; + let trace_result = unsafe { + host::trace( + trace_message.as_ptr(), + trace_message.len(), + trace_data_payload.as_ptr(), + trace_data_payload.len(), + 1, // as_hex = true + ) + }; + + if trace_result < 0 { + let _ = trace_num("ERROR: trace() failed:", trace_result as i64); + return -603; // Trace function failed + } + let _ = trace_num("Trace function bytes written:", trace_result as i64); + + // Test 6.4: trace_num() - Debug logging with number + let test_number = 42i64; + let trace_num_result = trace_num("Test number trace", test_number); + + use xrpl_std::host::Result; + match trace_num_result { + Result::Ok(_) => { + let _ = trace_num("Trace_num function succeeded", 0); + } + Result::Err(_) => { + let _ = trace_num("ERROR: trace_num() failed:", -604); + return -604; // Trace number function failed + } + } + + let _ = trace("SUCCESS: Utility functions"); + 0 +} + +/// Test Category 7: Data Update Functions (1 function) +/// Tests the function for modifying the current ledger entry +fn test_data_update_functions() -> i32 { + let _ = trace("--- Category 7: Data Update Functions ---"); + + // Test 7.1: update_data() - Update current ledger entry data + let update_payload = b"Updated ledger entry data from WASM test"; + + let update_result = unsafe { host::set_data(update_payload.as_ptr(), update_payload.len()) }; + + if update_result != update_payload.len() as i32 { + let _ = trace_num("ERROR: update_data failed:", update_result as i64); + return -701; // Data update failed + } + + let _ = trace_data( + "Successfully updated ledger entry with:", + update_payload, + DataRepr::AsHex, + ); + let _ = trace("SUCCESS: Data update functions"); + 0 +} diff --git a/src/test/app/wasm_fixtures/all_keylets/Cargo.lock b/src/test/app/wasm_fixtures/all_keylets/Cargo.lock new file mode 100644 index 0000000000..5da5b26f66 --- /dev/null +++ b/src/test/app/wasm_fixtures/all_keylets/Cargo.lock @@ -0,0 +1,171 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "all_keylets" +version = "0.0.1" +dependencies = [ + "xrpl-wasm-stdlib", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "xrpl-macros" +version = "0.1.0" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=renames#21c522f34a24b460297ebb6be1822680459bf37e" +dependencies = [ + "bs58", + "quote", + "sha2", + "syn", +] + +[[package]] +name = "xrpl-wasm-stdlib" +version = "0.8.0" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=renames#21c522f34a24b460297ebb6be1822680459bf37e" +dependencies = [ + "xrpl-macros", +] diff --git a/src/test/app/wasm_fixtures/all_keylets/Cargo.toml b/src/test/app/wasm_fixtures/all_keylets/Cargo.toml new file mode 100644 index 0000000000..ad53fd62b1 --- /dev/null +++ b/src/test/app/wasm_fixtures/all_keylets/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2024" +name = "all_keylets" +version = "0.0.1" + +# This empty workspace definition keeps this project independent of the parent workspace +[workspace] + +[lib] +crate-type = ["cdylib"] + +[profile.release] +lto = true +opt-level = 's' +panic = "abort" + +[dependencies] +xrpl-std = { git = "https://github.com/ripple/xrpl-wasm-stdlib.git", package = "xrpl-wasm-stdlib", branch = "renames" } + +[profile.dev] +panic = "abort" diff --git a/src/test/app/wasm_fixtures/all_keylets/src/lib.rs b/src/test/app/wasm_fixtures/all_keylets/src/lib.rs new file mode 100644 index 0000000000..f0a4e5abb5 --- /dev/null +++ b/src/test/app/wasm_fixtures/all_keylets/src/lib.rs @@ -0,0 +1,176 @@ +#![cfg_attr(target_arch = "wasm32", no_std)] + +#[cfg(not(target_arch = "wasm32"))] +extern crate std; + +use crate::host::{Error, Result, Result::Err, Result::Ok}; +use xrpl_std::core::keylets; +use xrpl_std::core::ledger_objects::current_escrow::get_current_escrow; +use xrpl_std::core::ledger_objects::current_escrow::CurrentEscrow; +use xrpl_std::core::ledger_objects::ledger_object; +use xrpl_std::core::ledger_objects::traits::CurrentEscrowFields; +use xrpl_std::core::ledger_objects::LedgerObjectFieldGetter; +use xrpl_std::core::types::currency::Currency; +use xrpl_std::core::types::issue::{IouIssue, Issue, XrpIssue}; +use xrpl_std::core::types::mpt_id::MptId; +use xrpl_std::host; +use xrpl_std::host::trace::{trace, trace_acct, trace_data, trace_num, DataRepr}; +use xrpl_std::sfield; + +pub fn object_exists( + keylet_result: Result, + keylet_type: &str, + sfield: sfield::SField, +) -> Result { + let field = CODE; + match keylet_result { + Ok(keylet) => { + let _ = trace_data(keylet_type, &keylet, DataRepr::AsHex); + + let slot = unsafe { host::cache_le(keylet.as_ptr(), keylet.len(), 0) }; + if slot <= 0 { + let _ = trace_num("Error: ", slot.into()); + return Err(Error::from_code(slot)); + } + if field == 0 { + let new_field = sfield::PreviousTxnID; + let _ = trace_num("Getting field: ", new_field.clone().into()); + match ledger_object::get_field(slot, new_field) { + Ok(data) => { + let _ = trace_data("Field data: ", &data.0, DataRepr::AsHex); + } + Err(result_code) => { + let _ = trace_num("Error getting field: ", result_code.into()); + return Err(result_code); + } + } + } else { + let _ = trace_num("Getting field: ", field.into()); + match ledger_object::get_field(slot, sfield) { + Ok(_data) => { + let _ = trace("Field data: retrieved"); + } + Err(result_code) => { + let _ = trace_num("Error getting field: ", result_code.into()); + return Err(result_code); + } + } + } + + Ok(true) + } + Err(error) => { + let _ = trace_num("Error getting keylet: ", error.into()); + Err(error) + } + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn escrow_finish() -> i32 { + let _ = trace("$$$$$ STARTING WASM EXECUTION $$$$$"); + + let escrow: CurrentEscrow = get_current_escrow(); + + let account = escrow.get_account().unwrap_or_panic(); + let _ = trace_acct("Account:", &account); + + let destination = escrow.get_destination().unwrap_or_panic(); + let _ = trace_acct("Destination:", &destination); + + let mut seq = 5; + + macro_rules! check_object_exists { + ($keylet:expr, $type:expr, $field:expr) => { + match object_exists($keylet, $type, $field) { + Ok(_exists) => { + // false isn't returned + let _ = trace(concat!( + $type, + " object exists, proceeding with escrow finish." + )); + } + Err(error) => { + let _ = trace_num("Current seq value:", seq.try_into().unwrap()); + return error.code(); + } + } + }; + } + + let accountroot_id = keylets::accountroot_id(&account); + check_object_exists!(accountroot_id, "Account", sfield::Account); + + let currency_code: &[u8; 3] = b"USD"; + let currency: Currency = Currency::from(*currency_code); + let trustline_id = keylets::trustline_id(&account, &destination, ¤cy); + check_object_exists!(trustline_id, "Trustline", sfield::Generic); + seq += 1; + + let asset1 = Issue::XRP(XrpIssue {}); + let asset2 = Issue::IOU(IouIssue::new(destination, currency)); + check_object_exists!(keylets::amm_id(&asset1, &asset2), "AMM", sfield::Account); + + let check_id = keylets::check_id(&account, seq); + check_object_exists!(check_id, "Check", sfield::Account); + seq += 1; + + let cred_type: &[u8] = b"termsandconditions"; + let credential_id = keylets::credential_id(&account, &account, cred_type); + check_object_exists!(credential_id, "Credential", sfield::Subject); + seq += 1; + + let delegate_id = keylets::delegate_id(&account, &destination); + check_object_exists!(delegate_id, "Delegate", sfield::Account); + seq += 1; + + let deposit_preauth_id = keylets::deposit_preauth_id(&account, &destination); + check_object_exists!(deposit_preauth_id, "DepositPreauth", sfield::Account); + seq += 1; + + let did_id = keylets::did_id(&account); + check_object_exists!(did_id, "DID", sfield::Account); + seq += 1; + + let escrow_id = keylets::escrow_id(&account, seq); + check_object_exists!(escrow_id, "Escrow", sfield::Account); + seq += 1; + + let mpt_issuance_id = keylets::mpt_issuance_id(&account, seq); + let mpt_id = MptId::new(seq.try_into().unwrap(), account); + check_object_exists!(mpt_issuance_id, "MPTIssuance", sfield::Issuer); + seq += 1; + + let mptoken_id = keylets::mptoken_id(&mpt_id, &destination); + check_object_exists!(mptoken_id, "MPToken", sfield::Account); + + let nft_offer_id = keylets::nft_offer_id(&destination, 6); + check_object_exists!(nft_offer_id, "NFTokenOffer", sfield::Owner); + + let offer_id = keylets::offer_id(&account, seq); + check_object_exists!(offer_id, "Offer", sfield::Account); + seq += 1; + + let paychan_id = keylets::paychan_id(&account, &destination, seq); + check_object_exists!(paychan_id, "PayChannel", sfield::Account); + seq += 1; + + let pd_id = keylets::permissioned_domain_id(&account, seq); + check_object_exists!(pd_id, "PermissionedDomain", sfield::Owner); + seq += 1; + + let signers_id = keylets::signers_id(&account); + check_object_exists!(signers_id, "SignerList", sfield::Generic); + seq += 1; + + seq += 1; // ticket sequence number is one greater + let ticket_id = keylets::ticket_id(&account, seq); + check_object_exists!(ticket_id, "Ticket", sfield::Account); + seq += 1; + + let vault_id = keylets::vault_id(&account, seq); + check_object_exists!(vault_id, "Vault", sfield::Account); + // seq += 1; + + 1 // All keylets exist, finish the escrow. +} diff --git a/src/test/app/wasm_fixtures/bad_align.c b/src/test/app/wasm_fixtures/bad_align.c new file mode 100644 index 0000000000..560245e762 --- /dev/null +++ b/src/test/app/wasm_fixtures/bad_align.c @@ -0,0 +1,42 @@ +#include + +int32_t float_from_uint(uint8_t const *, int32_t, uint8_t *, int32_t, int32_t); +int32_t check_id(uint8_t const *, int32_t, uint8_t const *, int32_t, uint8_t *, + int32_t); + +uint8_t e_data1[32 * 1024]; +uint8_t e_data2[32 * 1024]; + +int32_t test1() +{ + e_data1[1] = 0xFF; + e_data1[2] = 0xFF; + e_data1[3] = 0xFF; + e_data1[4] = 0xFF; + e_data1[5] = 0xFF; + e_data1[6] = 0xFF; + e_data1[7] = 0xFF; + e_data1[8] = 0xFF; + int32_t result = float_from_uint(&e_data1[1], 8, &e_data1[35], 12, 0); + return result >= 0 ? *((int32_t *)(&e_data1[36])) : result; +} + +int32_t test2() +{ + // Set up misaligned uint32 (seq) at offset 1 + e_data2[1] = 0xFF; + e_data2[2] = 0xFF; + e_data2[3] = 0xFF; + e_data2[4] = 0xFF; + // Set up valid non-zero AccountID (20 bytes) at offset 10 + for (int i = 0; i < 20; i++) + e_data2[10 + i] = i + 1; + // Call check_id with misaligned uint32 at &e_data2[1] to hit line 72 in + // HostFuncWrapper.cpp + int32_t result = check_id(&e_data2[10], 20, &e_data2[1], 4, &e_data2[35], 32); + // Return the misaligned value directly to validate it was read correctly (-1 + // if all 0xFF) + return result >= 0 ? *((int32_t *)(&e_data2[36])) : result; +} + +int32_t test() { return test1() + test2(); } diff --git a/src/test/app/wasm_fixtures/codecov_tests/Cargo.lock b/src/test/app/wasm_fixtures/codecov_tests/Cargo.lock new file mode 100644 index 0000000000..d7d91db071 --- /dev/null +++ b/src/test/app/wasm_fixtures/codecov_tests/Cargo.lock @@ -0,0 +1,171 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "codecov_tests" +version = "0.0.1" +dependencies = [ + "xrpl-wasm-stdlib", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "xrpl-macros" +version = "0.1.0" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=renames#9822d645870908a79d87a57b0244caa6359cb9cf" +dependencies = [ + "bs58", + "quote", + "sha2", + "syn", +] + +[[package]] +name = "xrpl-wasm-stdlib" +version = "0.8.0" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=renames#9822d645870908a79d87a57b0244caa6359cb9cf" +dependencies = [ + "xrpl-macros", +] diff --git a/src/test/app/wasm_fixtures/codecov_tests/Cargo.toml b/src/test/app/wasm_fixtures/codecov_tests/Cargo.toml new file mode 100644 index 0000000000..1cc49ac490 --- /dev/null +++ b/src/test/app/wasm_fixtures/codecov_tests/Cargo.toml @@ -0,0 +1,18 @@ +[package] +edition = "2024" +name = "codecov_tests" +version = "0.0.1" + +# This empty workspace definition keeps this project independent of the parent workspace +[workspace] + +[lib] +crate-type = ["cdylib"] + +[profile.release] +lto = true +opt-level = 's' +panic = "abort" + +[dependencies] +xrpl-std = { git = "https://github.com/ripple/xrpl-wasm-stdlib.git", package = "xrpl-wasm-stdlib", branch = "renames" } diff --git a/src/test/app/wasm_fixtures/codecov_tests/src/host_bindings_loose.rs b/src/test/app/wasm_fixtures/codecov_tests/src/host_bindings_loose.rs new file mode 100644 index 0000000000..6204dff0a2 --- /dev/null +++ b/src/test/app/wasm_fixtures/codecov_tests/src/host_bindings_loose.rs @@ -0,0 +1,47 @@ +//TODO add docs after discussing the interface +//Note that Craft currently does not honor the rounding modes +#[allow(unused)] +pub const FLOAT_ROUNDING_MODES_TO_NEAREST: i32 = 0; +#[allow(unused)] +pub const FLOAT_ROUNDING_MODES_TOWARDS_ZERO: i32 = 1; +#[allow(unused)] +pub const FLOAT_ROUNDING_MODES_DOWNWARD: i32 = 2; +#[allow(unused)] +pub const FLOAT_ROUNDING_MODES_UPWARD: i32 = 3; + +// pub enum RippledRoundingModes{ +// ToNearest = 0, +// TowardsZero = 1, +// DOWNWARD = 2, +// UPWARD = 3 +// } + +#[allow(unused)] +#[link(wasm_import_module = "host_lib")] +unsafe extern "C" { + pub fn parent_ldgr_hash(out_buff_ptr: i32, out_buff_len: i32) -> i32; + + pub fn cache_le(keylet_ptr: i32, keylet_len: i32, cache_num: i32) -> i32; + + pub fn tx_inner_arr_len(locator_ptr: i32, locator_len: i32) -> i32; + + pub fn accountroot_id( + account_ptr: i32, + account_len: i32, + out_buff_ptr: *mut u8, + out_buff_len: usize, + ) -> i32; + + pub fn trustline_id( + account1_ptr: *const u8, + account1_len: usize, + account2_ptr: *const u8, + account2_len: usize, + currency_ptr: i32, + currency_len: i32, + out_buff_ptr: *mut u8, + out_buff_len: usize, + ) -> i32; + + pub fn trace_num(msg_read_ptr: i32, msg_read_len: i32, number: i64) -> i32; +} diff --git a/src/test/app/wasm_fixtures/codecov_tests/src/lib.rs b/src/test/app/wasm_fixtures/codecov_tests/src/lib.rs new file mode 100644 index 0000000000..59f16155d2 --- /dev/null +++ b/src/test/app/wasm_fixtures/codecov_tests/src/lib.rs @@ -0,0 +1,1782 @@ +#![cfg_attr(target_arch = "wasm32", no_std)] + +#[cfg(not(target_arch = "wasm32"))] +extern crate std; + +use core::panic; +use xrpl_std::core::current_tx::escrow_finish::{get_current_escrow_finish, EscrowFinish}; +use xrpl_std::core::current_tx::traits::TransactionCommonFields; +use xrpl_std::core::keylets; +use xrpl_std::core::locator::Locator; +use xrpl_std::core::types::blob::DEFAULT_BLOB_SIZE; +use xrpl_std::core::types::issue::Issue; +use xrpl_std::core::types::issue::XrpIssue; +use xrpl_std::core::types::mpt_id::MptId; +use xrpl_std::host; +use xrpl_std::host::error_codes; +use xrpl_std::host::trace::{trace, trace_num as trace_number}; +use xrpl_std::sfield; +use xrpl_std::types::XRPL_CONTRACT_DATA_SIZE; + +mod host_bindings_loose; +include!("host_bindings_loose.rs"); + +fn check_result(result: i32, expected: i32, test_name: &'static str) { + match result { + code if code == expected => { + let _ = trace_number(test_name, code.into()); + } + code if code >= 0 => { + let _ = trace(test_name); + let _ = trace_number("TEST FAILED", code.into()); + panic!("Unexpected success code: {}", code); + } + code => { + let _ = trace(test_name); + let _ = trace_number("TEST FAILED", code.into()); + panic!("Error code: {}", code); + } + } +} + +fn with_buffer(mut f: F) -> R +where + F: FnMut(*mut u8, usize) -> R, +{ + let mut buf = [0u8; N]; + f(buf.as_mut_ptr(), buf.len()) +} + +#[unsafe(no_mangle)] +pub extern "C" fn escrow_finish() -> i32 { + let _ = trace("$$$$$ STARTING WASM EXECUTION $$$$$"); + + // ######################################## + // Step #1: Test all host function happy paths + // Note: not testing all the keylet functions, + // that's in a separate test file (all_keylets). + // The float tests are also in a separate file (float_tests). + // ######################################## + with_buffer::<4, _, _>(|ptr, len| { + check_result(unsafe { host::ldgr_index(ptr, len) }, 4, "ldgr_index"); + }); + with_buffer::<4, _, _>(|ptr, len| { + check_result( + unsafe { host::parent_ldgr_time(ptr, len) }, + 4, + "parent_ldgr_time", + ); + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { host::parent_ldgr_hash(ptr, len) }, + 32, + "parent_ldgr_hash", + ); + }); + with_buffer::<4, _, _>(|ptr, len| { + check_result(unsafe { host::base_fee(ptr, len) }, 4, "base_fee"); + }); + let amendment_name: &[u8] = b"test_amendment"; + let amendment_id: [u8; 32] = [1; 32]; + check_result( + unsafe { host::amendment_enabled(amendment_name.as_ptr(), amendment_name.len()) }, + 1, + "amendment_enabled", + ); + check_result( + unsafe { host::amendment_enabled(amendment_id.as_ptr(), amendment_id.len()) }, + 1, + "amendment_enabled", + ); + let tx: EscrowFinish = get_current_escrow_finish(); + let account = tx.get_account().unwrap_or_panic(); // get_tx_field under the hood + let keylet = keylets::accountroot_id(&account).unwrap_or_panic(); // accountroot_id under the hood + check_result( + unsafe { host::cache_le(keylet.as_ptr(), keylet.len(), 0) }, + 1, + "cache_le", + ); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { host::home_le_field(sfield::Account.into(), ptr, len) }, + 20, + "home_le_field", + ); + }); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { host::le_field(1, sfield::Account.into(), ptr, len) }, + 20, + "le_field", + ); + }); + let mut locator = Locator::new(); + locator.pack(sfield::Account); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { host::tx_inner(locator.as_ptr(), locator.len(), ptr, len) }, + 20, + "tx_inner", + ); + }); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { host::home_le_inner(locator.as_ptr(), locator.len(), ptr, len) }, + 20, + "home_le_inner", + ); + }); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { host::le_inner(1, locator.as_ptr(), locator.len(), ptr, len) }, + 20, + "le_inner", + ); + }); + check_result( + unsafe { host::tx_arr_len(sfield::Memos.into()) }, + 32, + "tx_arr_len", + ); + check_result( + unsafe { host::home_le_arr_len(sfield::Memos.into()) }, + 32, + "home_le_arr_len", + ); + check_result( + unsafe { host::le_arr_len(1, sfield::Memos.into()) }, + 32, + "le_arr_len", + ); + check_result( + unsafe { host::tx_inner_arr_len(locator.as_ptr(), locator.len()) }, + 32, + "tx_inner_arr_len", + ); + check_result( + unsafe { host::home_le_inner_arr_len(locator.as_ptr(), locator.len()) }, + 32, + "home_le_inner_arr_len", + ); + check_result( + unsafe { host::le_inner_arr_len(1, locator.as_ptr(), locator.len()) }, + 32, + "le_inner_arr_len", + ); + check_result( + unsafe { host::set_data(account.0.as_ptr(), account.0.len()) }, + 20, + "set_data", + ); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { host::sha512_half(locator.as_ptr(), locator.len(), ptr, len) }, + 32, + "sha512_half", + ); + }); + let message: &[u8] = b"test message"; + let pubkey: &[u8] = b"test pubkey"; //tx.get_public_key().unwrap_or_panic(); + let signature: &[u8] = b"test signature"; + check_result( + unsafe { + host::check_sig( + message.as_ptr(), + message.len(), + pubkey.as_ptr(), + pubkey.len(), + signature.as_ptr(), + signature.len(), + ) + }, + 1, + "check_sig", + ); + + let nft_id: [u8; 32] = amendment_id; + with_buffer::<18, _, _>(|ptr, len| { + check_result( + unsafe { + host::nft_uri( + account.0.as_ptr(), + account.0.len(), + nft_id.as_ptr(), + nft_id.len(), + ptr, + len, + ) + }, + 18, + "nft_uri", + ) + }); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { host::nft_issuer(nft_id.as_ptr(), nft_id.len(), ptr, len) }, + 20, + "nft_issuer", + ) + }); + with_buffer::<4, _, _>(|ptr, len| { + check_result( + unsafe { host::nft_taxon(nft_id.as_ptr(), nft_id.len(), ptr, len) }, + 4, + "nft_taxon", + ) + }); + check_result( + unsafe { host::nft_flags(nft_id.as_ptr(), nft_id.len()) }, + 8, + "nft_flags", + ); + check_result( + unsafe { host::nft_xfer_fee(nft_id.as_ptr(), nft_id.len()) }, + 10, + "nft_xfer_fee", + ); + with_buffer::<4, _, _>(|ptr, len| { + check_result( + unsafe { host::nft_serial(nft_id.as_ptr(), nft_id.len(), ptr, len) }, + 4, + "nft_serial", + ) + }); + let message = "testing trace"; + check_result( + unsafe { + host::trace_acct( + message.as_ptr(), + message.len(), + account.0.as_ptr(), + account.0.len(), + ) + }, + 0, + "trace_acct", + ); + let amount = &[0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F]; // 95 drops of XRP + check_result( + unsafe { + host::trace_amt( + message.as_ptr(), + message.len(), + amount.as_ptr(), + amount.len(), + ) + }, + 0, + "trace_amt", + ); + let amount = &[0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; // 0 drops of XRP + check_result( + unsafe { + host::trace_amt( + message.as_ptr(), + message.len(), + amount.as_ptr(), + amount.len(), + ) + }, + 0, + "trace_amt_zero", + ); + + // ######################################## + // Step #2: Test set_data edge cases + // ######################################## + check_result( + unsafe { host_bindings_loose::parent_ldgr_hash(-1, 4) }, + error_codes::INVALID_PARAMS, + "parent_ldgr_hash_neg_ptr", + ); + with_buffer::<4, _, _>(|ptr, _len| { + check_result( + unsafe { host_bindings_loose::parent_ldgr_hash(ptr as i32, -1) }, + error_codes::INVALID_PARAMS, + "parent_ldgr_hash_neg_len", + ) + }); + with_buffer::<3, _, _>(|ptr, len| { + check_result( + unsafe { host_bindings_loose::parent_ldgr_hash(ptr as i32, len as i32) }, + error_codes::BUFFER_TOO_SMALL, + "parent_ldgr_hash_buf_too_small", + ) + }); + with_buffer::<4, _, _>(|ptr, _len| { + check_result( + unsafe { host_bindings_loose::parent_ldgr_hash(ptr as i32, 1_000_000_000) }, + error_codes::POINTER_OUT_OF_BOUNDS, + "parent_ldgr_hash_len_too_long", + ) + }); + + // ######################################## + // Step #3: Test getData[Type] edge cases + // ######################################## + + // SField + check_result( + unsafe { host::tx_arr_len(2) }, // not a valid SField value + error_codes::INVALID_FIELD, + "tx_arr_len_invalid_sfield", + ); + + // Slice + check_result( + unsafe { host_bindings_loose::tx_inner_arr_len(-1, locator.len() as i32) }, + error_codes::INVALID_PARAMS, + "tx_inner_arr_len_neg_ptr", + ); + check_result( + unsafe { host_bindings_loose::tx_inner_arr_len(locator.as_ptr() as i32, -1) }, + error_codes::INVALID_PARAMS, + "tx_inner_arr_len_neg_len", + ); + let long_len = DEFAULT_BLOB_SIZE + 1; + check_result( + unsafe { host_bindings_loose::tx_inner_arr_len(locator.as_ptr() as i32, long_len as i32) }, + error_codes::DATA_FIELD_TOO_LARGE, + "tx_inner_arr_len_too_long", + ); + check_result( + unsafe { + host_bindings_loose::tx_inner_arr_len( + locator.as_ptr() as i32 + 1_000_000_000, + locator.len() as i32, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "tx_inner_arr_len_ptr_oob", + ); + + // uint32 + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::check_id( + account.0.as_ptr(), + account.0.len(), + locator.as_ptr().wrapping_add(1_000_000_000), + 8, + ptr, + len, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "check_id_oob_len_u32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::check_id( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "check_id_wrong_len_u32", + ) + }); + + // uint64 + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_from_uint( + locator.as_ptr().wrapping_add(1_000_000_000), + 8, + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_from_uint_len_oob", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_from_uint( + locator.as_ptr(), + locator.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::INVALID_PARAMS, + "float_from_uint_wrong_len_uint64", + ) + }); + + // uint256 + check_result( + unsafe { + host_bindings_loose::cache_le( + locator.as_ptr() as i32 + 1_000_000_000, + locator.len() as i32, + 1, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "cache_le_ptr_oob", + ); + check_result( + unsafe { host_bindings_loose::cache_le(locator.as_ptr() as i32, locator.len() as i32, 1) }, + error_codes::INVALID_PARAMS, + "cache_le_wrong_len", + ); + + // AccountID + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host_bindings_loose::accountroot_id( + locator.as_ptr() as i32 + 1_000_000_000, + locator.len() as i32, + ptr, + len, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "accountroot_id_len_oob", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host_bindings_loose::accountroot_id( + locator.as_ptr() as i32, + locator.len() as i32, + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "accountroot_id_wrong_len", + ) + }); + + // Currency + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host_bindings_loose::trustline_id( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + locator.as_ptr() as i32 + 1_000_000_000, + locator.len() as i32, + ptr, + len, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trustline_id_len_oob_currency", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host_bindings_loose::trustline_id( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + locator.as_ptr() as i32, + locator.len() as i32, + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "trustline_id_wrong_len_currency", + ) + }); + + // Issue + let asset1_bytes = Issue::XRP(XrpIssue {}).as_bytes(); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_id( + asset1_bytes.as_ptr(), + asset1_bytes.len(), + locator.as_ptr().wrapping_add(1_000_000_000), + locator.len(), + ptr, + len, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "amm_id_len_oob_asset2", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_id( + asset1_bytes.as_ptr(), + asset1_bytes.len(), + locator.as_ptr(), + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "amm_id_len_wrong_len_asset2", + ) + }); + let currency: &[u8] = b"USD00000000000000000"; // 20 bytes + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_id( + asset1_bytes.as_ptr(), + asset1_bytes.len(), + currency.as_ptr(), + currency.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "amm_id_len_wrong_non_xrp_currency_len", + ) + }); + let xrp_issue: &[u8] = &[0; 40]; // 40 bytes + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_id( + xrp_issue.as_ptr(), + xrp_issue.len(), + asset1_bytes.as_ptr(), + asset1_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "amm_id_len_wrong_xrp_currency_len", + ) + }); + let mptid = MptId::new(1, account); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_id( + mptid.as_ptr(), + mptid.len(), + asset1_bytes.as_ptr(), + asset1_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "amm_id_mpt", + ) + }); + + // string + check_result( + unsafe { + host_bindings_loose::trace_num( + locator.as_ptr() as i32 + 1_000_000_000, + locator.len() as i32, + 42, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_num_oob_str", + ); + + // ######################################## + // Step #4: Test other host function edge cases + // ######################################## + + // invalid SFields + + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::tx_field(2, ptr, len) }, + error_codes::INVALID_FIELD, + "tx_field_invalid_sfield", + ); + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::home_le_field(2, ptr, len) }, + error_codes::INVALID_FIELD, + "home_le_field_invalid_sfield", + ); + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::le_field(1, 2, ptr, len) }, + error_codes::INVALID_FIELD, + "le_field_invalid_sfield", + ); + }); + check_result( + unsafe { host::tx_arr_len(2) }, + error_codes::INVALID_FIELD, + "tx_arr_len_invalid_sfield", + ); + check_result( + unsafe { host::home_le_arr_len(2) }, + error_codes::INVALID_FIELD, + "home_le_arr_len_invalid_sfield", + ); + check_result( + unsafe { host::le_arr_len(1, 2) }, + error_codes::INVALID_FIELD, + "le_arr_len_invalid_sfield", + ); + + // invalid Slice + + check_result( + unsafe { host::amendment_enabled(amendment_name.as_ptr(), long_len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "amendment_enabled_too_big_slice", + ); + check_result( + unsafe { host::amendment_enabled(amendment_name.as_ptr(), 65) }, + error_codes::DATA_FIELD_TOO_LARGE, + "amendment_enabled_too_long", + ); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::tx_inner(locator.as_ptr(), long_len, ptr, len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "tx_inner_too_big_slice", + ); + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::home_le_inner(locator.as_ptr(), long_len, ptr, len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "home_le_inner_too_big_slice", + ); + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::le_inner(1, locator.as_ptr(), long_len, ptr, len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "le_inner_too_big_slice", + ); + }); + check_result( + unsafe { host::tx_inner_arr_len(locator.as_ptr(), long_len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "tx_inner_arr_len_too_big_slice", + ); + check_result( + unsafe { host::home_le_inner_arr_len(locator.as_ptr(), long_len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "home_le_inner_arr_len_too_big_slice", + ); + check_result( + unsafe { host::le_inner_arr_len(1, locator.as_ptr(), long_len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "le_inner_arr_len_too_big_slice", + ); + let too_big_data_len = XRPL_CONTRACT_DATA_SIZE + 1; + check_result( + unsafe { host::set_data(locator.as_ptr(), too_big_data_len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "set_data_too_big_slice", + ); + check_result( + unsafe { + host::check_sig( + message.as_ptr(), + long_len, + pubkey.as_ptr(), + pubkey.len(), + signature.as_ptr(), + signature.len(), + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "check_sig", + ); + check_result( + unsafe { + host::check_sig( + message.as_ptr(), + message.len(), + pubkey.as_ptr(), + long_len, + signature.as_ptr(), + signature.len(), + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "check_sig", + ); + check_result( + unsafe { + host::check_sig( + message.as_ptr(), + message.len(), + pubkey.as_ptr(), + pubkey.len(), + signature.as_ptr(), + long_len, + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "check_sig", + ); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::sha512_half(locator.as_ptr(), long_len, ptr, len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "sha512_half_too_big_slice", + ); + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_id( + asset1_bytes.as_ptr(), + long_len, + asset1_bytes.as_ptr(), + asset1_bytes.len(), + ptr, + len, + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "amm_id_too_big_slice", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::credential_id( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), + long_len, + ptr, + len, + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "credential_id_too_big_slice", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::mptoken_id( + mptid.as_ptr(), + long_len, + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "mptoken_id_too_big_slice_mptid", + ) + }); + check_result( + unsafe { + host::trace( + message.as_ptr(), + message.len(), + locator.as_ptr().wrapping_add(1_000_000_000), + locator.len(), + 0, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_oob_slice", + ); + let float: [u8; 8] = [0xD4, 0x83, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00]; + check_result( + unsafe { + host::trace_xfloat( + message.as_ptr(), + message.len(), + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_xfloat_oob_slice", + ); + check_result( + unsafe { + host::trace_amt( + message.as_ptr(), + message.len(), + locator.as_ptr().wrapping_add(1_000_000_000), + locator.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_amt_oob_slice", + ); + check_result( + unsafe { + host::float_cmp( + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + float.as_ptr(), + float.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_cmp_oob_slice1", + ); + check_result( + unsafe { + host::float_cmp( + float.as_ptr(), + float.len(), + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_cmp_oob_slice2", + ); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_add( + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + float.as_ptr(), + float.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_add_oob_slice1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_add( + float.as_ptr(), + float.len(), + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_add_oob_slice2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_sub( + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + float.as_ptr(), + float.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_sub_oob_slice1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_sub( + float.as_ptr(), + float.len(), + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_sub_oob_slice2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_mult( + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + float.as_ptr(), + float.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_mult_oob_slice1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_mult( + float.as_ptr(), + float.len(), + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_mult_oob_slice2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_div( + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + float.as_ptr(), + float.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_div_oob_slice1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_div( + float.as_ptr(), + float.len(), + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_div_oob_slice2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_root( + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + 3, + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_root_oob_slice", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_pow( + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + 3, + ptr, + len, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_pow_oob_slice", + ) + }); + + // invalid UInt32 + + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::escrow_id( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "escrow_id_wrong_size_uint32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::mpt_issuance_id( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "mpt_issuance_id_wrong_size_uint32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::nft_offer_id( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "nft_offer_id_wrong_size_uint32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::offer_id( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "offer_id_wrong_size_uint32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::oracle_id( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "oracle_id_wrong_size_uint32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::paychan_id( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "paychan_id_wrong_size_uint32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::permissioned_domain_id( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "permissioned_domain_id_wrong_size_uint32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::ticket_id( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "ticket_id_wrong_size_uint32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::vault_id( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "vault_id_wrong_size_uint32", + ) + }); + + // invalid UInt256 + + check_result( + unsafe { host::cache_le(locator.as_ptr(), locator.len(), 0) }, + error_codes::INVALID_PARAMS, + "cache_le_wrong_size_uint256", + ); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::nft_uri( + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "nft_uri_wrong_size_uint256", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::nft_issuer(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "nft_issuer_wrong_size_uint256", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::nft_taxon(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "nft_taxon_wrong_size_uint256", + ) + }); + check_result( + unsafe { host::nft_flags(locator.as_ptr(), locator.len()) }, + error_codes::INVALID_PARAMS, + "nft_flags_wrong_size_uint256", + ); + check_result( + unsafe { host::nft_xfer_fee(locator.as_ptr(), locator.len()) }, + error_codes::INVALID_PARAMS, + "nft_xfer_fee_wrong_size_uint256", + ); + with_buffer::<4, _, _>(|ptr, len| { + check_result( + unsafe { host::nft_serial(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "nft_serial_wrong_size_uint256", + ) + }); + + // invalid AccountID + + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::accountroot_id(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "accountroot_id_wrong_size_account_id", + ) + }); + let seq: i32 = 1; + let seq_bytes = seq.to_be_bytes(); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::check_id( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "check_id_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::credential_id( + locator.as_ptr(), // invalid AccountID size + locator.len(), + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), // valid slice size + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "credential_id_wrong_size_account_id1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::credential_id( + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), // invalid AccountID size + locator.len(), + locator.as_ptr(), // valid slice size + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "credential_id_wrong_size_account_id2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::delegate_id( + locator.as_ptr(), // invalid AccountID size + locator.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "delegate_id_wrong_size_account_id1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::delegate_id( + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), // invalid AccountID size + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "delegate_id_wrong_size_account_id2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::deposit_preauth_id( + locator.as_ptr(), // invalid AccountID size + locator.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "deposit_preauth_id_wrong_size_account_id1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::deposit_preauth_id( + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), // invalid AccountID size + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "deposit_preauth_id_wrong_size_account_id2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::did_id(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "did_id_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::escrow_id( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "escrow_id_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::trustline_id( + locator.as_ptr(), // invalid AccountID size + locator.len(), + account.0.as_ptr(), + account.0.len(), + currency.as_ptr(), + currency.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "trustline_id_wrong_size_account_id1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::trustline_id( + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), // invalid AccountID size + locator.len(), + currency.as_ptr(), + currency.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "trustline_id_wrong_size_account_id2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::mpt_issuance_id( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "mpt_issuance_id_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::mptoken_id( + mptid.as_ptr(), + mptid.len(), + locator.as_ptr(), + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "mptoken_id_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::nft_offer_id( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "nft_offer_id_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::offer_id( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "offer_id_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::oracle_id( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "oracle_id_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::paychan_id( + locator.as_ptr(), // invalid AccountID size + locator.len(), + account.0.as_ptr(), + account.0.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "paychan_id_wrong_size_account_id1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::paychan_id( + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), // invalid AccountID size + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "paychan_id_wrong_size_account_id2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::permissioned_domain_id( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "permissioned_domain_id_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::signers_id(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "signers_id_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::ticket_id( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "ticket_id_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::vault_id( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "vault_id_wrong_size_account_id", + ) + }); + let uint256: &[u8] = b"00000000000000000000000000000001"; + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::nft_uri( + locator.as_ptr(), + locator.len(), + uint256.as_ptr(), + uint256.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "nft_uri_wrong_size_account_id", + ) + }); + check_result( + unsafe { + host::trace_acct( + message.as_ptr(), + message.len(), + locator.as_ptr(), + locator.len(), + ) + }, + error_codes::INVALID_PARAMS, + "trace_acct_wrong_size_account_id", + ); + + // invalid Currency was already tested above + // invalid string + + check_result( + unsafe { + host::trace( + message.as_ptr().wrapping_add(1_000_000_000), + message.len(), + uint256.as_ptr(), + uint256.len(), + 0, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_oob_string", + ); + check_result( + unsafe { + host::trace_xfloat( + message.as_ptr().wrapping_add(1_000_000_000), + message.len(), + float.as_ptr(), + float.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_xfloat_oob_string", + ); + check_result( + unsafe { + host::trace_acct( + message.as_ptr().wrapping_add(1_000_000_000), + message.len(), + account.0.as_ptr(), + account.0.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_acct_oob_string", + ); + check_result( + unsafe { + host::trace_amt( + message.as_ptr().wrapping_add(1_000_000_000), + message.len(), + amount.as_ptr(), + amount.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_amt_oob_string", + ); + + // trace too large + + check_result( + unsafe { + host::trace( + locator.as_ptr(), + locator.len(), + locator.as_ptr(), + long_len, + 0, + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "trace_too_long", + ); + check_result( + unsafe { host::trace_num(locator.as_ptr(), long_len, 1) }, + error_codes::DATA_FIELD_TOO_LARGE, + "trace_num_too_long", + ); + check_result( + unsafe { host::trace_xfloat(message.as_ptr(), long_len, float.as_ptr(), float.len()) }, + error_codes::DATA_FIELD_TOO_LARGE, + "trace_xfloat_too_long", + ); + check_result( + unsafe { + host::trace_acct( + message.as_ptr(), + long_len, + account.0.as_ptr(), + account.0.len(), + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "trace_acct_too_long", + ); + check_result( + unsafe { host::trace_amt(message.as_ptr(), long_len, amount.as_ptr(), amount.len()) }, + error_codes::DATA_FIELD_TOO_LARGE, + "trace_amt_too_long", + ); + + // trace amount errors + + check_result( + unsafe { + host::trace_amt( + message.as_ptr(), + message.len(), + locator.as_ptr(), + locator.len(), + ) + }, + error_codes::INVALID_PARAMS, + "trace_amt_wrong_length", + ); + + // other misc errors + + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::mptoken_id( + locator.as_ptr(), + locator.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "mptoken_id_mptid_wrong_length", + ) + }); + check_result( + unsafe { + host::trace( + message.as_ptr(), + message.len(), + locator.as_ptr(), + locator.len(), + 2, + ) + }, + error_codes::INVALID_PARAMS, + "trace_invalid_as_hex", + ); + + // ensure that the Slice index desync issue is fixed + let empty: &[u8] = b""; + check_result( + unsafe { + host::trace_acct( + empty.as_ptr(), + empty.len(), + account.0.as_ptr(), + account.0.len(), + ) + }, + 0, + "trace_acct_check_desync", + ); + + 1 // <-- If we get here, finish the escrow. +} diff --git a/src/test/app/wasm_fixtures/copyFixtures.py b/src/test/app/wasm_fixtures/copyFixtures.py new file mode 100644 index 0000000000..8e457b71e2 --- /dev/null +++ b/src/test/app/wasm_fixtures/copyFixtures.py @@ -0,0 +1,287 @@ +# cspell: disable +import os +import re +import shlex +import subprocess +import sys +import tempfile +import zipfile +from difflib import get_close_matches + +OPT = "-Oz" +BASE_PATH = os.path.abspath(os.path.dirname(__file__)) + + +def pascal_case(name): + return "".join(word[:1].upper() + word[1:] for word in re.split(r"[_\W]+", name)) + + +def normalize_name(name): + name = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", name) + return re.sub(r"[^a-z0-9]", "", name.lower()) + + +def fixture_key(name): + name = normalize_name(name).removeprefix("k") + return name.removesuffix("wasmhex").removesuffix("hex") + + +def declared_fixtures(): + h_path = os.path.join(BASE_PATH, "fixtures.h") + with open(h_path, "r", encoding="utf8") as f: + return re.findall( + r"extern std::string const ([A-Za-z_][A-Za-z0-9_]*);", f.read() + ) + + +def find_fixture_name(project_name, suffix): + default = re.sub(r"_([a-z])", lambda m: m.group(1).upper(), project_name) + suffix + k_default = f"k{pascal_case(project_name)}{suffix}" + declarations = declared_fixtures() + normalized = {normalize_name(name): name for name in declarations} + fixture_keys = {fixture_key(name): name for name in declarations} + + for name in (default, k_default): + if normalize_name(name) in normalized: + return normalized[normalize_name(name)] + + project_key = normalize_name(project_name) + matches = [ + name + for key, name in fixture_keys.items() + if key.endswith(project_key) + or key.startswith(project_key) + or project_key.endswith(key) + or project_key.startswith(key) + ] + if len(matches) == 1: + return matches[0] + + close = get_close_matches(project_key, fixture_keys.keys(), n=1, cutoff=0.82) + if close: + return fixture_keys[close[0]] + + return k_default + + +def fixture_cpp_path(fixture_name): + pattern = rf"extern std::string const {fixture_name} =" + for file_name in os.listdir(BASE_PATH): + if not file_name.endswith(".cpp"): + continue + cpp_path = os.path.join(BASE_PATH, file_name) + with open(cpp_path, "r", encoding="utf8") as f: + if re.search(pattern, f.read()): + return cpp_path + return os.path.join(BASE_PATH, "fixtures.cpp") + + +def update_fixture(project_name, wasm, suffix="WasmHex"): + fixture_name = find_fixture_name(project_name, suffix) + print(f"Updating fixture: {fixture_name}") + + cpp_path = fixture_cpp_path(fixture_name) + h_path = os.path.join(BASE_PATH, "fixtures.h") + with open(cpp_path, "r", encoding="utf8") as f: + cpp_content = f.read() + + pattern = rf'extern std::string const {fixture_name} =[ \n]+"[^;]*;' + if re.search(pattern, cpp_content, flags=re.MULTILINE): + updated_cpp_content = re.sub( + pattern, + f'extern std::string const {fixture_name} = "{wasm}";', + cpp_content, + flags=re.MULTILINE, + ) + else: + with open(h_path, "r", encoding="utf8") as f: + h_content = f.read() + updated_h_content = ( + h_content.rstrip() + f"\n\nextern std::string const {fixture_name};\n" + ) + with open(h_path, "w", encoding="utf8") as f: + f.write(updated_h_content) + updated_cpp_content = ( + cpp_content.rstrip() + + f'\n\nextern std::string const {fixture_name} = "{wasm}";\n' + ) + + with open(cpp_path, "w", encoding="utf8") as f: + f.write(updated_cpp_content) + + +def read_wasm_hex(path): + with open(path, "rb") as f: + return f.read().hex() + + +def process_rust(project_name): + project_path = os.path.join(BASE_PATH, project_name) + wasm_location = os.path.join( + project_path, "target", "wasm32v1-none", "release", f"{project_name}.wasm" + ) + try: + subprocess.run( + ["cargo", "build", "--target", "wasm32v1-none", "--release"], + cwd=project_path, + check=True, + ) + subprocess.run( + ["wasm-opt", wasm_location, OPT, "-o", wasm_location], check=True + ) + print(f"WASM file for {project_name} has been built and optimized.") + except FileNotFoundError as e: + print(f"exec error: {e.filename} is required to build Rust fixtures") + sys.exit(1) + except subprocess.CalledProcessError as e: + print(f"exec error: {e}") + sys.exit(1) + + update_fixture(project_name, read_wasm_hex(wasm_location)) + + +def process_c(project_name): + project_path = os.path.join(BASE_PATH, f"{project_name}.c") + wasm_path = os.path.join(BASE_PATH, f"{project_name}.wasm") + cc = os.environ.get("CC") + sysroot = os.environ.get("SYSROOT") + if not cc or not sysroot: + print("exec error: CC and SYSROOT are required to build C fixtures") + sys.exit(1) + + build_cmd = [ + *shlex.split(cc), + f"--sysroot={sysroot}", + "-O3", + "-ffast-math", + "--target=wasm32", + "-fno-exceptions", + "-fno-threadsafe-statics", + "-fvisibility=default", + "-Wl,--export-all", + "-Wl,--no-entry", + "-Wl,--allow-undefined", + "-DNDEBUG", + "--no-standard-libraries", + "-fno-builtin-memset", + "-o", + wasm_path, + project_path, + ] + try: + subprocess.run(build_cmd, check=True) + subprocess.run(["wasm-opt", wasm_path, OPT, "-o", wasm_path], check=True) + print( + f"WASM file for {project_name} has been built with WASI support using clang." + ) + except FileNotFoundError as e: + print(f"exec error: {e.filename} is required to build C fixtures") + sys.exit(1) + except subprocess.CalledProcessError as e: + print(f"exec error: {e}") + sys.exit(1) + + update_fixture(project_name, read_wasm_hex(wasm_path)) + + +def wat_to_wasm(wat_path, wasm_path): + build_cmd = ["wat2wasm", wat_path, "-o", wasm_path] + try: + subprocess.run(build_cmd, check=True) + print(f"WASM file for {os.path.basename(wat_path)} has been built.") + except FileNotFoundError: + print("exec error: wat2wasm is required to build WAT fixtures") + sys.exit(1) + except subprocess.CalledProcessError as e: + print(f"exec error: {e}") + sys.exit(1) + + +def process_wat_file(wat_path): + project_name = os.path.splitext(os.path.basename(wat_path))[0] + with open(wat_path, "r", encoding="utf8") as f: + if "(module" not in f.read(): + print(f"Skipping WAT fixture without a module: {project_name}") + return + + with tempfile.TemporaryDirectory() as tmpdir: + wasm_path = os.path.join(tmpdir, f"{project_name}.wasm") + wat_to_wasm(wat_path, wasm_path) + update_fixture(project_name, read_wasm_hex(wasm_path), "Hex") + + +def process_wat_zip(zip_path): + project_name = os.path.splitext(os.path.basename(zip_path))[0] + with tempfile.TemporaryDirectory() as tmpdir: + with zipfile.ZipFile(zip_path) as archive: + wat_names = [name for name in archive.namelist() if name.endswith(".wat")] + if len(wat_names) != 1: + print(f"exec error: expected one .wat file in {zip_path}") + sys.exit(1) + archive.extract(wat_names[0], tmpdir) + + wasm_path = os.path.join(tmpdir, f"{project_name}.wasm") + wat_to_wasm(os.path.join(tmpdir, wat_names[0]), wasm_path) + update_fixture(project_name, read_wasm_hex(wasm_path), "Hex") + + +def process_wat(project_name): + candidates = [ + os.path.join(BASE_PATH, f"{project_name}.wat"), + os.path.join(BASE_PATH, "wat", f"{project_name}.wat"), + os.path.join(BASE_PATH, "wat", f"{project_name}.zip"), + ] + for path in candidates: + if os.path.isfile(path): + if path.endswith(".zip"): + process_wat_zip(path) + else: + process_wat_file(path) + return + + print(f"exec error: fixture {project_name} not found") + sys.exit(1) + + +if __name__ == "__main__": + if len(sys.argv) > 2: + print("Usage: python copyFixtures.py []") + sys.exit(1) + + if len(sys.argv) == 2: + project_name = os.path.splitext(os.path.basename(sys.argv[1]))[0] + if os.path.isfile(os.path.join(BASE_PATH, project_name, "Cargo.toml")): + process_rust(project_name) + elif os.path.isfile(os.path.join(BASE_PATH, f"{project_name}.c")): + process_c(project_name) + else: + process_wat(project_name) + print("Fixture has been processed.") + else: + dirs = [ + d + for d in os.listdir(BASE_PATH) + if os.path.isfile(os.path.join(BASE_PATH, d, "Cargo.toml")) + ] + c_files = [f for f in os.listdir(BASE_PATH) if f.endswith(".c")] + wat_files = [f for f in os.listdir(BASE_PATH) if f.endswith(".wat")] + wat_path = os.path.join(BASE_PATH, "wat") + wat_fixture_files = [ + f + for f in (os.listdir(wat_path) if os.path.isdir(wat_path) else []) + if f.endswith((".wat", ".zip")) + ] + + for d in sorted(dirs): + process_rust(d) + for c in sorted(c_files): + process_c(c[:-2]) + for wat in sorted(wat_files): + process_wat_file(os.path.join(BASE_PATH, wat)) + for wat_fixture in sorted(wat_fixture_files): + path = os.path.join(wat_path, wat_fixture) + if wat_fixture.endswith(".zip"): + process_wat_zip(path) + else: + process_wat_file(path) + print("All fixtures have been processed.") diff --git a/src/test/app/wasm_fixtures/fixtures.cpp b/src/test/app/wasm_fixtures/fixtures.cpp new file mode 100644 index 0000000000..6ff902717e --- /dev/null +++ b/src/test/app/wasm_fixtures/fixtures.cpp @@ -0,0 +1,657 @@ +// TODO: consider moving these to separate files (and figure out the build) + +#include + +#include + +extern std::string const kLedgerSqnWasmHex = + "0061736d01000000010e0360027f7f017f6000006000017f02120103656e760a6c6467725f696e6465780000030302" + "01020503010002063f0a7f01418088040b7f004180080b7f004180080b7f004180080b7f00418088040b7f00418008" + "0b7f00418088040b7f00418080080b7f0041000b7f0041010b07b1010c066d656d6f72790200115f5f7761736d5f63" + "616c6c5f63746f727300010d657363726f775f66696e69736800020c5f5f64736f5f68616e646c6503010a5f5f6461" + "74615f656e6403020b5f5f737461636b5f6c6f7703030c5f5f737461636b5f6869676803040d5f5f676c6f62616c5f" + "6261736503050b5f5f686561705f6261736503060a5f5f686561705f656e6403070d5f5f6d656d6f72795f62617365" + "03080c5f5f7461626c655f6261736503090a3d0202000b3801037f230041106b220024002000410c6a410410002101" + "200028020c2102200041106a2400200141054100200241054f1b20014100481b0b007f0970726f647563657273010c" + "70726f6365737365642d62790105636c616e675f31392e312e352d776173692d73646b202868747470733a2f2f6769" + "746875622e636f6d2f6c6c766d2f6c6c766d2d70726f6a656374206162346235613264623538323935386166316565" + "33303861373930636664623432626432343732302900490f7461726765745f6665617475726573042b0f6d75746162" + "6c652d676c6f62616c732b087369676e2d6578742b0f7265666572656e63652d74797065732b0a6d756c746976616c" + "7565"; + +extern std::string const kAllHostFunctionsWasmHex = + "0061736d0100000001540c60027f7f017f60037f7f7f017f60047f7f7f7f017f60017f017f60067f7f7f7f7f7f017f" + "60037f7f7f0060057f7f7f7f7f017f60037f7f7e017f60087f7f7f7f7f7f7f7f017f60017f0060027f7f006000017f" + "02dc041a08686f73745f6c69620874785f6669656c64000108686f73745f6c69620974726163655f6e756d00070868" + "6f73745f6c6962057472616365000608686f73745f6c69620a6c6467725f696e646578000008686f73745f6c696210" + "706172656e745f6c6467725f74696d65000008686f73745f6c696210706172656e745f6c6467725f68617368000008" + "686f73745f6c69620874785f696e6e6572000208686f73745f6c69620a74785f6172725f6c656e000308686f73745f" + "6c69621074785f696e6e65725f6172725f6c656e000008686f73745f6c69620d686f6d655f6c655f6669656c640001" + "08686f73745f6c69620d686f6d655f6c655f696e6e6572000208686f73745f6c69620f686f6d655f6c655f6172725f" + "6c656e000308686f73745f6c696215686f6d655f6c655f696e6e65725f6172725f6c656e000008686f73745f6c6962" + "0863616368655f6c65000108686f73745f6c69620d63726564656e7469616c5f6964000808686f73745f6c69620965" + "7363726f775f6964000408686f73745f6c6962096f7261636c655f6964000408686f73745f6c69620b736861353132" + "5f68616c66000208686f73745f6c6962076e66745f757269000408686f73745f6c6962087365745f64617461000008" + "686f73745f6c6962086c655f6669656c64000208686f73745f6c6962086c655f696e6e6572000608686f73745f6c69" + "620a6c655f6172725f6c656e000008686f73745f6c6962106c655f696e6e65725f6172725f6c656e000108686f7374" + "5f6c69620e6163636f756e74726f6f745f6964000208686f73745f6c69620a74726163655f616363740002030c0b09" + "0a05050b05000101030005030100110619037f01418080c0000b7f0041af99c0000b7f0041b099c0000b073504066d" + "656d6f727902000d657363726f775f66696e697368001e0a5f5f646174615f656e6403010b5f5f686561705f626173" + "6503020a911e0b990101027f230041306b220124002000027f418180202001411c6a4114100022024114470440417f" + "20022002417f4e1b210241010c010b200020012f001c3b0001200041036a2001411e6a2d00003a0000200120012900" + "233703082001200141286a29000037000d200128001f21022000410d6a200129000d37000020002001290308370208" + "41000b3a000020002002360204200141306a24000b460020012d00004101460440418080c000410b20013402041001" + "000b20002001290001370000200041106a200141116a280000360000200041086a200141096a2900003700000b1900" + "200241094f0440000b20002002360204200020013602000b1900200241214f0440000b200020023602042000200136" + "02000ba91b01097f230041b0036b22002400418b80c000411b41014100410010021a41a680c0004119410141004100" + "10021a41e780c000412b41014100410010021a2000410036027002400240024002400240024002400240200041f000" + "6a220741041003220141004a0440419281c00041172000280270220141187420014180fe0371410874722001410876" + "4180fe037120014118767272ad10011a200041003602900120004190016a220341041004220141004c0d0141a981c0" + "004113200028029001220141187420014180fe03714108747220014108764180fe037120014118767272ad10011a20" + "0041c8016a22024200370300200041c0016a22054200370300200041b8016a22044200370300200042003703b00120" + "0041b0016a22064120100522014120470d0241bc81c000411320064120410110021a41cf81c0004120410141004100" + "10021a41dc82c000412e41014100410010021a200041a0016a410036020020004198016a4200370300200042003703" + "90014181802020034114100022014114470d03418a83c00041142003101f2000420037034841888018200041c8006a" + "22034108100022014108470d04419e83c0004117420810011a41b583c000412820034108410110021a200041003602" + "3041848008200041306a22034104100022014104470d0541dd83c000411520034104410110021a200041f4006a4100" + "36000020004100360071200041013a0070200242003703002005420037030020044200370300200042003703b00102" + "4020074108200641201006220141004e044041f283c00041142001ad10011a200041286a20062001101d418684c000" + "410d2000280228200028022c410110021a0c010b419384c00041292001ac10011a0b41bc84c00041154183803c1007" + "ac10011a41d184c00041134189803c1007ac10011a0240200041f0006a41081008220141004e044041e484c0004114" + "2001ad10011a0c010b41f884c000412d2001ac10011a0b41a585c000412341014100410010021a41de86c000413341" + "014100410010021a2000420037034841828018200041c8006a220141081009220341004c0d06200341084604404191" + "87c000412b420810011a41bc87c000412f20014108410110021a0c080b41eb87c000412f2003ad10011a200041206a" + "200041c8006a2003101c419a88c000411720002802202000280224410110021a0c070b41bf82c000411d2001ac1001" + "1a419b7f21020c070b419a82c00041252001ac10011a419a7f21020c060b41ef81c000412b2001ac10011a41997f21" + "020c050b41b486c000412a2001ac10011a41b77e21020c040b41f385c00041c1002001ac10011a41b67e21020c030b" + "41c885c000412b2001ac10011a41b57e21020c020b41b188c00041c5002003ac10011a0b200041a0016a4100360200" + "20004198016a4200370300200042003703900102404181802020004190016a220341141009220141004a044041f688" + "c000411e2003101f0c010b419489c00041332001ac10011a0b200041f4006a41003600002000410036007120004101" + "3a0070200041c8016a4200370300200041c0016a4200370300200041b8016a4200370300200042003703b001024020" + "0041f0006a4108200041b0016a22014120100a220341004e044041c789c000411c2003ad10011a200041186a200120" + "03101d41e389c00041152000280218200028021c410110021a0c010b41f889c00041392003ac10011a0b41b18ac000" + "41244183803c100bac10011a0240200041f0006a4108100c220141004e044041d58ac000411c2001ad10011a0c010b" + "41f18ac000413d2001ac10011a0b41ae8bc000412841014100410010021a41d68bc000412f41014100410010021a20" + "0041b0016a2203101a200041f0006a22012003101b200041a8016a4200370300200041a0016a420037030020004198" + "016a4200370300200042003703900102400240024002400240200120004190016a2203102022014120460440200341" + "204100100d220441004a044041858cc00041232004ad10011a200042003703302004200041306a2201410810212203" + "41004c0d022003410846044041a88cc000412a420810011a41d28cc000412e20014108410110021a0c060b41808dc0" + "00412e2003ad10011a200041106a200041306a2003101c41ae8dc000411620002802102000280214410110021a0c05" + "0b41e68fc000413c2004ac10011a200041c8016a4200370300200041c0016a4200370300200041b8016a4200370300" + "200042003703b0014101200041b0016a4120102122014100480d020c030b41ba92c000412e2001ac10011a41ef7c21" + "020c050b41c48dc000412b2003ac10011a0c020b41a290c00041c1002001ac10011a0b200041cc006a410036000020" + "004100360049200041013a00484101200041c8006a200041b0016a10222201410048044041e390c00041352001ac10" + "011a0b4101102322014100480440419891c00041322001ac10011a0b4101200041c8006a10242201410048044041ca" + "91c00041392001ac10011a0b418392c000413741014100410010021a0c010b200041cc006a41003600002000410036" + "0049200041013a0048200041c8016a4200370300200041c0016a4200370300200041b8016a42003703002000420037" + "03b00102402004200041c8006a200041b0016a22011022220341004e044041ef8dc000411b2003ad10011a20004108" + "6a20012003101d418a8ec00041142000280208200028020c410110021a0c010b419e8ec00041312003ac10011a0b41" + "cf8ec000412320041023ac10011a02402004200041c8006a1024220141004e044041f28ec000411b2001ad10011a0c" + "010b418d8fc00041352001ac10011a0b41c28fc000412441014100410010021a0b41e892c000412f41014100410010" + "021a200041b0016a2201101a200041306a22042001101b200041e0006a4200370300200041d8006a42003703002000" + "41d0006a420037030020004200370348024002400240024002402004200041c8006a22031020220141204604404197" + "93c000410f20034120410110021a20004188016a420037030020004180016a4200370300200041f8006a4200370300" + "200042003703700240200441142004411441a693c0004109200041f0006a22014120100e220341004a044020002001" + "2003101d41ae93c000411220002802002000280204410110021a0c010b41c093c000413c2003ac10011a0b200041a8" + "016a22064200370300200041a0016a2202420037030020004198016a22054200370300200042003703900120004180" + "808cc07e360268200041306a22034114200041e8006a410420004190016a22084120100f22014120470d0141fc93c0" + "00410e20084120410110021a200041c8016a4200370300200041c0016a4200370300200041b8016a42003703002000" + "42003703b001200041808080d00236026c20034114200041ec006a4104200041b0016a22044120101022014120470d" + "02418a94c000410e20044120410110021a419894c000412441014100410010021a419195c000412541014100410010" + "021a20004188016a420037030020004180016a4200370300200041f8006a42003703002000420037037041b695c000" + "4117200041f0006a22034120101122014120470d0341cd95c000410b41b695c0004117410110021a41d895c0004111" + "20034120410110021a2004101a200041c8006a22072004101b20064200370300200242003703002005420037030020" + "0042003703900102404100200422026b410371220320026a220520024d0d0020030440200321010340200241003a00" + "00200241016a2102200141016b22010d000b0b200341016b4107490d000340200241003a0000200241076a41003a00" + "00200241066a41003a0000200241056a41003a0000200241046a41003a0000200241036a41003a0000200241026a41" + "003a0000200241016a41003a0000200241086a22022005470d000b0b200541800220036b2201417c716a220220054b" + "0440034020054100360200200541046a22052002490d000b0b024020022001410371220120026a22034f0d00200122" + "0504400340200241003a0000200241016a2102200541016b22050d000b0b200141016b4107490d000340200241003a" + "0000200241076a41003a0000200241066a41003a0000200241056a41003a0000200241046a41003a0000200241036a" + "41003a0000200241026a41003a0000200241016a41003a0000200241086a22022003470d000b0b0240200741142008" + "412020044180021012220141004a044041e995c00041102001ad10011a20014181024f0d0641f995c0004109200420" + "01410110021a0c010b418296c000412e2001ac10011a0b41b096c000411241c296c00041074101100222014100480d" + "0541c996c000411d2001ad10011a41e696c0004111422a1001410048044041ad97c000411a42a47b10011a41a47b21" + "020c070b41f796c000411c420010011a41012102419397c000411a41014100410010021a41ff97c000412941014100" + "410010021a41a898c000412810132201412846044041d098c000412741a898c0004128410110021a41f798c000411e" + "41014100410010021a41bf80c000412841014100410010021a0c070b419599c000411a2001ac10011a41c37a21020c" + "060b41f494c000411d2001ac10011a418b7c21020c050b41d894c000411c2001ac10011a41897c21020c040b41bc94" + "c000411c2001ac10011a41887c21020c030b41dd97c00041222001ac10011a41a77b21020c020b000b41c797c00041" + "162001ac10011a41a57b21020b200041b0036a240020020b0d00200020012002411410191a0b0c0020004114200141" + "2010180b0e002000418280182001200210140b0e002000200141082002412010150b0a0020004183803c10160b0a00" + "20002001410810170b0bb9190100418080c0000baf196572726f725f636f64653d3d3d3d20484f53542046554e4354" + "494f4e532054455354203d3d3d54657374696e6720323620686f73742066756e6374696f6e73535543434553533a20" + "416c6c20686f73742066756e6374696f6e20746573747320706173736564212d2d2d2043617465676f727920313a20" + "4c6564676572204865616465722046756e6374696f6e73202d2d2d4c65646765722073657175656e6365206e756d62" + "65723a506172656e74206c65646765722074696d653a506172656e74206c656467657220686173683a535543434553" + "533a204c6564676572206865616465722066756e6374696f6e734552524f523a206765745f706172656e745f6c6564" + "6765725f686173682077726f6e67206c656e6774683a4552524f523a206765745f706172656e745f6c65646765725f" + "74696d65206661696c65643a4552524f523a206765745f6c65646765725f73716e206661696c65643a2d2d2d204361" + "7465676f727920323a205472616e73616374696f6e20446174612046756e6374696f6e73202d2d2d5472616e736163" + "74696f6e204163636f756e743a5472616e73616374696f6e20466565206c656e6774683a5472616e73616374696f6e" + "20466565202873657269616c697a65642058525020616d6f756e74293a5472616e73616374696f6e2053657175656e" + "63653a4e6573746564206669656c64206c656e6774683a4e6573746564206669656c643a494e464f3a206765745f74" + "785f6e65737465645f6669656c64206e6f74206170706c696361626c653a5369676e657273206172726179206c656e" + "6774683a4d656d6f73206172726179206c656e6774683a4e6573746564206172726179206c656e6774683a494e464f" + "3a206765745f74785f6e65737465645f61727261795f6c656e206e6f74206170706c696361626c653a535543434553" + "533a205472616e73616374696f6e20646174612066756e6374696f6e734552524f523a206765745f74785f6669656c" + "642853657175656e6365292077726f6e67206c656e6774683a4552524f523a206765745f74785f6669656c64284665" + "65292077726f6e67206c656e67746820286578706563746564203820627974657320666f7220585250293a4552524f" + "523a206765745f74785f6669656c64284163636f756e74292077726f6e67206c656e6774683a2d2d2d204361746567" + "6f727920333a2043757272656e74204c6564676572204f626a6563742046756e6374696f6e73202d2d2d4375727265" + "6e74206f626a6563742062616c616e6365206c656e677468202858525020616d6f756e74293a43757272656e74206f" + "626a6563742062616c616e6365202873657269616c697a65642058525020616d6f756e74293a43757272656e74206f" + "626a6563742062616c616e6365206c656e67746820286e6f6e2d58525020616d6f756e74293a43757272656e74206f" + "626a6563742062616c616e63653a494e464f3a206765745f63757272656e745f6c65646765725f6f626a5f6669656c" + "642842616c616e636529206661696c656420286d6179206265206578706563746564293a43757272656e74206c6564" + "676572206f626a656374206163636f756e743a494e464f3a206765745f63757272656e745f6c65646765725f6f626a" + "5f6669656c64284163636f756e7429206661696c65643a43757272656e74206e6573746564206669656c64206c656e" + "6774683a43757272656e74206e6573746564206669656c643a494e464f3a206765745f63757272656e745f6c656467" + "65725f6f626a5f6e65737465645f6669656c64206e6f74206170706c696361626c653a43757272656e74206f626a65" + "6374205369676e657273206172726179206c656e6774683a43757272656e74206e6573746564206172726179206c65" + "6e6774683a494e464f3a206765745f63757272656e745f6c65646765725f6f626a5f6e65737465645f61727261795f" + "6c656e206e6f74206170706c696361626c653a535543434553533a2043757272656e74206c6564676572206f626a65" + "63742066756e6374696f6e732d2d2d2043617465676f727920343a20416e79204c6564676572204f626a6563742046" + "756e6374696f6e73202d2d2d5375636365737366756c6c7920636163686564206f626a65637420696e20736c6f743a" + "436163686564206f626a6563742062616c616e6365206c656e677468202858525020616d6f756e74293a4361636865" + "64206f626a6563742062616c616e6365202873657269616c697a65642058525020616d6f756e74293a436163686564" + "206f626a6563742062616c616e6365206c656e67746820286e6f6e2d58525020616d6f756e74293a43616368656420" + "6f626a6563742062616c616e63653a494e464f3a206765745f6c65646765725f6f626a5f6669656c642842616c616e" + "636529206661696c65643a436163686564206e6573746564206669656c64206c656e6774683a436163686564206e65" + "73746564206669656c643a494e464f3a206765745f6c65646765725f6f626a5f6e65737465645f6669656c64206e6f" + "74206170706c696361626c653a436163686564206f626a656374205369676e657273206172726179206c656e677468" + "3a436163686564206e6573746564206172726179206c656e6774683a494e464f3a206765745f6c65646765725f6f62" + "6a5f6e65737465645f61727261795f6c656e206e6f74206170706c696361626c653a535543434553533a20416e7920" + "6c6564676572206f626a6563742066756e6374696f6e73494e464f3a2063616368655f6c65646765725f6f626a2066" + "61696c65642028657870656374656420776974682074657374206669787475726573293a494e464f3a206765745f6c" + "65646765725f6f626a5f6669656c64206661696c656420617320657870656374656420286e6f20636163686564206f" + "626a656374293a494e464f3a206765745f6c65646765725f6f626a5f6e65737465645f6669656c64206661696c6564" + "2061732065787065637465643a494e464f3a206765745f6c65646765725f6f626a5f61727261795f6c656e20666169" + "6c65642061732065787065637465643a494e464f3a206765745f6c65646765725f6f626a5f6e65737465645f617272" + "61795f6c656e206661696c65642061732065787065637465643a535543434553533a20416e79206c6564676572206f" + "626a6563742066756e6374696f6e732028696e7465726661636520746573746564294552524f523a206163636f756e" + "74726f6f745f6964206661696c656420666f722063616368696e6720746573743a2d2d2d2043617465676f72792035" + "3a204b65796c65742047656e65726174696f6e2046756e6374696f6e73202d2d2d4163636f756e74206b65796c6574" + "3a546573745479706543726564656e7469616c206b65796c65743a494e464f3a2063726564656e7469616c5f6b6579" + "6c6574206661696c656420286578706563746564202d20696e74657266616365206973737565293a457363726f7720" + "6b65796c65743a4f7261636c65206b65796c65743a535543434553533a204b65796c65742067656e65726174696f6e" + "2066756e6374696f6e734552524f523a206f7261636c655f6b65796c6574206661696c65643a4552524f523a206573" + "63726f775f6b65796c6574206661696c65643a4552524f523a206163636f756e74726f6f745f6964206661696c6564" + "3a2d2d2d2043617465676f727920363a205574696c6974792046756e6374696f6e73202d2d2d48656c6c6f2c205852" + "504c205741534d20776f726c6421496e70757420646174613a5348413531322068616c6620686173683a4e46542064" + "617461206c656e6774683a4e465420646174613a494e464f3a206765745f6e6674206661696c656420286578706563" + "746564202d206e6f2073756368204e4654293a54657374207472616365206d6573736167657061796c6f6164547261" + "63652066756e6374696f6e206279746573207772697474656e3a54657374206e756d62657220747261636554726163" + "655f6e756d2066756e6374696f6e20737563636565646564535543434553533a205574696c6974792066756e637469" + "6f6e734552524f523a2074726163655f6e756d2829206661696c65643a4552524f523a207472616365282920666169" + "6c65643a4552524f523a20636f6d707574655f7368613531325f68616c66206661696c65643a2d2d2d204361746567" + "6f727920373a2044617461205570646174652046756e6374696f6e73202d2d2d55706461746564206c656467657220" + "656e74727920646174612066726f6d205741534d20746573745375636365737366756c6c792075706461746564206c" + "656467657220656e74727920776974683a535543434553533a2044617461207570646174652066756e6374696f6e73" + "4552524f523a207570646174655f64617461206661696c65643a004d0970726f64756365727302086c616e67756167" + "65010452757374000c70726f6365737365642d6279010572757374631d312e38372e30202831373036376539616320" + "323032352d30352d303929002c0f7461726765745f6665617475726573022b0f6d757461626c652d676c6f62616c73" + "2b087369676e2d657874"; + +extern std::string const kAllKeyletsWasmHex = + "0061736d0100000001500a60067f7f7f7f7f7f017f60047f7f7f7f017f60087f7f7f7f7f7f7f7f017f60047f7f7f7f" + "0060037f7f7f017f60037f7f7e017f60057f7f7f7f7f017f6000017f60037f7f7f0060067f7f7f7f7f7e00029f0418" + "08686f73745f6c69620974726163655f6e756d000508686f73745f6c6962057472616365000608686f73745f6c6962" + "0863616368655f6c65000408686f73745f6c6962086c655f6669656c64000108686f73745f6c69620d686f6d655f6c" + "655f6669656c64000408686f73745f6c69620a74726163655f61636374000108686f73745f6c69620e6163636f756e" + "74726f6f745f6964000108686f73745f6c69620c74727573746c696e655f6964000208686f73745f6c696206616d6d" + "5f6964000008686f73745f6c696208636865636b5f6964000008686f73745f6c69620d63726564656e7469616c5f69" + "64000208686f73745f6c69620b64656c65676174655f6964000008686f73745f6c6962126465706f7369745f707265" + "617574685f6964000008686f73745f6c6962066469645f6964000108686f73745f6c696209657363726f775f696400" + "0008686f73745f6c69620f6d70745f69737375616e63655f6964000008686f73745f6c69620a6d70746f6b656e5f69" + "64000008686f73745f6c69620c6e66745f6f666665725f6964000008686f73745f6c6962086f666665725f69640000" + "08686f73745f6c69620a7061796368616e5f6964000208686f73745f6c6962167065726d697373696f6e65645f646f" + "6d61696e5f6964000008686f73745f6c69620a7369676e6572735f6964000108686f73745f6c6962097469636b6574" + "5f6964000008686f73745f6c6962087661756c745f6964000003070603030307080905030100110619037f01418080" + "c0000b7f0041c28ac0000b7f0041d08ac0000b073504066d656d6f727902000d657363726f775f66696e697368001b" + "0a5f5f646174615f656e6403010b5f5f686561705f6261736503020ae8370614002000200120022003418280204282" + "8020101d0b140020002001200220034181802042818020101d0bd10302017f017e230041a0016b2204240002402001" + "2d0000410146044041d780c000411620012802042201ac10001a200041013a0000200020013602040c010b20044118" + "6a200141196a290000370300200441106a200141116a290000370300200441086a200141096a290000370300200420" + "012900013703002002200320044120410110011a2004412041001002220141004c044041d080c00041072001ac1000" + "1a200041013a0000200020013602040c010b418b80c000410f4285801410001a20014185801420044180016a412010" + "032201412047044041af80c0004115417f20012001417f4e1b2201ac10001a200041013a0000200020013602040c01" + "0b200441c2006a20044182016a2d00003a0000200441f0006a20044197016a2900002205370300200441286a220120" + "04418f016a290000370300200441306a22022005370300200441386a22032004419f016a2d00003a0000200420042f" + "0080013b014020042004290087013703202004200428008301360043200441df006a20032d00003a0000200441d700" + "6a2002290300370000200441cf006a20012903003700002004200429032037004741c480c000410c200441406b4120" + "410110011a20004180023b01000b200441a0016a24000bd32c02097f027e23004180076b2200240041ed80c0004123" + "41014100410010011a02402000027f02404181802020004190016a220741141004220641144604402000410e6a2000" + "4192016a22032d00003a000020002000290097013703e80120002000419c016a22012900003700ed01200020002f00" + "90013b010c200020002903e8013703d806200020002900ed013700dd06200020002800930136000f200041186a2000" + "2900dd06370000200020002903d806370013419081c00041082000410c6a2204411410051a41838020200741141004" + "22064114470d03200041226a20032d00003a000020002000290097013703e801200020012900003700ed0120002000" + "2f0090013b0120200020002903e8013703d806200020002900ed013700dd0620002000280093013600232000412c6a" + "20002900dd06370000200020002903d806370027419881c000410c200041206a411410051a200041a8016a22034200" + "370300200041a0016a2201420037030020004198016a42003703002000420037039001200441142007412010062204" + "4120460d01024020044100480440200020043602380c010b2000417f3602380b41010c020b0c020b200041cd006a20" + "03290300370000200041c5006a20012903003700002000413d6a20004198016a290300370000200020002903900137" + "003541000b3a003420004190016a200041346a41a481c00041071019024020002d0090014101460440200028029401" + "2106419c8ac0004112420510001a0c010b4100210641ab81c000413541014100410010011a200041e6006a41c4003a" + "0000200041e0006a4100360200200041eb006a41003a0000200041d5a6013b01642000420037035820004100360067" + "200041a8016a22044200370300200041a0016a2203420037030020004198016a220142003703002000420037039001" + "02402000410c6a4114200041206a4114200041d8006a411420004190016a4120100722074120470440024020074100" + "480440200020073602700c010b2000417f3602700b410121060c010b20004185016a2004290300370000200041fd00" + "6a2003290300370000200041f5006a2001290300370000200020002903900137006d0b200020063a006c2000419001" + "6a200041ec006a41e081c0004109101a20002d00900141014604402000280294012106419c8ac0004112420510001a" + "0c010b4100210641e981c000413741014100410010011a200041f8016a200041306a2204280100360200200041f001" + "6a200041286a220329010037030020004184026a200041e0006a290300220a3702002000418c026a200041e8006a28" + "02002201360200200020002901203703e8012000200029035822093702fc01200041e8066a22052001360200200041" + "e0066a2207200a370300200020093703d806200041f4066a2003290100370200200041fc066a200428010036020020" + "0020002901203702ec0620004190026a200041d8066a22034128101c20004194016a200041e8016a41d000101c2000" + "410136029001200041f0066a220142003703002005420037030020074200370300200042003703d806024041ae8ac0" + "004114200041bc016a412820034120100822034120470440024020034100480440200020033602ec010c010b200041" + "7f3602ec010b410121060c010b20004181026a2001290300370000200041f9016a2005290300370000200041f1016a" + "2007290300370000200020002903d8063700e9010b200020063a00e801200041bc026a200041e8016a41a082c00041" + "03101920002d00bc02410146044020002802c0022106419c8ac0004112420610001a0c010b4100210641a382c00041" + "3141014100410010011a200041063602d80620004180026a22044200370300200041f8016a22034200370300200041" + "f0016a22014200370300200042003703e80102402000410c6a4114200041d8066a4104200041e8016a412010092207" + "4120470440024020074100480440200020073602c8020c010b2000417f3602c8020b410121060c010b200041dd026a" + "2004290300370000200041d5026a2003290300370000200041cd026a2001290300370000200020002903e8013700c5" + "020b200020063a00c402200041e8016a200041c4026a41d482c0004105101920002d00e801410146044020002802ec" + "012106419c8ac0004112420610001a0c010b41d982c000413341014100410010011a20004180026a42003703002000" + "41f8016a4200370300200041f0016a4200370300200042003703e801024002402000410c6a2201411420014114418c" + "83c0004112200041e8016a4120100a2201412047044041d780c0004116417f20012001417f4e1b2206ac10001a0c01" + "0b200041da066a20002d00ea013a0000200041f0026a200041f7016a290000220a370300200041f8026a200041ff01" + "6a290000220937030020004180036a20004187026a2d000022013a0000200041e7066a200a370000200041ef066a20" + "09370000200041f7066a20013a0000200020002f01e8013b01d806200020002900ef0122093703e802200020002800" + "eb013600db06200020093700df06419e83c000410a200041d8066a22014120410110011a2001412041001002220641" + "004c044041d080c00041072006ac10001a0c010b418b80c000410f4298802010001a200641988020200041e8016a41" + "14100322014114460d0141af80c0004115417f20012001417f4e1b2206ac10001a0b419c8ac0004112420710001a0c" + "010b419a80c000411541014100410010011a41a883c000413841014100410010011a230041206b2208240020084118" + "6a22074200370300200841106a22044200370300200841086a220342003703002008420037030020004184036a2201" + "027f2000410c6a22064114200041206a2202411420084120100b220541204704400240200541004804402001200536" + "02040c010b2001417f3602040b41010c010b20012008290300370001200141196a2007290300370000200141116a20" + "04290300370000200141096a200329030037000041000b3a0000200841206a2400200041e8016a2205200141e083c0" + "004108101920002d00e80145044041e883c000413641014100410010011a230041206b22082400200841186a220742" + "00370300200841106a22044200370300200841086a2203420037030020084200370300200041a8036a2201027f2006" + "41142002411420084120100c22024120470440024020024100480440200120023602040c010b2001417f3602040b41" + "010c010b20012008290300370001200141196a2007290300370000200141116a2004290300370000200141096a2003" + "29030037000041000b3a0000200841206a240020052001419e84c000410e101920002d00e801410146044020002802" + "ec012106419c8ac0004112420910001a0c020b41ac84c000413c41014100410010011a230041206b22022400200241" + "186a22074200370300200241106a22044200370300200241086a2203420037030020024200370300200041cc036a22" + "01027f2000410c6a411420024120100d22054120470440024020054100480440200120053602040c010b2001417f36" + "02040b41010c010b20012002290300370001200141196a2007290300370000200141116a2004290300370000200141" + "096a200329030037000041000b3a0000200241206a2400200041e8016a200141e884c0004103101920002d00e80141" + "0146044020002802ec012106419c8ac0004112420a10001a0c020b41eb84c000413141014100410010011a23004130" + "6b220224002002410b36020c200241286a22074200370300200241206a22044200370300200241186a220342003703" + "0020024200370310200041f0036a2201027f2000410c6a41142002410c6a4104200241106a4120100e220541204704" + "40024020054100480440200120053602040c010b2001417f3602040b41010c010b2001200229031037000120014119" + "6a2007290300370000200141116a2004290300370000200141096a200329030037000041000b3a0000200241306a24" + "00200041e8016a2001419c85c0004106101920002d00e801410146044020002802ec012106419c8ac0004112420b10" + "001a0c020b41a285c000413441014100410010011a230041306b220224002002410c36020c200241286a2207420037" + "0300200241206a22044200370300200241186a220342003703002002420037031020004194046a2201027f2000410c" + "6a41142002410c6a4104200241106a4120100f22054120470440024020054100480440200120053602040c010b2001" + "417f3602040b41010c010b20012002290310370001200141196a2007290300370000200141116a2004290300370000" + "200141096a200329030037000041000b3a0000200241306a2400200041fc016a2000411c6a280100360200200041f4" + "016a200041146a2901003702002000200029010c3702ec01200041808080e0003602e801200041d8066a2103230041" + "406a22042400024020012d0000410146044041d780c000411620012802042201ac10001a200341013a000020032001" + "3602040c010b200441206a200141196a290000370300200441186a200141116a290000370300200441106a20014109" + "6a2900003703002004200129000137030841d685c000410b200441086a22014120410110011a024002402001412041" + "001002220141004c044041d080c00041072001ac10001a0c010b418b80c000410f4284802010001a20014184802020" + "04412c6a4114100322014114460d0141af80c0004115417f20012001417f4e1b2201ac10001a0b200341013a000020" + "0320013602040c010b419a80c000411541014100410010011a20034180023b01000b200441406b240020002d00d806" + "410146044020002802dc062106419c8ac0004112420c10001a0c020b41e185c000413941014100410010011a230041" + "206b22022400200241186a22074200370300200241106a22044200370300200241086a220342003703002002420037" + "0300200041b8046a2201027f200041e8016a4118200041206a41142002412010102205412047044002402005410048" + "0440200120053602040c010b2001417f3602040b41010c010b20012002290300370001200141196a20072903003700" + "00200141116a2004290300370000200141096a200329030037000041000b3a0000200241206a2400200041d8066a20" + "01419a86c0004107101920002d00d806410146044020002802dc062106419c8ac0004112420d10001a0c020b41a186" + "c000413541014100410010011a230041306b220224002002410636020c200241286a22074200370300200241206a22" + "044200370300200241186a2203420037030020024200370310200041dc046a2201027f200041206a41142002410c6a" + "4104200241106a4120101122054120470440024020054100480440200120053602040c010b2001417f3602040b4101" + "0c010b20012002290310370001200141196a2007290300370000200141116a2004290300370000200141096a200329" + "030037000041000b3a0000200241306a2400200041d8066a200141d686c000410c101820002d00d806410146044020" + "002802dc062106419c8ac0004112420d10001a0c020b41e286c000413a41014100410010011a230041306b22022400" + "2002410d36020c200241286a22074200370300200241206a22044200370300200241186a2203420037030020024200" + "37031020004180056a2201027f2000410c6a41142002410c6a4104200241106a412010122205412047044002402005" + "4100480440200120053602040c010b2001417f3602040b41010c010b20012002290310370001200141196a20072903" + "00370000200141116a2004290300370000200141096a200329030037000041000b3a0000200241306a2400200041d8" + "066a2001419c87c0004105101920002d00d806410146044020002802dc062106419c8ac0004112420d10001a0c020b" + "41a187c000413341014100410010011a230041306b220224002002410e36020c200241286a22074200370300200241" + "206a22044200370300200241186a2203420037030020024200370310200041a4056a2201027f2000410c6a41142000" + "41206a41142002410c6a4104200241106a4120101322054120470440024020054100480440200120053602040c010b" + "2001417f3602040b41010c010b20012002290310370001200141196a2007290300370000200141116a200429030037" + "0000200141096a200329030037000041000b3a0000200241306a2400200041d8066a200141d487c000410a10192000" + "2d00d806410146044020002802dc062106419c8ac0004112420e10001a0c020b41de87c00041384101410041001001" + "1a230041306b220224002002410f36020c200241286a22074200370300200241206a22044200370300200241186a22" + "03420037030020024200370310200041c8056a2201027f2000410c6a41142002410c6a4104200241106a4120101422" + "054120470440024020054100480440200120053602040c010b2001417f3602040b41010c010b200120022903103700" + "01200141196a2007290300370000200141116a2004290300370000200141096a200329030037000041000b3a000020" + "0241306a2400200041d8066a2001419688c0004112101820002d00d806410146044020002802dc062106419c8ac000" + "4112420f10001a0c020b41a888c00041c00041014100410010011a230041206b22022400200241186a220742003703" + "00200241106a22044200370300200241086a2203420037030020024200370300200041ec056a2201027f2000410c6a" + "411420024120101522054120470440024020054100480440200120053602040c010b2001417f3602040b41010c010b" + "20012002290300370001200141196a2007290300370000200141116a2004290300370000200141096a200329030037" + "000041000b3a0000200241206a2400200041d8066a200141e888c000410a101a20002d00d806410146044020002802" + "dc062106419c8ac0004112421010001a0c020b41f288c000413841014100410010011a230041306b22022400200241" + "1236020c200241286a22074200370300200241206a22044200370300200241186a2203420037030020024200370310" + "20004190066a2201027f2000410c6a41142002410c6a4104200241106a412010162205412047044002402005410048" + "0440200120053602040c010b2001417f3602040b41010c010b20012002290310370001200141196a20072903003700" + "00200141116a2004290300370000200141096a200329030037000041000b3a0000200241306a2400200041d8066a20" + "0141aa89c0004106101920002d00d806410146044020002802dc062106419c8ac0004112421210001a0c020b410121" + "0641b089c000413441014100410010011a230041306b220224002002411336020c200241286a220742003703002002" + "41206a22044200370300200241186a2203420037030020024200370310200041b4066a2201027f2000410c6a411420" + "02410c6a4104200241106a4120101722054120470440024020054100480440200120053602040c010b2001417f3602" + "040b41010c010b20012002290310370001200141196a2007290300370000200141116a200429030037000020014109" + "6a200329030037000041000b3a0000200241306a2400200041d8066a200141e489c0004105101920002d00d8064101" + "46044020002802dc062106419c8ac0004112421310001a0c020b41e989c000413341014100410010011a0c010b2000" + "2802ec012106419c8ac0004112420810001a0b20004180076a240020060f0b418080c000410b417f20062006417f4e" + "1bac1000000bfd0401067f200241104f0440024020002000410020006b41037122056a22044f0d0020012103200504" + "40200521060340200020032d00003a0000200341016a2103200041016a2100200641016b22060d000b0b200541016b" + "4107490d000340200020032d00003a0000200041016a200341016a2d00003a0000200041026a200341026a2d00003a" + "0000200041036a200341036a2d00003a0000200041046a200341046a2d00003a0000200041056a200341056a2d0000" + "3a0000200041066a200341066a2d00003a0000200041076a200341076a2d00003a0000200341086a2103200041086a" + "22002004470d000b0b2004200220056b2207417c7122086a21000240200120056a2206410371450440200020044d0d" + "0120062101034020042001280200360200200141046a2101200441046a22042000490d000b0c010b200020044d0d00" + "2006410374220541187121032006417c71220241046a2101410020056b411871210520022802002102034020042002" + "2003762001280200220220057472360200200141046a2101200441046a22042000490d000b0b200741037121022006" + "20086a21010b02402000200020026a22064f0d002002410771220304400340200020012d00003a0000200141016a21" + "01200041016a2100200341016b22030d000b0b200241016b4107490d000340200020012d00003a0000200041016a20" + "0141016a2d00003a0000200041026a200141026a2d00003a0000200041036a200141036a2d00003a0000200041046a" + "200141046a2d00003a0000200041056a200141056a2d00003a0000200041066a200141066a2d00003a000020004107" + "6a200141076a2d00003a0000200141086a2101200041086a22002006470d000b0b0b940201017f230041406a220624" + "00024020012d0000410146044041d780c000411620012802042201ac10001a200041013a0000200020013602040c01" + "0b200641206a200141196a290000370300200641186a200141116a290000370300200641106a200141096a29000037" + "03002006200129000137030820022003200641086a22014120410110011a024002402001412041001002220141004c" + "044041d080c00041072001ac10001a0c010b418b80c000410f200510001a200120042006412c6a4114100322014114" + "460d0141af80c0004115417f20012001417f4e1b2201ac10001a0b200041013a0000200020013602040c010b419a80" + "c000411541014100410010011a20004180023b01000b200641406b24000b0bb80a0100418080c0000bae0a6572726f" + "725f636f64653d47657474696e67206669656c643a204669656c6420646174613a207265747269657665644572726f" + "722067657474696e67206669656c643a204669656c6420646174613a204572726f723a204572726f72206765747469" + "6e67206b65796c65743a202424242424205354415254494e47205741534d20455845435554494f4e20242424242441" + "63636f756e743a44657374696e6174696f6e3a4163636f756e744163636f756e74206f626a65637420657869737473" + "2c2070726f63656564696e67207769746820657363726f772066696e6973682e54727573746c696e6554727573746c" + "696e65206f626a656374206578697374732c2070726f63656564696e67207769746820657363726f772066696e6973" + "682e414d4d414d4d206f626a656374206578697374732c2070726f63656564696e67207769746820657363726f7720" + "66696e6973682e436865636b436865636b206f626a656374206578697374732c2070726f63656564696e6720776974" + "6820657363726f772066696e6973682e7465726d73616e64636f6e646974696f6e7343726564656e7469616c437265" + "64656e7469616c206f626a656374206578697374732c2070726f63656564696e67207769746820657363726f772066" + "696e6973682e44656c656761746544656c6567617465206f626a656374206578697374732c2070726f63656564696e" + "67207769746820657363726f772066696e6973682e4465706f736974507265617574684465706f7369745072656175" + "7468206f626a656374206578697374732c2070726f63656564696e67207769746820657363726f772066696e697368" + "2e444944444944206f626a656374206578697374732c2070726f63656564696e67207769746820657363726f772066" + "696e6973682e457363726f77457363726f77206f626a656374206578697374732c2070726f63656564696e67207769" + "746820657363726f772066696e6973682e4d505449737375616e63654d505449737375616e6365206f626a65637420" + "6578697374732c2070726f63656564696e67207769746820657363726f772066696e6973682e4d50546f6b656e4d50" + "546f6b656e206f626a656374206578697374732c2070726f63656564696e67207769746820657363726f772066696e" + "6973682e4e46546f6b656e4f666665724e46546f6b656e4f66666572206f626a656374206578697374732c2070726f" + "63656564696e67207769746820657363726f772066696e6973682e4f666665724f66666572206f626a656374206578" + "697374732c2070726f63656564696e67207769746820657363726f772066696e6973682e5061794368616e6e656c50" + "61794368616e6e656c206f626a656374206578697374732c2070726f63656564696e67207769746820657363726f77" + "2066696e6973682e5065726d697373696f6e6564446f6d61696e5065726d697373696f6e6564446f6d61696e206f62" + "6a656374206578697374732c2070726f63656564696e67207769746820657363726f772066696e6973682e5369676e" + "65724c6973745369676e65724c697374206f626a656374206578697374732c2070726f63656564696e672077697468" + "20657363726f772066696e6973682e5469636b65745469636b6574206f626a656374206578697374732c2070726f63" + "656564696e67207769746820657363726f772066696e6973682e5661756c745661756c74206f626a65637420657869" + "7374732c2070726f63656564696e67207769746820657363726f772066696e6973682e43757272656e742073657120" + "76616c75653a004d0970726f64756365727302086c616e6775616765010452757374000c70726f6365737365642d62" + "79010572757374631d312e38372e30202831373036376539616320323032352d30352d303929002c0f746172676574" + "5f6665617475726573022b0f6d757461626c652d676c6f62616c732b087369676e2d657874"; + +extern std::string const kCodecovTestsWasmHex = + "0061736d0100000001570b60067f7f7f7f7f7f017f60047f7f7f7f017f60027f7f017f60037f7f7f017f60077f7f7f" + "7f7f7f7f017f60057f7f7f7f7f017f60087f7f7f7f7f7f7f7f017f60017f017f60037f7f7e017f60047f7f7f7f0060" + "00017f02c60a3b08686f73745f6c6962057472616365000508686f73745f6c69620974726163655f6e756d00080868" + "6f73745f6c69620a6c6467725f696e646578000208686f73745f6c696210706172656e745f6c6467725f74696d6500" + "0208686f73745f6c696210706172656e745f6c6467725f68617368000208686f73745f6c696208626173655f666565" + "000208686f73745f6c696211616d656e646d656e745f656e61626c6564000208686f73745f6c69620874785f666965" + "6c64000308686f73745f6c69620e6163636f756e74726f6f745f6964000108686f73745f6c69620863616368655f6c" + "65000308686f73745f6c69620d686f6d655f6c655f6669656c64000308686f73745f6c6962086c655f6669656c6400" + "0108686f73745f6c69620874785f696e6e6572000108686f73745f6c69620d686f6d655f6c655f696e6e6572000108" + "686f73745f6c6962086c655f696e6e6572000508686f73745f6c69620a74785f6172725f6c656e000708686f73745f" + "6c69620f686f6d655f6c655f6172725f6c656e000708686f73745f6c69620a6c655f6172725f6c656e000208686f73" + "745f6c69621074785f696e6e65725f6172725f6c656e000208686f73745f6c696215686f6d655f6c655f696e6e6572" + "5f6172725f6c656e000208686f73745f6c6962106c655f696e6e65725f6172725f6c656e000308686f73745f6c6962" + "087365745f64617461000208686f73745f6c69620b7368613531325f68616c66000108686f73745f6c696209636865" + "636b5f736967000008686f73745f6c6962076e66745f757269000008686f73745f6c69620a6e66745f697373756572" + "000108686f73745f6c6962096e66745f7461786f6e000108686f73745f6c6962096e66745f666c616773000208686f" + "73745f6c69620c6e66745f786665725f666565000208686f73745f6c69620a6e66745f73657269616c000108686f73" + "745f6c69620a74726163655f61636374000108686f73745f6c69620974726163655f616d74000108686f73745f6c69" + "6208636865636b5f6964000008686f73745f6c69620f666c6f61745f66726f6d5f75696e74000508686f73745f6c69" + "620c74727573746c696e655f6964000608686f73745f6c696206616d6d5f6964000008686f73745f6c69620d637265" + "64656e7469616c5f6964000608686f73745f6c69620a6d70746f6b656e5f6964000008686f73745f6c69620c747261" + "63655f78666c6f6174000108686f73745f6c696209666c6f61745f636d70000108686f73745f6c696209666c6f6174" + "5f616464000408686f73745f6c696209666c6f61745f737562000408686f73745f6c69620a666c6f61745f6d756c74" + "000408686f73745f6c696209666c6f61745f646976000408686f73745f6c69620a666c6f61745f726f6f7400000868" + "6f73745f6c696209666c6f61745f706f77000008686f73745f6c696209657363726f775f6964000008686f73745f6c" + "69620f6d70745f69737375616e63655f6964000008686f73745f6c69620c6e66745f6f666665725f6964000008686f" + "73745f6c6962086f666665725f6964000008686f73745f6c6962096f7261636c655f6964000008686f73745f6c6962" + "0a7061796368616e5f6964000608686f73745f6c6962167065726d697373696f6e65645f646f6d61696e5f69640000" + "08686f73745f6c6962097469636b65745f6964000008686f73745f6c6962087661756c745f6964000008686f73745f" + "6c69620b64656c65676174655f6964000008686f73745f6c6962126465706f7369745f707265617574685f69640000" + "08686f73745f6c6962066469645f6964000108686f73745f6c69620a7369676e6572735f69640001030302090a0503" + "0100110619037f01418080c0000b7f0041cf9bc0000b7f0041d09bc0000b073504066d656d6f727902000d65736372" + "6f775f66696e697368003c0a5f5f646174615f656e6403010b5f5f686561705f6261736503020a8c2f024600024020" + "0020014704402002200341014100410010001a20004100480d01418b80c000410b2000ad1001000b200220032000ac" + "10011a0f0b418b80c000410b2000ac1001000bc22e020b7f017e23004190026b22002400419680c000412341014100" + "410010001a20004100360260200041e0006a220241041002410441888ac000410a103b200041003602602002410410" + "03410441928ac0004110103b200041f8006a22054200370300200041f0006a22014200370300200041e8006a220642" + "0037030020004200370360200241201004412041a28ac0004110103b20004100360260200241041005410441b28ac0" + "004108103b200041106a2208428182848890a0c08001370300200041186a2209428182848890a0c080013703002000" + "41206a220a428182848890a0c080013703002000428182848890a0c0800137030841b980c000410e1006410141c780" + "c0004111103b200041086a41201006410141c780c0004111103b418180202002411410072203411446044002402000" + "412e6a200041e2006a2d00003a0000200020002900673703e8012000200041ec006a2900003700ed01200020002f00" + "603b012c200020002903e8013703a801200020002900ed013700ad012000200028006336002f200041386a20002900" + "ad01370000200020002903a80137003320054200370300200142003703002006420037030020004200370360200041" + "2c6a2205411420024120100822034120470d00200041c2006a20002d00623a0000200041f0016a2207200041ef006a" + "290000220b370300200041cf006a200b370000200041d7006a200041f7006a290000370000200041df006a200041ff" + "006a2d00003a0000200020002f01603b01402000200028006336004320002000290067370047200041406b41204100" + "1009410141d880c0004108103b2001410036020020064200370300200042003703604181802020024114100a411441" + "ba8ac000410d103b20014100360200200642003703002000420037036041014181802020024114100b411441c78ac0" + "004108103b02404100200041e4006a22046b410371220320046a220120044d0d002003044020032106034020044100" + "3a0000200441016a2104200641016b22060d000b0b200341016b4107490d000340200441003a0000200441076a4100" + "3a0000200441066a41003a0000200441056a41003a0000200441046a41003a0000200441036a41003a000020044102" + "6a41003a0000200441016a41003a0000200441086a22042001470d000b0b2001413c20036b2203417c716a22042001" + "4b0440034020014100360200200141046a22012004490d000b0b024020042003410371220320046a22064f0d002003" + "220104400340200441003a0000200441016a2104200141016b22010d000b0b200341016b4107490d00034020044100" + "3a0000200441076a41003a0000200441066a41003a0000200441056a41003a0000200441046a41003a000020044103" + "6a41003a0000200441026a41003a0000200441016a41003a0000200441086a22042006470d000b0b200041043602a0" + "01200041818020360260200041f8016a2203410036020020074200370300200042003703e80120024104200041e801" + "6a22014114100c411441cf8ac0004108103b2003410036020020074200370300200042003703e801200220002802a0" + "0120014114100d411441d78ac000410d103b2003410036020020074200370300200042003703e80141012002200028" + "02a00120014114100e411441e48ac0004108103b4189803c100f412041e080c000410a103b4189803c1010412041ea" + "80c000410f103b41014189803c1011412041f980c000410a103b200220002802a00110124120418381c0004110103b" + "200220002802a00110134120419381c0004115103b4101200220002802a0011014412041a881c0004110103b200541" + "141015411441b881c0004108103b20004180026a220642003703002003420037030020074200370300200042003703" + "e801200220002802a001200141201016412041ec8ac000410b103b41c081c000410c41cc81c000410b41d781c00041" + "0e1017410141e581c0004109103b200041c0016a200a290300370300200041b8016a2009290300370300200041b001" + "6a2008290300370300200020002903083703a801200341003b010020074200370300200042003703e8012005411420" + "0041a8016a22044120200141121018411241f78ac0004107103b2003410036020020074200370300200042003703e8" + "0120044120200141141019411441fe8ac000410a103b200041003602e8012004412020014104101a410441888bc000" + "4109103b20044120101b410841ee81c0004109103b20044120101c410a41f781c000410c103b200041003602e80120" + "04412020014104101d410441918bc000410a103b418382c000410d20054114101e4100419082c000410a103b418382" + "c000410d419a82c0004108101f410041a282c0004109103b418382c000410d41ab82c0004108101f410041b382c000" + "410e103b417f41041004417141c182c0004118103b200041003602e8012001417f10044171419b8bc0004118103b20" + "0041ea016a41003a0000200041003b01e801200141031004417d41b38bc000411e103b200041003602e80120014180" + "94ebdc031004417341d18bc000411d103b4102100f416f41d982c0004119103b417f20002802a0011012417141f282" + "c0004118103b2002417f10124171418a83c0004118103b20024181081012417441a283c0004119103b200041e094eb" + "dc036a220820002802a0011012417341bb83c0004118103b2006420037030020034200370300200742003703002000" + "42003703e8012005411420084108200141201020417341ee8bc0004114103b20064200370300200342003703002007" + "4200370300200042003703e8012005411420054114200141201020417141828cc0004116103b200642003703002003" + "420037030020074200370300200042003703e801200841082001412041001021417341988cc0004117103b20064200" + "3703002003420037030020074200370300200042003703e801200220002802a0012001412041001021417141af8cc0" + "004120103b200820002802a00141011009417341d383c0004110103b200220002802a00141011009417141e383c000" + "4112103b200642003703002003420037030020074200370300200042003703e801200820002802a001200141201008" + "417341cf8cc0004116103b200642003703002003420037030020074200370300200042003703e801200220002802a0" + "01200141201008417141e58cc0004118103b200642003703002003420037030020074200370300200042003703e801" + "2005411420054114200820002802a001200141201022417341fd8cc000411d103b2006420037030020034200370300" + "20074200370300200042003703e8012005411420054114200220002802a0012001412010224171419a8dc000411f10" + "3b200642003703002003420037030020074200370300200042003703e80141bb9bc0004114200820002802a0012001" + "41201023417341b98dc0004115103b200642003703002003420037030020074200370300200042003703e80141bb9b" + "c0004114200220002802a001200141201023417141ce8dc000411b103b200642003703002003420037030020074200" + "370300200042003703e80141bb9bc000411441f583c0004114200141201023417141e98dc0004125103b2006420037" + "03002003420037030020074200370300200042003703e801418984c000412841bb9bc0004114200141201023417141" + "8e8ec0004121103b200041dc016a2000413c6a280100360200200041d4016a200041346a2901003702002000200029" + "012c3702cc01200041808080083602c801200041003b01e801200041c8016a2209411841bb9bc00041142001410210" + "23417141af8ec000410a103b200820002802a001422a1001417341b184c0004111103b200041003b01e80141022001" + "41021007416f41b98ec0004117103b200041003b01e801410220014102100a416f41d08ec000411c103b200041003b" + "01e8014101410220014102100b416f41ec8ec0004117103b4102100f416f41d982c0004119103b41021010416f41c2" + "84c000411e103b410141021011416f41e084c0004119103b41b980c0004181081006417441f984c000411f103b41b9" + "80c00041c10010064174419885c000411a103b200041003b01e801200241810820014102100c417441838fc0004116" + "103b200041003b01e801200241810820014102100d417441998fc000411b103b200041003b01e80141012002418108" + "20014102100e417441b48fc0004116103b20024181081012417441b285c000411e103b20024181081013417441d085" + "c0004123103b410120024181081014417441f385c000411e103b200241812010154174419186c0004116103b418382" + "c00041810841cc81c000410b41d781c000410e1017417441e581c0004109103b418382c000410d41cc81c000418108" + "41d781c000410e1017417441e581c0004109103b418382c000410d41cc81c000410b41d781c0004181081017417441" + "e581c0004109103b200041003b01e8012002418108200141021016417441ca8fc0004119103b200041003b01e80141" + "bb9bc00041810841bb9bc0004114200141021023417441e38fc0004114103b200041003b01e8012005411420054114" + "2002418108200141021024417441f78fc000411b103b200041003b01e8012009418108200541142001410210254174" + "419290c000411e103b418382c000410d200820002802a00141001000417341a786c000410f103b200042d487b6f4c7" + "d4b1c0003700e001418382c000410d200041e095ebdc036a220441081026417341b686c0004116103b418382c00041" + "0d200820002802a001101f417341cc86c0004113103b20044108200041e0016a220841081027417341df86c0004114" + "103b20084108200441081027417341f386c0004114103b200041003b01e80120044108200841082001410241001028" + "417341b090c0004114103b200041003b01e80120084108200441082001410241001028417341c490c0004114103b20" + "0041003b01e80120044108200841082001410241001029417341d890c0004114103b200041003b01e8012008410820" + "0441082001410241001029417341ec90c0004114103b200041003b01e8012004410820084108200141024100102a41" + "73418091c0004115103b200041003b01e8012008410820044108200141024100102a4173419591c0004115103b2000" + "41003b01e8012004410820084108200141024100102b417341aa91c0004114103b200041003b01e801200841082004" + "4108200141024100102b417341be91c0004114103b200041003b01e801200441084103200141024100102c417341d2" + "91c0004114103b200041003b01e801200441084103200141024100102d417341e691c0004113103b20064200370300" + "2003420037030020074200370300200042003703e801200541142005411420014120102e417141f991c000411b103b" + "200642003703002003420037030020074200370300200042003703e801200541142005411420014120102f41714194" + "92c0004121103b200642003703002003420037030020074200370300200042003703e8012005411420054114200141" + "201030417141b592c000411e103b200642003703002003420037030020074200370300200042003703e80120054114" + "20054114200141201031417141d392c000411a103b2006420037030020034200370300200742003703002000420037" + "03e8012005411420054114200141201032417141ed92c000411b103b20064200370300200342003703002007420037" + "0300200042003703e8012005411420054114200541142001412010334171418893c000411c103b2006420037030020" + "03420037030020074200370300200042003703e8012005411420054114200141201034417141a493c0004128103b20" + "0642003703002003420037030020074200370300200042003703e8012005411420054114200141201035417141cc93" + "c000411b103b200642003703002003420037030020074200370300200042003703e801200541142005411420014120" + "1036417141e793c000411a103b200220002802a001410010094171418787c000411b103b200041003b01e801200541" + "14200220002802a0012001410210184171418194c000411a103b200041003b01e801200220002802a0012001410210" + "194171419b94c000411d103b200041003b01e801200220002802a00120014102101a417141b894c000411c103b2002" + "20002802a001101b417141a287c000411c103b200220002802a001101c417141be87c000411f103b200041003602e8" + "01200220002802a00120014104101d417141d494c000411d103b200041003b01e801200220002802a0012001410210" + "08417141f194c0004124103b200041808080083602e801200041003b018e02200220002802a001200141042000418e" + "026a2203410210204171419595c000411e103b200041003b018e02200220002802a001220620054114200220062003" + "41021024417141b395c0004124103b200041003b018e0220054114200220002802a001220620022006200341021024" + "417141d795c0004124103b200041003b018e02200220002802a00120054114200341021037417141fb95c000412210" + "3b200041003b018e0220054114200220002802a0012003410210374171419d96c0004122103b200041003b018e0220" + "0220002802a00120054114200341021038417141bf96c0004129103b200041003b018e0220054114200220002802a0" + "01200341021038417141e896c0004129103b200041003b018e02200220002802a0012003410210394171419197c000" + "411c103b200041003b018e02200220002802a0012001410420034102102e417141ad97c000411f103b200041003b01" + "8e02200220002802a0012005411441f583c0004114200341021022417141cc97c0004123103b200041003b018e0220" + "054114200220002802a00141f583c0004114200341021022417141ef97c0004123103b200041003b018e0220022000" + "2802a0012001410420034102102f4171419298c0004125103b200041003b018e0220094118200220002802a0012003" + "41021025417141b798c0004120103b200041003b018e02200220002802a00120014104200341021030417141d798c0" + "004122103b200041003b018e02200220002802a00120014104200341021031417141f998c000411e103b200041003b" + "018e02200220002802a001200141042003410210324171419799c000411f103b200041003b018e02200220002802a0" + "012005411420014104200341021033417141b699c0004121103b200041003b018e0220054114200220002802a00120" + "014104200341021033417141d799c0004121103b200041003b018e02200220002802a0012001410420034102103441" + "7141f899c000412c103b200041003b018e02200220002802a00120034102103a417141a49ac0004120103b20004100" + "3b018e02200220002802a00120014104200341021035417141c49ac000411f103b200041003b018e02200220002802" + "a00120014104200341021036417141e39ac000411e103b200041003b018e02200220002802a00141dd87c000412020" + "0341021018417141819bc000411d103b418382c000410d200220002802a001101e417141fd87c0004120103b418396" + "abdd03410d41dd87c0004120410010004173419d88c0004110103b418396abdd03410d200841081026417341ad88c0" + "004117103b418396abdd03410d20054114101e417341c488c0004115103b418396abdd03410d41ab82c0004108101f" + "417341d988c0004114103b200220002802a001200241810841001000417441ed88c000410e103b2002418108420110" + "01417441fb88c0004112103b418382c0004181082008410810264174418d89c0004115103b418382c0004181082005" + "4114101e417441a289c0004113103b418382c00041810841ab82c0004108101f417441b589c0004112103b418382c0" + "00410d200220002802a001101f417141c789c0004116103b200041003b018e02200220002802a00120054114200341" + "0210254171419e9bc000411d103b418382c000410d200220002802a00141021000417141dd89c0004114103b410141" + "0020054114101e410041f189c0004117103b20004190026a240041010f0b0b418080c000410b417f20032003417f4e" + "1bac1001000b0ba61b0200418080c0000b89046572726f725f636f64653d54455354204641494c4544242424242420" + "5354415254494e47205741534d20455845435554494f4e202424242424746573745f616d656e646d656e74616d656e" + "646d656e745f656e61626c656463616368655f6c6574785f6172725f6c656e686f6d655f6c655f6172725f6c656e6c" + "655f6172725f6c656e74785f696e6e65725f6172725f6c656e686f6d655f6c655f696e6e65725f6172725f6c656e6c" + "655f696e6e65725f6172725f6c656e7365745f6461746174657374206d65737361676574657374207075626b657974" + "657374207369676e6174757265636865636b5f7369676e66745f666c6167736e66745f786665725f66656574657374" + "696e6720747261636574726163655f61636374400000000000005f74726163655f616d744000000000000000747261" + "63655f616d745f7a65726f706172656e745f6c6467725f686173685f6e65675f70747274785f6172725f6c656e5f69" + "6e76616c69645f736669656c6474785f696e6e65725f6172725f6c656e5f6e65675f70747274785f696e6e65725f61" + "72725f6c656e5f6e65675f6c656e74785f696e6e65725f6172725f6c656e5f746f6f5f6c6f6e6774785f696e6e6572" + "5f6172725f6c656e5f7074725f6f6f6263616368655f6c655f7074725f6f6f6263616368655f6c655f77726f6e675f" + "6c656e55534430303030303030303030303030303030300041b184c0000b8a1774726163655f6e756d5f6f6f625f73" + "7472686f6d655f6c655f6172725f6c656e5f696e76616c69645f736669656c646c655f6172725f6c656e5f696e7661" + "6c69645f736669656c64616d656e646d656e745f656e61626c65645f746f6f5f6269675f736c696365616d656e646d" + "656e745f656e61626c65645f746f6f5f6c6f6e6774785f696e6e65725f6172725f6c656e5f746f6f5f6269675f736c" + "696365686f6d655f6c655f696e6e65725f6172725f6c656e5f746f6f5f6269675f736c6963656c655f696e6e65725f" + "6172725f6c656e5f746f6f5f6269675f736c6963657365745f646174615f746f6f5f6269675f736c69636574726163" + "655f6f6f625f736c69636574726163655f78666c6f61745f6f6f625f736c69636574726163655f616d745f6f6f625f" + "736c696365666c6f61745f636d705f6f6f625f736c69636531666c6f61745f636d705f6f6f625f736c696365326361" + "6368655f6c655f77726f6e675f73697a655f75696e743235366e66745f666c6167735f77726f6e675f73697a655f75" + "696e743235366e66745f786665725f6665655f77726f6e675f73697a655f75696e7432353630303030303030303030" + "3030303030303030303030303030303030303030303174726163655f616363745f77726f6e675f73697a655f616363" + "6f756e745f696474726163655f6f6f625f737472696e6774726163655f78666c6f61745f6f6f625f737472696e6774" + "726163655f616363745f6f6f625f737472696e6774726163655f616d745f6f6f625f737472696e6774726163655f74" + "6f6f5f6c6f6e6774726163655f6e756d5f746f6f5f6c6f6e6774726163655f78666c6f61745f746f6f5f6c6f6e6774" + "726163655f616363745f746f6f5f6c6f6e6774726163655f616d745f746f6f5f6c6f6e6774726163655f616d745f77" + "726f6e675f6c656e67746874726163655f696e76616c69645f61735f68657874726163655f616363745f636865636b" + "5f646573796e636c6467725f696e646578706172656e745f6c6467725f74696d65706172656e745f6c6467725f6861" + "7368626173655f666565686f6d655f6c655f6669656c646c655f6669656c6474785f696e6e6572686f6d655f6c655f" + "696e6e65726c655f696e6e65727368613531325f68616c666e66745f7572696e66745f6973737565726e66745f7461" + "786f6e6e66745f73657269616c706172656e745f6c6467725f686173685f6e65675f6c656e706172656e745f6c6467" + "725f686173685f6275665f746f6f5f736d616c6c706172656e745f6c6467725f686173685f6c656e5f746f6f5f6c6f" + "6e67636865636b5f69645f6f6f625f6c656e5f753332636865636b5f69645f77726f6e675f6c656e5f753332666c6f" + "61745f66726f6d5f75696e745f6c656e5f6f6f62666c6f61745f66726f6d5f75696e745f77726f6e675f6c656e5f75" + "696e7436346163636f756e74726f6f745f69645f6c656e5f6f6f626163636f756e74726f6f745f69645f77726f6e67" + "5f6c656e74727573746c696e655f69645f6c656e5f6f6f625f63757272656e637974727573746c696e655f69645f77" + "726f6e675f6c656e5f63757272656e6379616d6d5f69645f6c656e5f6f6f625f617373657432616d6d5f69645f6c65" + "6e5f77726f6e675f6c656e5f617373657432616d6d5f69645f6c656e5f77726f6e675f6e6f6e5f7872705f63757272" + "656e63795f6c656e616d6d5f69645f6c656e5f77726f6e675f7872705f63757272656e63795f6c656e616d6d5f6964" + "5f6d707474785f6669656c645f696e76616c69645f736669656c64686f6d655f6c655f6669656c645f696e76616c69" + "645f736669656c646c655f6669656c645f696e76616c69645f736669656c6474785f696e6e65725f746f6f5f626967" + "5f736c696365686f6d655f6c655f696e6e65725f746f6f5f6269675f736c6963656c655f696e6e65725f746f6f5f62" + "69675f736c6963657368613531325f68616c665f746f6f5f6269675f736c696365616d6d5f69645f746f6f5f626967" + "5f736c69636563726564656e7469616c5f69645f746f6f5f6269675f736c6963656d70746f6b656e5f69645f746f6f" + "5f6269675f736c6963655f6d70746964666c6f61745f6164645f6f6f625f736c69636531666c6f61745f6164645f6f" + "6f625f736c69636532666c6f61745f7375625f6f6f625f736c69636531666c6f61745f7375625f6f6f625f736c6963" + "6532666c6f61745f6d756c745f6f6f625f736c69636531666c6f61745f6d756c745f6f6f625f736c69636532666c6f" + "61745f6469765f6f6f625f736c69636531666c6f61745f6469765f6f6f625f736c69636532666c6f61745f726f6f74" + "5f6f6f625f736c696365666c6f61745f706f775f6f6f625f736c696365657363726f775f69645f77726f6e675f7369" + "7a655f75696e7433326d70745f69737375616e63655f69645f77726f6e675f73697a655f75696e7433326e66745f6f" + "666665725f69645f77726f6e675f73697a655f75696e7433326f666665725f69645f77726f6e675f73697a655f7569" + "6e7433326f7261636c655f69645f77726f6e675f73697a655f75696e7433327061796368616e5f69645f77726f6e67" + "5f73697a655f75696e7433327065726d697373696f6e65645f646f6d61696e5f69645f77726f6e675f73697a655f75" + "696e7433327469636b65745f69645f77726f6e675f73697a655f75696e7433327661756c745f69645f77726f6e675f" + "73697a655f75696e7433326e66745f7572695f77726f6e675f73697a655f75696e743235366e66745f697373756572" + "5f77726f6e675f73697a655f75696e743235366e66745f7461786f6e5f77726f6e675f73697a655f75696e74323536" + "6e66745f73657269616c5f77726f6e675f73697a655f75696e743235366163636f756e74726f6f745f69645f77726f" + "6e675f73697a655f6163636f756e745f6964636865636b5f69645f77726f6e675f73697a655f6163636f756e745f69" + "6463726564656e7469616c5f69645f77726f6e675f73697a655f6163636f756e745f69643163726564656e7469616c" + "5f69645f77726f6e675f73697a655f6163636f756e745f69643264656c65676174655f69645f77726f6e675f73697a" + "655f6163636f756e745f69643164656c65676174655f69645f77726f6e675f73697a655f6163636f756e745f696432" + "6465706f7369745f707265617574685f69645f77726f6e675f73697a655f6163636f756e745f6964316465706f7369" + "745f707265617574685f69645f77726f6e675f73697a655f6163636f756e745f6964326469645f69645f77726f6e67" + "5f73697a655f6163636f756e745f6964657363726f775f69645f77726f6e675f73697a655f6163636f756e745f6964" + "74727573746c696e655f69645f77726f6e675f73697a655f6163636f756e745f69643174727573746c696e655f6964" + "5f77726f6e675f73697a655f6163636f756e745f6964326d70745f69737375616e63655f69645f77726f6e675f7369" + "7a655f6163636f756e745f69646d70746f6b656e5f69645f77726f6e675f73697a655f6163636f756e745f69646e66" + "745f6f666665725f69645f77726f6e675f73697a655f6163636f756e745f69646f666665725f69645f77726f6e675f" + "73697a655f6163636f756e745f69646f7261636c655f69645f77726f6e675f73697a655f6163636f756e745f696470" + "61796368616e5f69645f77726f6e675f73697a655f6163636f756e745f6964317061796368616e5f69645f77726f6e" + "675f73697a655f6163636f756e745f6964327065726d697373696f6e65645f646f6d61696e5f69645f77726f6e675f" + "73697a655f6163636f756e745f69647369676e6572735f69645f77726f6e675f73697a655f6163636f756e745f6964" + "7469636b65745f69645f77726f6e675f73697a655f6163636f756e745f69647661756c745f69645f77726f6e675f73" + "697a655f6163636f756e745f69646e66745f7572695f77726f6e675f73697a655f6163636f756e745f69646d70746f" + "6b656e5f69645f6d707469645f77726f6e675f6c656e677468004d0970726f64756365727302086c616e6775616765" + "010452757374000c70726f6365737365642d6279010572757374631d312e38372e3020283137303637653961632032" + "3032352d30352d303929002c0f7461726765745f6665617475726573022b0f6d757461626c652d676c6f62616c732b" + "087369676e2d657874"; + +extern std::string const kBadAlignWasmHex = + "0061736d01000000011b046000017f60057f7f7f7f7f017f60067f7f7f7f7f7f017f60000002260203656e760f666c" + "6f61745f66726f6d5f75696e74000103656e7608636865636b5f6964000203050403000000050301000306470b7f00" + "4180080b7f00418088020b7f004180080b7f00418088040b7f00418088040b7f00418088080b7f004180080b7f0041" + "8088080b7f004180800c0b7f0041000b7f0041010b07cc0110066d656d6f72790200115f5f7761736d5f63616c6c5f" + "63746f72730002057465737431000307655f64617461310300057465737432000407655f6461746132030104746573" + "7400050c5f5f64736f5f68616e646c6503020a5f5f646174615f656e6403030b5f5f737461636b5f6c6f7703040c5f" + "5f737461636b5f6869676803050d5f5f676c6f62616c5f6261736503060b5f5f686561705f6261736503070a5f5f68" + "6561705f656e6403080d5f5f6d656d6f72795f6261736503090c5f5f7461626c655f62617365030a0a99020402000b" + "2801017f418108427f370000418108410841a308410c41001000220041a40828020020004100481b0b5f01017f419a" + "88024191a4cca00136010041928802428994ace0d0c1c38710370100418a88024281848ca0d0c0c183083701004181" + "8802417f360000418a8802411441818802410441a3880241201001220041a4880228020020004100481b0b8a010103" + "7f418108427f370000418108410841a308410c410010002100419a88024191a4cca00136010041928802428994ace0" + "d0c1c38710370100418a88024281848ca0d0c0c1830837010041818802417f36000041a4082802002101418a880241" + "1441818802410441a3880241201001220241a4880228020020024100481b2000200120004100481b6a0b007f097072" + "6f647563657273010c70726f6365737365642d62790105636c616e675f31392e312e352d776173692d73646b202868" + "747470733a2f2f6769746875622e636f6d2f6c6c766d2f6c6c766d2d70726f6a656374206162346235613264623538" + "32393538616631656533303861373930636664623432626432343732302900490f7461726765745f66656174757265" + "73042b0f6d757461626c652d676c6f62616c732b087369676e2d6578742b0f7265666572656e63652d74797065732b" + "0a6d756c746976616c7565"; diff --git a/src/test/app/wasm_fixtures/fixtures.h b/src/test/app/wasm_fixtures/fixtures.h new file mode 100644 index 0000000000..4a3461a1fe --- /dev/null +++ b/src/test/app/wasm_fixtures/fixtures.h @@ -0,0 +1,12 @@ +#pragma once + +// TODO: consider moving these to separate files (and figure out the build) + +#include + +extern std::string const kLedgerSqnWasmHex; +extern std::string const kAllHostFunctionsWasmHex; +extern std::string const kAllKeyletsWasmHex; +extern std::string const kCodecovTestsWasmHex; + +extern std::string const kBadAlignWasmHex; diff --git a/src/test/app/wasm_fixtures/ledgerSqn.c b/src/test/app/wasm_fixtures/ledgerSqn.c new file mode 100644 index 0000000000..0f4c27af7d --- /dev/null +++ b/src/test/app/wasm_fixtures/ledgerSqn.c @@ -0,0 +1,14 @@ +#include + +int32_t ldgr_index(uint8_t *, int32_t); + +int escrow_finish() +{ + uint32_t sqn; + int32_t result = ldgr_index((uint8_t *)&sqn, sizeof(sqn)); + + if (result < 0) + return result; + + return sqn >= 5 ? 5 : 0; +}