diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..24620f8d7ca --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# Allow anyone to review any change by default. +* diff --git a/.github/scripts/levelization/results/ordering.txt b/.github/scripts/levelization/results/ordering.txt index c2000d17681..98e81b47f69 100644 --- a/.github/scripts/levelization/results/ordering.txt +++ b/.github/scripts/levelization/results/ordering.txt @@ -205,6 +205,7 @@ xrpl.core > xrpl.json xrpl.core > xrpl.protocol xrpl.json > xrpl.basics xrpl.ledger > xrpl.basics +xrpl.ledger > xrpl.core xrpl.ledger > xrpl.protocol xrpl.ledger > xrpl.server xrpl.ledger > xrpl.shamap diff --git a/CMakeLists.txt b/CMakeLists.txt index d315a5dcec7..446f8dc8f25 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,6 +93,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/cfg/xrpld-example.cfg b/cfg/xrpld-example.cfg index effc62c274c..bb8e93a8565 100644 --- a/cfg/xrpld-example.cfg +++ b/cfg/xrpld-example.cfg @@ -1283,6 +1283,39 @@ # Example: # owner_reserve = 200000 # 0.2 XRP # +# extension_compute_limit = +# +# The extension compute limit is the maximum amount of gas that can be +# consumed by a single transaction. The gas limit is used to prevent +# transactions from consuming too many resources. +# +# If this parameter is unspecified, xrpld will use an internal +# default. Don't change this without understanding the consequences. +# +# Example: +# extension_compute_limit = 1000000 # 1 million gas +# +# extension_size_limit = +# +# The extension size limit is the maximum size of a WASM extension in +# bytes. The size limit is used to prevent extensions from consuming +# too many resources. +# +# If this parameter is unspecified, xrpld will use an internal +# default. Don't change this without understanding the consequences. +# +# Example: +# extension_size_limit = 100000 # 100 kb +# +# gas_price = +# +# The gas price is the conversion between WASM gas and its price in drops. +# +# If this parameter is unspecified, xrpld will use an internal +# default. Don't change this without understanding the consequences. +# +# Example: +# gas_price = 1000000 # 1 drop per gas #------------------------------------------------------------------------------- # # 9. Misc Settings diff --git a/cmake/XrplCore.cmake b/cmake/XrplCore.cmake index 9b1dc74049f..8c6c3604231 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 e2eb8d871a8..5a46c7dcc3f 100644 --- a/conan.lock +++ b/conan.lock @@ -3,6 +3,7 @@ "requires": [ "zlib/1.3.2#1cb806da49011867778ffb6ac7190fcb%1777558780.503", "xxhash/0.8.3#681d36a0a6111fc56e5e45ea182c19cc%1765850149.987", + "wasmi/1.0.6#407c9db14601a8af1c7dd3b388f3e4cd%1768164779.349", "sqlite3/3.53.0#324ada52333108388a9a6108bfa96734%1776096494.149", "soci/4.0.3#fe32b9ad5eb47e79ab9e45a68f363945%1774450067.231", "snappy/1.1.10#968fef506ff261592ec30c574d4a7809%1765850147.878", diff --git a/conanfile.py b/conanfile.py index 2cf5aefbc26..bcb06cd66e0 100644 --- a/conanfile.py +++ b/conanfile.py @@ -34,6 +34,7 @@ class Xrpl(ConanFile): "openssl/3.6.2", "secp256k1/0.7.1", "soci/4.0.3", + "wasmi/1.0.6", "zlib/1.3.2", ] @@ -214,6 +215,7 @@ def package_info(self): "soci::soci", "secp256k1::secp256k1", "sqlite3::sqlite", + "wasmi::wasmi", "xxhash::xxhash", "zlib::zlib", ] diff --git a/cspell.config.yaml b/cspell.config.yaml index 275df41f584..b4ee022a644 100644 --- a/cspell.config.yaml +++ b/cspell.config.yaml @@ -7,6 +7,8 @@ ignorePaths: - cmake/** - LICENSE.md - .clang-tidy + - src/test/app/wasm_fixtures/**/*.wat + - src/test/app/wasm_fixtures/*.c language: en allowCompoundWords: true # TODO (#6334) ignoreRandomStrings: true @@ -65,6 +67,7 @@ words: - Btrfs - Buildx - canonicality + - cdylib - changespq - checkme - choco @@ -103,6 +106,7 @@ words: - distro - doxyfile - dxrpl + - emittance - enabled - enablerepo - endmacro @@ -266,6 +270,7 @@ words: - statsd - STATSDCOLLECTOR - stissue + - stjson - stnum - stobj - stobject diff --git a/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index e67f1f534d8..19147c9ca49 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -731,6 +731,10 @@ abs(Number x) noexcept Number power(Number const& f, unsigned n); +// logarithm with base 10 +Number +log10(Number const& value, int iterations = 50); + // Returns f^(1/d) // Uses Newton–Raphson iterations until the result stops changing // to find the root of the polynomial g(x) = x^d - f diff --git a/include/xrpl/core/ServiceRegistry.h b/include/xrpl/core/ServiceRegistry.h index 1d0c9e38f40..4433fd5121a 100644 --- a/include/xrpl/core/ServiceRegistry.h +++ b/include/xrpl/core/ServiceRegistry.h @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -238,6 +239,9 @@ class ServiceRegistry virtual DatabaseCon& getWalletDB() = 0; + virtual Fees + getFees() const = 0; + // Temporary: Get the underlying Application for functions that haven't // been migrated yet. This should be removed once all code is migrated. virtual Application& diff --git a/include/xrpl/ledger/ApplyViewImpl.h b/include/xrpl/ledger/ApplyViewImpl.h index 7f790f2be52..930929b7b63 100644 --- a/include/xrpl/ledger/ApplyViewImpl.h +++ b/include/xrpl/ledger/ApplyViewImpl.h @@ -54,6 +54,18 @@ class ApplyViewImpl final : public detail::ApplyViewBase deliver_ = amount; } + void + setGasUsed(std::optional const gasUsed) + { + gasUsed_ = gasUsed; + } + + void + setWasmReturnCode(std::int32_t const wasmReturnCode) + { + wasmReturnCode_ = wasmReturnCode; + } + /** Get the number of modified entries */ std::size_t @@ -72,6 +84,8 @@ class ApplyViewImpl final : public detail::ApplyViewBase private: std::optional deliver_; + std::optional gasUsed_; + std::optional wasmReturnCode_; }; } // namespace xrpl diff --git a/include/xrpl/ledger/OpenViewSandbox.h b/include/xrpl/ledger/OpenViewSandbox.h new file mode 100644 index 00000000000..66f65c51dea --- /dev/null +++ b/include/xrpl/ledger/OpenViewSandbox.h @@ -0,0 +1,82 @@ +#pragma once + +#include + +#include + +namespace xrpl { + +class OpenViewSandbox +{ +private: + OpenView& parent_; + std::unique_ptr sandbox_; + +public: + using key_type = ReadView::key_type; + + OpenViewSandbox(OpenView& parent) + : parent_(parent), sandbox_(std::make_unique(kBatchView, parent)) + { + } + + void + rawErase(std::shared_ptr const& sle) + { + sandbox_->rawErase(sle); + } + + void + rawInsert(std::shared_ptr const& sle) + { + sandbox_->rawInsert(sle); + } + + void + rawReplace(std::shared_ptr const& sle) + { + sandbox_->rawReplace(sle); + } + + void + rawDestroyXRP(XRPAmount const& fee) + { + sandbox_->rawDestroyXRP(fee); + } + + void + rawTxInsert( + key_type const& key, + std::shared_ptr const& txn, + std::shared_ptr const& metaData) + { + sandbox_->rawTxInsert(key, txn, metaData); + } + + void + commit() + { + sandbox_->apply(parent_); + sandbox_ = std::make_unique(kBatchView, parent_); + } + + void + discard() + { + sandbox_ = std::make_unique(kBatchView, parent_); + } + + OpenView const& + view() const + { + return *sandbox_; + } + + OpenView& + view() + { + return *sandbox_; + } +}; + +} // namespace xrpl diff --git a/include/xrpl/ledger/View.h b/include/xrpl/ledger/View.h index 0d76c98a73b..a14f4b04811 100644 --- a/include/xrpl/ledger/View.h +++ b/include/xrpl/ledger/View.h @@ -3,6 +3,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -211,6 +215,27 @@ doWithdraw( STAmount const& amount, beast::Journal j); +enum class SendIssuerHandling { ihSENDER_NOT_ALLOWED, ihRECEIVER_NOT_ALLOWED, ihIGNORE }; +enum class SendEscrowHandling { ehIGNORE, ehCHECK }; +enum class SendAuthHandling { ahCHECK_SENDER, ahCHECK_RECEIVER, ahBOTH, ahNEITHER }; +enum class SendFreezeHandling { fhCHECK_SENDER, fhCHECK_RECEIVER, fhBOTH, fhNEITHER }; +enum class SendTransferHandling { thIGNORE, thCHECK }; +enum class SendBalanceHandling { bhIGNORE, bhCHECK }; + +TER +canTransferFT( + ReadView const& view, + AccountID const& sender, + AccountID const& receiver, + STAmount const& amount, + beast::Journal j, + SendIssuerHandling issuerHandling, + SendEscrowHandling escrowHandling, + SendAuthHandling authHandling, + SendFreezeHandling freezeHandling, + SendTransferHandling transferHandling, + SendBalanceHandling balanceHandling); + /** Deleter function prototype. Returns the status of the entry deletion * (if should not be skipped) and if the entry should be skipped. The status * is always tesSUCCESS if the entry should be skipped. diff --git a/include/xrpl/ledger/detail/ApplyStateTable.h b/include/xrpl/ledger/detail/ApplyStateTable.h index 7b18f742b44..ed48e430529 100644 --- a/include/xrpl/ledger/detail/ApplyStateTable.h +++ b/include/xrpl/ledger/detail/ApplyStateTable.h @@ -51,6 +51,8 @@ class ApplyStateTable TER ter, std::optional const& deliver, std::optional const& parentBatchId, + std::optional const& gasUsed, + std::optional const& wasmReturnCode, bool isDryRun, beast::Journal j); diff --git a/include/xrpl/ledger/helpers/ContractUtils.h b/include/xrpl/ledger/helpers/ContractUtils.h new file mode 100644 index 00000000000..831276b623a --- /dev/null +++ b/include/xrpl/ledger/helpers/ContractUtils.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +class ContractDataMap : public std::map> +{ +public: + uint32_t modifiedCount = 0; +}; + +class ContractEventMap : public std::map +{ +}; + +namespace contract { + +/** The maximum number of data modifications in a single function. */ +int64_t constexpr maxDataModifications = 1000; + +/** The maximum number of bytes the data can occupy. */ +int64_t constexpr maxContractDataSize = 1024; + +/** The multiplier for contract data size calculations. */ +int64_t constexpr dataByteMultiplier = 512; + +/** The cost multiplier of creating a contract in bytes. */ +int64_t constexpr createByteMultiplier = 500ULL; + +/** The value to return when the fee calculation failed. */ +int64_t constexpr feeCalculationFailed = 0x7FFFFFFFFFFFFFFFLL; + +/** The maximum number of contract parameters that can be in a transaction. */ +std::size_t constexpr maxContractParams = 8; + +/** The maximum number of contract functions that can be in a transaction. */ +std::size_t constexpr maxContractFunctions = 32; + +int64_t +contractCreateFee(uint64_t byteCount); + +NotTEC +preflightFunctions(STTx const& tx, beast::Journal j); + +NotTEC +preflightInstanceParameters(STTx const& tx, beast::Journal j); + +bool +validateParameterMapping(STArray const& params, STArray const& values, beast::Journal j); + +NotTEC +preflightInstanceParameterValues(STTx const& tx, beast::Journal j); + +NotTEC +preflightFlagParameters(STArray const& parameters, beast::Journal j); + +bool +isValidParameterFlag(std::uint32_t flags); + +TER +preclaimFlagParameters( + ReadView const& view, + AccountID const& sourceAccount, + AccountID const& contractAccount, + STArray const& parameters, + beast::Journal j); + +TER +doApplyFlagParameters( + ApplyView& view, + STTx const& tx, + AccountID const& sourceAccount, + AccountID const& contractAccount, + STArray const& parameters, + XRPAmount const& priorBalance, + beast::Journal j); + +TER +finalizeContractData( + ServiceRegistry& registry, + ApplyView& view, + AccountID const& contractAccount, + ContractDataMap const& dataMap, + ContractEventMap const& eventMap, + uint256 const& txnID); + +} // namespace contract +} // namespace xrpl diff --git a/include/xrpl/ledger/helpers/EscrowHelpers.h b/include/xrpl/ledger/helpers/EscrowHelpers.h index 859981cf059..13a4cc68687 100644 --- a/include/xrpl/ledger/helpers/EscrowHelpers.h +++ b/include/xrpl/ledger/helpers/EscrowHelpers.h @@ -227,4 +227,18 @@ escrowUnlockApplyHelper( journal); } +// calculateAdditionalReserve computes the owner count impact of an Escrow. +// An escrow without a FinishFunction costs 1 reserve. With a FinishFunction, +// each additional 500 bytes beyond the first 500 adds another reserve slot. +template +inline uint32_t +calculateAdditionalReserve(T const& finishFunction) +{ + if (!finishFunction) + return 1; + // First 500 bytes included in the normal reserve + // Each additional 500 bytes requires an additional reserve + return 1 + (finishFunction->size() / 500); +} + } // namespace xrpl diff --git a/include/xrpl/ledger/helpers/NFTokenHelpers.h b/include/xrpl/ledger/helpers/NFTokenHelpers.h index 4294e1ca135..d275c7d6b3e 100644 --- a/include/xrpl/ledger/helpers/NFTokenHelpers.h +++ b/include/xrpl/ledger/helpers/NFTokenHelpers.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -136,4 +137,11 @@ checkTrustlineDeepFrozen( beast::Journal const j, Issue const& issue); +TER +transferNFToken( + ApplyView& view, + AccountID const& buyer, + AccountID const& seller, + uint256 const& nftokenID); + } // namespace xrpl::nft diff --git a/include/xrpl/ledger/helpers/NFTokenUtils.h b/include/xrpl/ledger/helpers/NFTokenUtils.h new file mode 100644 index 00000000000..46d8c5a8877 --- /dev/null +++ b/include/xrpl/ledger/helpers/NFTokenUtils.h @@ -0,0 +1,5 @@ +#pragma once + +// This file has been renamed to NFTokenHelpers.h +// This forwarding header exists for compatibility. +#include diff --git a/include/xrpl/protocol/Emitable.h b/include/xrpl/protocol/Emitable.h new file mode 100644 index 00000000000..6acce0d1020 --- /dev/null +++ b/include/xrpl/protocol/Emitable.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace xrpl { +/** + * We have both transaction type emitables and granular type emitables. + * Since we will reuse the TransactionFormats to parse the Transaction + * Emitables, only the GranularEmitableType is defined here. To prevent + * conflicts with TxType, the GranularEmitableType is always set to a value + * greater than the maximum value of uint16. + */ +enum GranularEmitableType : std::uint32_t { +#pragma push_macro("EMITABLE") +#undef EMITABLE + +#define EMITABLE(type, txType, value) type = value, + +#include + +#undef EMITABLE +#pragma pop_macro("EMITABLE") +}; + +enum Emittance { emitable, notEmitable }; + +class Emitable +{ +private: + Emitable(); + + std::unordered_map emitableTx_; + + std::unordered_map granularEmitableMap_; + + std::unordered_map granularNameMap_; + + std::unordered_map granularTxTypeMap_; + +public: + static Emitable const& + getInstance(); + + Emitable(Emitable const&) = delete; + Emitable& + operator=(Emitable const&) = delete; + + std::optional + getEmitableName(std::uint32_t const value) const; + + std::optional + getGranularValue(std::string const& name) const; + + std::optional + getGranularName(GranularEmitableType const& value) const; + + std::optional + getGranularTxType(GranularEmitableType const& gpType) const; + + bool + isEmitable(std::uint32_t const& emitableValue) const; + + // for tx level emitable, emitable value is equal to tx type plus one + uint32_t + txToEmitableType(TxType const& type) const; + + // tx type value is emitable value minus one + TxType + emitableToTxType(uint32_t const& value) const; +}; + +} // namespace xrpl diff --git a/include/xrpl/protocol/Fees.h b/include/xrpl/protocol/Fees.h index 14bcc068bf3..4d1bfe6a28c 100644 --- a/include/xrpl/protocol/Fees.h +++ b/include/xrpl/protocol/Fees.h @@ -8,6 +8,9 @@ namespace xrpl { // This was the reference fee units used in the old fee calculation. inline constexpr std::uint32_t kFeeUnitsDeprecated = 10; +// Number of micro-drops in one drop. +constexpr std::uint32_t MICRO_DROPS_PER_DROP{1'000'000}; + /** Reflects the fee settings for a particular ledger. The fees are always the same for any transactions applied @@ -24,6 +27,15 @@ struct Fees /** @brief Additional XRP reserve required per owned ledger object. */ XRPAmount increment{0}; + /** @brief Compute limit for Feature Extensions (instructions). */ + std::uint32_t extensionComputeLimit{0}; + + /** @brief Size limit for Feature Extensions (bytes). */ + std::uint32_t extensionSizeLimit{0}; + + /** @brief Price of WASM gas (micro-drops). */ + std::uint32_t gasPrice{0}; + explicit Fees() = default; Fees(Fees const&) = default; Fees& diff --git a/include/xrpl/protocol/Indexes.h b/include/xrpl/protocol/Indexes.h index 887a208ec61..62f220b957c 100644 --- a/include/xrpl/protocol/Indexes.h +++ b/include/xrpl/protocol/Indexes.h @@ -207,6 +207,12 @@ page(Keylet const& root, std::uint64_t index = 0) noexcept Keylet escrow(AccountID const& src, std::uint32_t seq) noexcept; +inline Keylet +escrow(uint256 const& key) noexcept +{ + return {ltESCROW, key}; +} + /** A PaymentChannel */ Keylet payChan(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept; @@ -342,6 +348,22 @@ permissionedDomain(AccountID const& account, std::uint32_t seq) noexcept; Keylet permissionedDomain(uint256 const& domainID) noexcept; + +Keylet +contractSource(uint256 const& contractHash) noexcept; + +Keylet +contract(uint256 const& contractHash, AccountID const& owner, std::uint32_t seq) noexcept; + +inline Keylet +contract(uint256 const& contractID) +{ + return {ltCONTRACT, contractID}; +} + +Keylet +contractData(AccountID const& owner, AccountID const& contractAccount) noexcept; + } // namespace keylet // Everything below is deprecated and should be removed in favor of keylets: diff --git a/include/xrpl/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index 99d5d818f14..601728b6308 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -78,14 +78,6 @@ enum LedgerEntryType : std::uint16_t { */ ltNICKNAME [[deprecated("This object type is not supported and should not be used.")]] = 0x006e, - /** A legacy, deprecated type. - - \deprecated **This object type is not supported and should not be used.** - Support for this type of object was never implemented. - No objects of this type were ever created. - */ - ltCONTRACT [[deprecated("This object type is not supported and should not be used.")]] = 0x0063, - /** A legacy, deprecated type. \deprecated **This object type is not supported and should not be used.** diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index 6a96b2ccbe3..72b3b8d570a 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -251,6 +251,12 @@ constexpr std::uint8_t kVaultMaximumIouScale = 18; * another vault; counted from 0 */ constexpr std::uint8_t kMaxAssetCheckDepth = 5; +/** Maximum length of a Data field in Escrow object that can be updated by WASM code. */ +std::size_t constexpr maxWasmDataLength = 4 * 1024; // 4KB + +/** Maximum length of parameters passed from WASM code to host functions. */ +std::size_t constexpr maxWasmParamLength = 1024; // 1KB + /** A ledger index. */ using LedgerIndex = std::uint32_t; diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index 34fb66ce00b..045ad242189 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -33,6 +33,9 @@ class STNumber; class STXChainBridge; class STVector256; class STCurrency; +class STData; +class STDataType; +class STJson; // NOLINTBEGIN(readability-identifier-naming) #pragma push_macro("XMACRO") @@ -72,6 +75,9 @@ class STCurrency; STYPE(STI_ISSUE, 24) \ STYPE(STI_XCHAIN_BRIDGE, 25) \ STYPE(STI_CURRENCY, 26) \ + STYPE(STI_DATA, 27) \ + STYPE(STI_DATATYPE, 28) \ + STYPE(STI_JSON, 29) \ \ /* high-level types */ \ /* cannot be serialized inside other types */ \ @@ -353,6 +359,9 @@ using SF_NUMBER = TypedField; using SF_VL = TypedField; using SF_VECTOR256 = TypedField; using SF_XCHAIN_BRIDGE = TypedField; +using SF_DATA = TypedField; +using SF_DATATYPE = TypedField; +using SF_JSON = TypedField; //------------------------------------------------------------------------------ diff --git a/include/xrpl/protocol/STData.h b/include/xrpl/protocol/STData.h new file mode 100644 index 00000000000..5c2165d6a84 --- /dev/null +++ b/include/xrpl/protocol/STData.h @@ -0,0 +1,290 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl { + +class STData final : public STBase +{ +private: + using data_type = detail::STVar; + std::uint16_t inner_type_; + data_type data_; + bool default_{true}; + +public: + using value_type = STData; // Although not directly holding a single value + + STData(SField const& n); + STData(SField const& n, unsigned char); + STData(SField const& n, std::uint16_t); + STData(SField const& n, std::uint32_t); + STData(SField const& n, std::uint64_t); + STData(SField const& n, uint128 const&); + STData(SField const& n, uint160 const&); + STData(SField const& n, uint192 const&); + STData(SField const& n, uint256 const&); + STData(SField const& n, Blob const&); + STData(SField const& n, Slice const&); + STData(SField const& n, AccountID const&); + STData(SField const& n, STAmount const&); + STData(SField const& n, STIssue const&); + STData(SField const& n, STCurrency const&); + STData(SField const& n, STNumber const&); + + STData(SerialIter& sit, SField const& name); + + std::size_t + size() const; + + SerializedTypeID + getSType() const override; + + std::string + getInnerTypeString() const; + + std::string + getText() const override; + + json::Value getJson(JsonOptions) const override; + + void + add(Serializer& s) const override; + + bool + isEquivalent(STBase const& t) const override; + + bool + isDefault() const override; + + SerializedTypeID + getInnerSType() const noexcept; + + STBase* + makeFieldPresent(); + + void + setFieldU8(unsigned char); + void + setFieldU16(std::uint16_t); + void + setFieldU32(std::uint32_t); + void + setFieldU64(std::uint64_t); + void + setFieldH128(uint128 const&); + void + setFieldH160(uint160 const&); + void + setFieldH192(uint192 const&); + void + setFieldH256(uint256 const&); + void + setFieldVL(Blob const&); + void + setFieldVL(Slice const&); + void + setAccountID(AccountID const&); + void + setFieldAmount(STAmount const&); + void + setIssue(STIssue const&); + void + setCurrency(STCurrency const&); + void + setFieldNumber(STNumber const&); + + unsigned char + getFieldU8() const; + std::uint16_t + getFieldU16() const; + std::uint32_t + getFieldU32() const; + std::uint64_t + getFieldU64() const; + uint128 + getFieldH128() const; + uint160 + getFieldH160() const; + uint192 + getFieldH192() const; + uint256 + getFieldH256() const; + Blob + getFieldVL() const; + AccountID + getAccountID() const; + STAmount const& + getFieldAmount() const; + STIssue + getFieldIssue() const; + STCurrency + getFieldCurrency() const; + STNumber + getFieldNumber() const; + +private: + STBase* + copy(std::size_t n, void* buf) const override; + STBase* + move(std::size_t n, void* buf) override; + + friend class detail::STVar; + + // Implementation for getting (most) fields that return by value. + // + // The remove_cv and remove_reference are necessitated by the STBitString + // types. Their value() returns by const ref. We return those types + // by value. + template < + typename T, + typename V = typename std::remove_cv< + typename std::remove_reference().value())>::type>::type> + V + getFieldByValue() const; + + // Implementations for getting (most) fields that return by const reference. + // + // If an absent optional field is deserialized we don't have anything + // obvious to return. So we insist on having the call provide an + // 'empty' value we return in that circumstance. + template + V const& + getFieldByConstRef(V const& empty) const; + + // Implementation for setting most fields with a setValue() method. + template + void + setFieldUsingSetValue(V value); + + // Implementation for setting fields using assignment + template + void + setFieldUsingAssignment(T const& value); +}; + +//------------------------------------------------------------------------------ +// Implementation +//------------------------------------------------------------------------------ + +inline SerializedTypeID +STData::getInnerSType() const noexcept +{ + return static_cast(inner_type_); +} + +template +V +STData::getFieldByValue() const +{ + STBase const* rf = &data_.get(); + + // if (!rf) + // throwFieldNotFound(getFName()); + + SerializedTypeID const id = rf->getSType(); + + if (id == STI_NOTPRESENT) + Throw("Field not present"); + + T const* cf = dynamic_cast(rf); + + if (!cf) + Throw("Wrong field type"); + + return cf->value(); +} + +// Implementations for getting (most) fields that return by const reference. +// +// If an absent optional field is deserialized we don't have anything +// obvious to return. So we insist on having the call provide an +// 'empty' value we return in that circumstance. +template +V const& +STData::getFieldByConstRef(V const& empty) const +{ + STBase const* rf = &data_.get(); + + // if (!rf) + // throwFieldNotFound(field); + + SerializedTypeID const id = rf->getSType(); + + if (id == STI_NOTPRESENT) + return empty; // optional field not present + + T const* cf = dynamic_cast(rf); + + if (!cf) + Throw("Wrong field type"); + + return *cf; +} + +// Implementation for setting most fields with a setValue() method. +template +void +STData::setFieldUsingSetValue(V value) +{ + static_assert(!std::is_lvalue_reference::value, ""); + + STBase* rf = &data_.get(); + + // if (!rf) + // throwFieldNotFound(field); + + if (rf->getSType() == STI_NOTPRESENT) + rf = makeFieldPresent(); + + T* cf = dynamic_cast(rf); + + if (!cf) + Throw("Wrong field type"); + + cf->setValue(std::move(value)); +} + +// Implementation for setting fields using assignment +template +void +STData::setFieldUsingAssignment(T const& value) +{ + STBase* rf = &data_.get(); + + // if (!rf) + // throwFieldNotFound(field); + + // if (rf->getSType() == STI_NOTPRESENT) + // rf = makeFieldPresent(field); + + T* cf = dynamic_cast(rf); + + if (!cf) + Throw("Wrong field type"); + + (*cf) = value; +} + +//------------------------------------------------------------------------------ +// +// Creation +// +//------------------------------------------------------------------------------ + +STData +dataFromJson(SField const& field, json::Value const& value); + +} // namespace xrpl diff --git a/include/xrpl/protocol/STDataType.h b/include/xrpl/protocol/STDataType.h new file mode 100644 index 00000000000..51aec6d4b30 --- /dev/null +++ b/include/xrpl/protocol/STDataType.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl { + +class STDataType final : public STBase +{ +private: + std::uint16_t inner_type_; + bool default_{true}; + +public: + using value_type = STDataType; // Although not directly holding a single value + + STDataType(SField const& n); + STDataType(SField const& n, SerializedTypeID); + + STDataType(SerialIter& sit, SField const& name); + + SerializedTypeID + getSType() const override; + + std::string + getInnerTypeString() const; + + std::string + getText() const override; + + json::Value getJson(JsonOptions) const override; + + void + add(Serializer& s) const override; + + bool + isEquivalent(STBase const& t) const override; + + bool + isDefault() const override; + + void setInnerSType(SerializedTypeID); + + SerializedTypeID + getInnerSType() const noexcept; + + STBase* + makeFieldPresent(); + + STBase* + copy(std::size_t n, void* buf) const override; + STBase* + move(std::size_t n, void* buf) override; + + friend class detail::STVar; +}; + +//------------------------------------------------------------------------------ +// Implementation +//------------------------------------------------------------------------------ + +inline SerializedTypeID +STDataType::getInnerSType() const noexcept +{ + return static_cast(inner_type_); +} + +//------------------------------------------------------------------------------ +// +// Creation +// +//------------------------------------------------------------------------------ + +STDataType +dataTypeFromJson(SField const& field, json::Value const& value); + +} // namespace xrpl diff --git a/include/xrpl/protocol/STJson.h b/include/xrpl/protocol/STJson.h new file mode 100644 index 00000000000..0ab84e86061 --- /dev/null +++ b/include/xrpl/protocol/STJson.h @@ -0,0 +1,193 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace xrpl { + +/** + * STJson: Serialized Type for JSON-like structures (objects or arrays). + * + * Supports two modes: + * - Object: Key-value pairs where keys are VL-encoded strings + * - Array: Ordered list of values + * + * Values are [SType marker][VL-encoded SType serialization]. + * Values can be any SType, including nested STJson. + * + * Serialization format: [type_byte][VL_length][data...] + * - type_byte: 0x00 = Object, 0x01 = Array + */ +class STJson : public STBase +{ +public: + enum class JsonType : uint8_t { Object = 0x00, Array = 0x01 }; + + using value_type = STJson; + value_type + value() const + { + return *this; + } + + using Key = std::string; + using Value = std::shared_ptr; + using Map = std::map; + using Array = std::vector; + + STJson() = default; + + explicit STJson(Map&& map); + explicit STJson(Array&& array); + explicit STJson(SField const& name); + explicit STJson(SerialIter& sit, SField const& name); + + SerializedTypeID + getSType() const override; + + // Type checking + bool + isArray() const; + + bool + isObject() const; + + JsonType + getType() const; + + // Depth checking (0 = no nesting, 1 = one level of nesting) + int + getDepth() const; + + // Parse from binary blob + static std::shared_ptr + fromBlob(void const* data, std::size_t size); + + // Parse from SerialIter + static std::shared_ptr + fromSerialIter(SerialIter& sit); + + // Serialize to binary + void + add(Serializer& s) const override; + + // JSON representation + json::Value + getJson(JsonOptions options) const override; + + bool + isEquivalent(STBase const& t) const override; + + bool + isDefault() const override; + + // Blob representation + Blob + toBlob() const; + + // STJson size + std::size_t + size() const; + + // Object accessors (only valid when isObject() == true) + Map const& + getMap() const; + + void + setObjectField(Key const& key, Value const& value); + + std::optional + getObjectField(Key const& key) const; + + void + setNestedObjectField(Key const& key, Key const& nestedKey, Value const& value); + + std::optional + getNestedObjectField(Key const& key, Key const& nestedKey) const; + + // Array accessors (only valid when isArray() == true) + Array const& + getArray() const; + + void + pushArrayElement(Value const& value); + + std::optional + getArrayElement(size_t index) const; + + void + setArrayElement(size_t index, Value const& value); + + void + setArrayElementField(size_t index, Key const& key, Value const& value); + + std::optional + getArrayElementField(size_t index, Key const& key) const; + + size_t + arraySize() const; + + // Nested array accessors (for arrays stored in object fields) + void + setNestedArrayElement(Key const& key, size_t index, Value const& value); + + void + setNestedArrayElementField( + Key const& key, + size_t index, + Key const& nestedKey, + Value const& value); + + std::optional + getNestedArrayElement(Key const& key, size_t index) const; + + std::optional + getNestedArrayElementField(Key const& key, size_t index, Key const& nestedKey) const; + + // Factory for SType value from blob (with SType marker) + static Value + makeValueFromVLWithType(SerialIter& sit); + + void + setValue(STJson const& v); + +private: + std::variant data_{Map{}}; + bool default_{false}; + + // Helper: validate nesting depth (max 1 level) + void + validateDepth(Value const& value, int currentDepth) const; + + // Helper: parse a single key-value pair from SerialIter + static std::pair + parsePair(SerialIter& sit); + + // Helper: parse array elements from SerialIter + static Array + parseArray(SerialIter& sit, int length); + + // Helper: encode a key as VL + static void + addVLKey(Serializer& s, std::string const& str); + + // Helper: encode a value as [SType marker][VL] + static void + addVLValue(Serializer& s, std::shared_ptr const& value); + + STBase* + copy(std::size_t n, void* buf) const override; + STBase* + move(std::size_t n, void* buf) override; + + friend class detail::STVar; +}; + +} // namespace xrpl diff --git a/include/xrpl/protocol/STObject.h b/include/xrpl/protocol/STObject.h index c635e8ce229..3084f51db07 100644 --- a/include/xrpl/protocol/STObject.h +++ b/include/xrpl/protocol/STObject.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -216,6 +217,10 @@ class STObject : public STBase, public CountedObject getFieldI32(SField const& field) const; [[nodiscard]] AccountID getAccountID(SField const& field) const; + STData + getFieldData(SField const& field) const; + STDataType + getFieldDataType(SField const& field) const; [[nodiscard]] Blob getFieldVL(SField const& field) const; @@ -234,6 +239,8 @@ class STObject : public STBase, public CountedObject getFieldCurrency(SField const& field) const; [[nodiscard]] STNumber const& getFieldNumber(SField const& field) const; + STJson const& + getFieldJson(SField const& field) const; /** Get the value of a field. @param A TypedField built from an SField value representing the desired @@ -338,6 +345,9 @@ class STObject : public STBase, public CountedObject void set(STBase&& v); + void + addFieldFromSlice(SField const& sfield, Slice const& data); + void setFieldU8(SField const& field, unsigned char); void @@ -378,6 +388,8 @@ class STObject : public STBase, public CountedObject setFieldArray(SField const& field, STArray const& v); void setFieldObject(SField const& field, STObject const& v); + void + setFieldJson(SField const& field, STJson const& v); template void diff --git a/include/xrpl/protocol/TER.h b/include/xrpl/protocol/TER.h index c89610f3544..f5a42c1f5b3 100644 --- a/include/xrpl/protocol/TER.h +++ b/include/xrpl/protocol/TER.h @@ -128,6 +128,9 @@ enum TEMcodes : TERUnderlyingType { temBAD_TRANSFER_FEE, temINVALID_INNER_BATCH, temBAD_MPT, + + temBAD_WASM, + temTEMP_DISABLED, }; //------------------------------------------------------------------------------ @@ -174,6 +177,8 @@ enum TEFcodes : TERUnderlyingType { tefNO_TICKET, tefNFTOKEN_IS_NOT_TRANSFERABLE, tefINVALID_LEDGER_FIX_TYPE, + tefNO_WASM, + tefWASM_FIELD_NOT_INCLUDED, }; //------------------------------------------------------------------------------ @@ -358,6 +363,12 @@ enum TECcodes : TERUnderlyingType { tecLIMIT_EXCEEDED = 195, tecPSEUDO_ACCOUNT = 196, tecPRECISION_LOSS = 197, + // DEPRECATED: This error code tecNO_DELEGATE_PERMISSION is reserved for + // backward compatibility with historical data on non-prod networks, can be + // reclaimed after those networks reset. + tecNO_DELEGATE_PERMISSION = 198, + tecWASM_REJECTED = 199, + tecINVALID_PARAMETERS = 200, }; //------------------------------------------------------------------------------ diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index 4652cc1bf09..d442eb94856 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -214,8 +214,21 @@ inline constexpr FlagValue tfUniversalMask = ~tfUniversal; TF_FLAG(tfLoanDefault, 0x00010000) \ TF_FLAG(tfLoanImpair, 0x00020000) \ TF_FLAG(tfLoanUnimpair, 0x00040000), \ + MASK_ADJ(0)) \ + \ + TRANSACTION(Contract, \ + TF_FLAG(tfImmutable, 0x00010000) \ + TF_FLAG(tfCodeImmutable, 0x00020000) \ + TF_FLAG(tfABIImmutable, 0x00040000) \ + TF_FLAG(tfUndeletable, 0x00080000), \ MASK_ADJ(0)) +constexpr std::uint32_t tfSendAmount = 0x00010000; +constexpr std::uint32_t tfSendNFToken = 0x00020000; +constexpr std::uint32_t tfAuthorizeToken = 0x00040000; +constexpr std::uint32_t tfContractParameterMask = + ~(tfSendAmount | tfSendNFToken | tfAuthorizeToken); + // clang-format on // Create all the flag values. diff --git a/include/xrpl/protocol/TxMeta.h b/include/xrpl/protocol/TxMeta.h index 6895350e9f2..3f80636c6fb 100644 --- a/include/xrpl/protocol/TxMeta.h +++ b/include/xrpl/protocol/TxMeta.h @@ -84,6 +84,12 @@ class TxMeta if (obj.isFieldPresent(sfParentBatchID)) parentBatchID_ = obj.getFieldH256(sfParentBatchID); + + if (obj.isFieldPresent(sfGasUsed)) + gasUsed_ = obj.getFieldU32(sfGasUsed); + + if (obj.isFieldPresent(sfWasmReturnCode)) + wasmReturnCode_ = obj.getFieldI32(sfWasmReturnCode); } [[nodiscard]] std::optional const& @@ -104,6 +110,30 @@ class TxMeta parentBatchID_ = id; } + void + setGasUsed(std::optional const gasUsed) + { + gasUsed_ = gasUsed; + } + + std::optional const& + getGasUsed() const + { + return gasUsed_; + } + + void + setWasmReturnCode(std::optional const wasmReturnCode) + { + wasmReturnCode_ = wasmReturnCode; + } + + std::optional const& + getWasmReturnCode() const + { + return wasmReturnCode_; + } + private: uint256 transactionID_; std::uint32_t ledgerSeq_; @@ -112,6 +142,8 @@ class TxMeta std::optional deliveredAmount_; std::optional parentBatchID_; + std::optional gasUsed_; + std::optional wasmReturnCode_; STArray nodes_; }; diff --git a/include/xrpl/protocol/detail/emitable.macro b/include/xrpl/protocol/detail/emitable.macro new file mode 100644 index 00000000000..be410bbb7c2 --- /dev/null +++ b/include/xrpl/protocol/detail/emitable.macro @@ -0,0 +1,19 @@ +#if !defined(EMITABLE) +#error "undefined macro: EMITABLE" +#endif + +/** + * EMITABLE(name, type, txType, value) + * + * This macro defines a permission: + * name: the name of the permission. + * type: the GranularPermissionType enum. + * txType: the corresponding TxType for this permission. + * value: the uint32 numeric value for the enum type. + */ + +/** This removes the contract account the ability to set or remove deposit auth. */ +EMITABLE(AccountDepositAuth, ttACCOUNT_SET, 65537) + +// ** This removes the contract account the ability to set or remove disable master key. */ +EMITABLE(AccountDisableMaster, ttACCOUNT_SET, 65538) diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index fd62b74d596..f5be523791f 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -15,6 +15,11 @@ // Add new amendments to the top of this list. // Keep it sorted in reverse chronological order. +XRPL_FEATURE(SmartContract, Supported::No, VoteBehavior::DefaultNo) +XRPL_FEATURE(SmartEscrow, Supported::No, VoteBehavior::DefaultNo) +XRPL_FIX (Security3_1_3, Supported::No, VoteBehavior::DefaultNo) +XRPL_FIX (PermissionedDomainInvariant, Supported::Yes, VoteBehavior::DefaultNo) +XRPL_FIX (ExpiredNFTokenOfferRemoval, Supported::Yes, VoteBehavior::DefaultNo) XRPL_FIX (Cleanup3_2_0, Supported::No, VoteBehavior::DefaultNo) XRPL_FEATURE(MPTokensV2, Supported::No, VoteBehavior::DefaultNo) XRPL_FIX (Cleanup3_1_3, Supported::Yes, VoteBehavior::DefaultYes) diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index 632038a9c5b..cffbade3195 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -150,6 +150,7 @@ LEDGER_ENTRY(ltACCOUNT_ROOT, 0x0061, AccountRoot, account, ({ {sfAMMID, SoeOptional}, // pseudo-account designator {sfVaultID, SoeOptional}, // pseudo-account designator {sfLoanBrokerID, SoeOptional}, // pseudo-account designator + {sfContractID, SoeOptional}, // pseudo-account designator })) /** A ledger object which contains a list of object identifiers. @@ -304,6 +305,10 @@ LEDGER_ENTRY(ltFEE_SETTINGS, 0x0073, FeeSettings, fee, ({ {sfBaseFeeDrops, SoeOptional}, {sfReserveBaseDrops, SoeOptional}, {sfReserveIncrementDrops, SoeOptional}, + // Smart Escrow fields + {sfExtensionComputeLimit, SoeOptional}, + {sfExtensionSizeLimit, SoeOptional}, + {sfGasPrice, SoeOptional}, {sfPreviousTxnID, SoeOptional}, {sfPreviousTxnLgrSeq, SoeOptional}, })) @@ -334,6 +339,8 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({ {sfCondition, SoeOptional}, {sfCancelAfter, SoeOptional}, {sfFinishAfter, SoeOptional}, + {sfFinishFunction, SoeOptional}, + {sfData, SoeOptional}, {sfSourceTag, SoeOptional}, {sfDestinationTag, SoeOptional}, {sfOwnerNode, SoeRequired}, @@ -607,5 +614,45 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({ {sfLoanScale, SoeDefault}, })) +/** A ledger object representing a contract source. + \sa keylet::contractSource + */ +LEDGER_ENTRY(ltCONTRACT_SOURCE, 0x0085, ContractSource, contract_source, ({ + {sfPreviousTxnID, SoeRequired}, + {sfPreviousTxnLgrSeq, SoeRequired}, + {sfContractHash, SoeRequired}, + {sfContractCode, SoeRequired}, + {sfFunctions, SoeRequired}, + {sfInstanceParameters, SoeOptional}, + {sfReferenceCount, SoeRequired}, +})) + +/** A ledger object representing a contract. + \sa keylet::contract + */ +LEDGER_ENTRY(ltCONTRACT, 0x0086, Contract, contract, ({ + {sfPreviousTxnID, SoeRequired}, + {sfPreviousTxnLgrSeq, SoeRequired}, + {sfSequence, SoeRequired}, + {sfOwnerNode, SoeRequired}, + {sfOwner, SoeRequired}, + {sfContractAccount, SoeRequired}, + {sfContractHash, SoeRequired}, + {sfInstanceParameterValues, SoeOptional}, + {sfURI, SoeOptional}, +})) + +/** A ledger object representing a contract data. + \sa keylet::contractData + */ +LEDGER_ENTRY(ltCONTRACT_DATA, 0x0087, ContractData, contract_data, ({ + {sfPreviousTxnID, SoeRequired}, + {sfPreviousTxnLgrSeq, SoeRequired}, + {sfOwnerNode, SoeRequired}, + {sfOwner, SoeRequired}, + {sfContractAccount, SoeRequired}, + {sfContractJson, SoeRequired}, +})) + #undef EXPAND #undef LEDGER_ENTRY_DUPLICATE diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index 01bb4fc4805..0664a10008e 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -22,9 +22,10 @@ TYPED_SFIELD(sfAssetScale, UINT8, 5) // 8-bit integers (uncommon) TYPED_SFIELD(sfTickSize, UINT8, 16) TYPED_SFIELD(sfUNLModifyDisabling, UINT8, 17) -TYPED_SFIELD(sfHookResult, UINT8, 18) +// 18 unused TYPED_SFIELD(sfWasLockingChainSend, UINT8, 19) TYPED_SFIELD(sfWithdrawalPolicy, UINT8, 20) +TYPED_SFIELD(sfContractResult, UINT8, 21) // 16-bit integers (common) TYPED_SFIELD(sfLedgerEntryType, UINT16, 1, SField::kSmdNever) @@ -36,10 +37,7 @@ TYPED_SFIELD(sfDiscountedFee, UINT16, 6) // 16-bit integers (uncommon) TYPED_SFIELD(sfVersion, UINT16, 16) -TYPED_SFIELD(sfHookStateChangeCount, UINT16, 17) -TYPED_SFIELD(sfHookEmitCount, UINT16, 18) -TYPED_SFIELD(sfHookExecutionIndex, UINT16, 19) -TYPED_SFIELD(sfHookApiVersion, UINT16, 20) +// 17 to 20 unused TYPED_SFIELD(sfLedgerFixType, UINT16, 21) TYPED_SFIELD(sfManagementFeeRate, UINT16, 22) // 1/10 basis points (bips) @@ -90,9 +88,7 @@ TYPED_SFIELD(sfTicketSequence, UINT32, 41) TYPED_SFIELD(sfNFTokenTaxon, UINT32, 42) TYPED_SFIELD(sfMintedNFTokens, UINT32, 43) TYPED_SFIELD(sfBurnedNFTokens, UINT32, 44) -TYPED_SFIELD(sfHookStateCount, UINT32, 45) -TYPED_SFIELD(sfEmitGeneration, UINT32, 46) -// 47 reserved for Hooks +// 45 to 47 unused TYPED_SFIELD(sfVoteWeight, UINT32, 48) TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50) TYPED_SFIELD(sfOracleDocumentID, UINT32, 51) @@ -113,6 +109,12 @@ TYPED_SFIELD(sfInterestRate, UINT32, 65) // 1/10 basis points (bi TYPED_SFIELD(sfLateInterestRate, UINT32, 66) // 1/10 basis points (bips) TYPED_SFIELD(sfCloseInterestRate, UINT32, 67) // 1/10 basis points (bips) TYPED_SFIELD(sfOverpaymentInterestRate, UINT32, 68) // 1/10 basis points (bips) +TYPED_SFIELD(sfExtensionComputeLimit, UINT32, 69) +TYPED_SFIELD(sfExtensionSizeLimit, UINT32, 70) +TYPED_SFIELD(sfGasPrice, UINT32, 71) +TYPED_SFIELD(sfComputationAllowance, UINT32, 72) +TYPED_SFIELD(sfGasUsed, UINT32, 73) +TYPED_SFIELD(sfParameterFlag, UINT32, 74) // 64-bit integers (common) TYPED_SFIELD(sfIndexNext, UINT64, 1) @@ -130,9 +132,7 @@ TYPED_SFIELD(sfNFTokenOfferNode, UINT64, 12) TYPED_SFIELD(sfEmitBurden, UINT64, 13) // 64-bit integers (uncommon) -TYPED_SFIELD(sfHookOn, UINT64, 16) -TYPED_SFIELD(sfHookInstructionCount, UINT64, 17) -TYPED_SFIELD(sfHookReturnCode, UINT64, 18) +// 16 to 18 unused TYPED_SFIELD(sfReferenceCount, UINT64, 19) TYPED_SFIELD(sfXChainClaimID, UINT64, 20) TYPED_SFIELD(sfXChainAccountCreateCount, UINT64, 21) @@ -194,10 +194,7 @@ TYPED_SFIELD(sfPreviousPageMin, UINT256, 26) TYPED_SFIELD(sfNextPageMin, UINT256, 27) TYPED_SFIELD(sfNFTokenBuyOffer, UINT256, 28) TYPED_SFIELD(sfNFTokenSellOffer, UINT256, 29) -TYPED_SFIELD(sfHookStateKey, UINT256, 30) -TYPED_SFIELD(sfHookHash, UINT256, 31) -TYPED_SFIELD(sfHookNamespace, UINT256, 32) -TYPED_SFIELD(sfHookSetTxnID, UINT256, 33) +// 30 to 33 unused TYPED_SFIELD(sfDomainID, UINT256, 34) TYPED_SFIELD(sfVaultID, UINT256, 35, SField::kSmdPseudoAccount | SField::kSmdDefault) @@ -206,6 +203,9 @@ TYPED_SFIELD(sfLoanBrokerID, UINT256, 37, SField::kSmdPseudoAccount | SField::kSmdDefault) TYPED_SFIELD(sfLoanID, UINT256, 38) TYPED_SFIELD(sfReferenceHolding, UINT256, 39) +TYPED_SFIELD(sfContractHash, UINT256, 40) +TYPED_SFIELD(sfContractID, UINT256, 41, + SField::kSmdPseudoAccount | SField::kSmdDefault) // number (common) TYPED_SFIELD(sfNumber, NUMBER, 1) @@ -226,8 +226,9 @@ TYPED_SFIELD(sfTotalValueOutstanding, NUMBER, 15, SField::kSmdNeedsAsset TYPED_SFIELD(sfPeriodicPayment, NUMBER, 16) TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17, SField::kSmdNeedsAsset | SField::kSmdDefault) -// int32 +// 32-bit signed (common) TYPED_SFIELD(sfLoanScale, INT32, 1) +TYPED_SFIELD(sfWasmReturnCode, INT32, 2) // currency amount (common) TYPED_SFIELD(sfAmount, AMOUNT, 1) @@ -249,15 +250,13 @@ TYPED_SFIELD(sfMinimumOffer, AMOUNT, 16) TYPED_SFIELD(sfRippleEscrow, AMOUNT, 17) TYPED_SFIELD(sfDeliveredAmount, AMOUNT, 18) TYPED_SFIELD(sfNFTokenBrokerFee, AMOUNT, 19) - -// Reserve 20 & 21 for Hooks. - +// 20 to 21 unused // currency amount (fees) TYPED_SFIELD(sfBaseFeeDrops, AMOUNT, 22) TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23) TYPED_SFIELD(sfReserveIncrementDrops, AMOUNT, 24) -// currency amount (AMM) +// currency amount (more) TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25) TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26) TYPED_SFIELD(sfEPrice, AMOUNT, 27) @@ -289,16 +288,16 @@ TYPED_SFIELD(sfMasterSignature, VL, 18, SField::kSmdDefault, SFi TYPED_SFIELD(sfUNLModifyValidator, VL, 19) TYPED_SFIELD(sfValidatorToDisable, VL, 20) TYPED_SFIELD(sfValidatorToReEnable, VL, 21) -TYPED_SFIELD(sfHookStateData, VL, 22) -TYPED_SFIELD(sfHookReturnString, VL, 23) -TYPED_SFIELD(sfHookParameterName, VL, 24) -TYPED_SFIELD(sfHookParameterValue, VL, 25) +// 22 to 25 unused TYPED_SFIELD(sfDIDDocument, VL, 26) TYPED_SFIELD(sfData, VL, 27) TYPED_SFIELD(sfAssetClass, VL, 28) TYPED_SFIELD(sfProvider, VL, 29) TYPED_SFIELD(sfMPTokenMetadata, VL, 30) TYPED_SFIELD(sfCredentialType, VL, 31) +TYPED_SFIELD(sfFinishFunction, VL, 32) +TYPED_SFIELD(sfContractCode, VL, 33) +TYPED_SFIELD(sfFunctionName, VL, 34) // account (common) TYPED_SFIELD(sfAccount, ACCOUNT, 1) @@ -315,7 +314,7 @@ TYPED_SFIELD(sfHolder, ACCOUNT, 11) TYPED_SFIELD(sfDelegate, ACCOUNT, 12) // account (uncommon) -TYPED_SFIELD(sfHookAccount, ACCOUNT, 16) +// 16 unused TYPED_SFIELD(sfOtherChainSource, ACCOUNT, 18) TYPED_SFIELD(sfOtherChainDestination, ACCOUNT, 19) TYPED_SFIELD(sfAttestationSignerAccount, ACCOUNT, 20) @@ -325,6 +324,7 @@ TYPED_SFIELD(sfIssuingChainDoor, ACCOUNT, 23) TYPED_SFIELD(sfSubject, ACCOUNT, 24) TYPED_SFIELD(sfBorrower, ACCOUNT, 25) TYPED_SFIELD(sfCounterparty, ACCOUNT, 26) +TYPED_SFIELD(sfContractAccount, ACCOUNT, 27) // vector of 256-bit TYPED_SFIELD(sfIndexes, VECTOR256, 1, SField::kSmdNever) @@ -363,7 +363,7 @@ UNTYPED_SFIELD(sfMemo, OBJECT, 10) UNTYPED_SFIELD(sfSignerEntry, OBJECT, 11) UNTYPED_SFIELD(sfNFToken, OBJECT, 12) UNTYPED_SFIELD(sfEmitDetails, OBJECT, 13) -UNTYPED_SFIELD(sfHook, OBJECT, 14) +// 14 unused UNTYPED_SFIELD(sfPermission, OBJECT, 15) // inner object (uncommon) @@ -371,11 +371,7 @@ UNTYPED_SFIELD(sfSigner, OBJECT, 16) // 17 unused UNTYPED_SFIELD(sfMajority, OBJECT, 18) UNTYPED_SFIELD(sfDisabledValidator, OBJECT, 19) -UNTYPED_SFIELD(sfEmittedTxn, OBJECT, 20) -UNTYPED_SFIELD(sfHookExecution, OBJECT, 21) -UNTYPED_SFIELD(sfHookDefinition, OBJECT, 22) -UNTYPED_SFIELD(sfHookParameter, OBJECT, 23) -UNTYPED_SFIELD(sfHookGrant, OBJECT, 24) +// 20 to 24 unused UNTYPED_SFIELD(sfVoteEntry, OBJECT, 25) UNTYPED_SFIELD(sfAuctionSlot, OBJECT, 26) UNTYPED_SFIELD(sfAuthAccount, OBJECT, 27) @@ -389,6 +385,10 @@ UNTYPED_SFIELD(sfRawTransaction, OBJECT, 34) UNTYPED_SFIELD(sfBatchSigner, OBJECT, 35) UNTYPED_SFIELD(sfBook, OBJECT, 36) UNTYPED_SFIELD(sfCounterpartySignature, OBJECT, 37, SField::kSmdDefault, SField::kNotSigning) +UNTYPED_SFIELD(sfFunction, OBJECT, 38) +UNTYPED_SFIELD(sfInstanceParameter, OBJECT, 39) +UNTYPED_SFIELD(sfInstanceParameterValue, OBJECT, 40) +UNTYPED_SFIELD(sfParameter, OBJECT, 41) // array of objects (common) // ARRAY/1 is reserved for end of array @@ -402,16 +402,14 @@ UNTYPED_SFIELD(sfSufficient, ARRAY, 7) UNTYPED_SFIELD(sfAffectedNodes, ARRAY, 8) UNTYPED_SFIELD(sfMemos, ARRAY, 9) UNTYPED_SFIELD(sfNFTokens, ARRAY, 10) -UNTYPED_SFIELD(sfHooks, ARRAY, 11) +// 11 unused UNTYPED_SFIELD(sfVoteSlots, ARRAY, 12) UNTYPED_SFIELD(sfAdditionalBooks, ARRAY, 13) // array of objects (uncommon) UNTYPED_SFIELD(sfMajorities, ARRAY, 16) UNTYPED_SFIELD(sfDisabledValidators, ARRAY, 17) -UNTYPED_SFIELD(sfHookExecutions, ARRAY, 18) -UNTYPED_SFIELD(sfHookParameters, ARRAY, 19) -UNTYPED_SFIELD(sfHookGrants, ARRAY, 20) +// 18 to 20 unused UNTYPED_SFIELD(sfXChainClaimAttestations, ARRAY, 21) UNTYPED_SFIELD(sfXChainCreateAccountAttestations, ARRAY, 22) // 23 unused @@ -423,3 +421,16 @@ UNTYPED_SFIELD(sfAcceptedCredentials, ARRAY, 28) UNTYPED_SFIELD(sfPermissions, ARRAY, 29) UNTYPED_SFIELD(sfRawTransactions, ARRAY, 30) UNTYPED_SFIELD(sfBatchSigners, ARRAY, 31, SField::kSmdDefault, SField::kNotSigning) +UNTYPED_SFIELD(sfFunctions, ARRAY, 32) +UNTYPED_SFIELD(sfInstanceParameters, ARRAY, 33) +UNTYPED_SFIELD(sfInstanceParameterValues, ARRAY, 34) +UNTYPED_SFIELD(sfParameters, ARRAY, 35) + +// data +TYPED_SFIELD(sfParameterValue, DATA, 1, SField::kSmdDefault) + +// data type +TYPED_SFIELD(sfParameterType, DATATYPE, 1) + +// json +TYPED_SFIELD(sfContractJson, JSON, 1) diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index 450e2558cce..48ec5a9fe8d 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -3,7 +3,7 @@ #endif /** - * TRANSACTION(tag, value, name, delegable, amendments, privileges, fields) + * TRANSACTION(tag, value, name, delegable, amendments, privileges, emitable, fields) * * To ease maintenance, you may replace any unneeded values with "..." * e.g. #define TRANSACTION(tag, value, name, ...) @@ -28,6 +28,7 @@ TRANSACTION(ttPAYMENT, 0, Payment, Delegation::Delegable, uint256{}, CreateAcct | MayCreateMpt, + Emittance::emitable, ({ {sfDestination, SoeRequired}, {sfAmount, SoeRequired, SoeMptSupported}, @@ -48,12 +49,15 @@ TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate, Delegation::Delegable, uint256{}, NoPriv, + Emittance::emitable, ({ {sfDestination, SoeRequired}, {sfAmount, SoeRequired, SoeMptSupported}, {sfCondition, SoeOptional}, {sfCancelAfter, SoeOptional}, {sfFinishAfter, SoeOptional}, + {sfFinishFunction, SoeOptional}, + {sfData, SoeOptional}, {sfDestinationTag, SoeOptional}, })) @@ -65,12 +69,14 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish, Delegation::Delegable, uint256{}, NoPriv, + Emittance::emitable, ({ {sfOwner, SoeRequired}, {sfOfferSequence, SoeRequired}, {sfFulfillment, SoeOptional}, {sfCondition, SoeOptional}, {sfCredentialIDs, SoeOptional}, + {sfComputationAllowance, SoeOptional}, })) @@ -82,6 +88,7 @@ TRANSACTION(ttACCOUNT_SET, 3, AccountSet, Delegation::NotDelegable, uint256{}, NoPriv, + Emittance::emitable, ({ {sfEmailHash, SoeOptional}, {sfWalletLocator, SoeOptional}, @@ -103,6 +110,7 @@ TRANSACTION(ttESCROW_CANCEL, 4, EscrowCancel, Delegation::Delegable, uint256{}, NoPriv, + Emittance::emitable, ({ {sfOwner, SoeRequired}, {sfOfferSequence, SoeRequired}, @@ -116,6 +124,7 @@ TRANSACTION(ttREGULAR_KEY_SET, 5, SetRegularKey, Delegation::NotDelegable, uint256{}, NoPriv, + Emittance::notEmitable, ({ {sfRegularKey, SoeOptional}, })) @@ -130,6 +139,7 @@ TRANSACTION(ttOFFER_CREATE, 7, OfferCreate, Delegation::Delegable, uint256{}, MayCreateMpt, + Emittance::emitable, ({ {sfTakerPays, SoeRequired, SoeMptSupported}, {sfTakerGets, SoeRequired, SoeMptSupported}, @@ -146,6 +156,7 @@ TRANSACTION(ttOFFER_CANCEL, 8, OfferCancel, Delegation::Delegable, uint256{}, NoPriv, + Emittance::emitable, ({ {sfOfferSequence, SoeRequired}, })) @@ -160,6 +171,7 @@ TRANSACTION(ttTICKET_CREATE, 10, TicketCreate, Delegation::Delegable, uint256{}, NoPriv, + Emittance::emitable, ({ {sfTicketCount, SoeRequired}, })) @@ -176,6 +188,7 @@ TRANSACTION(ttSIGNER_LIST_SET, 12, SignerListSet, Delegation::NotDelegable, uint256{}, NoPriv, + Emittance::notEmitable, ({ {sfSignerQuorum, SoeRequired}, {sfSignerEntries, SoeOptional}, @@ -189,6 +202,7 @@ TRANSACTION(ttPAYCHAN_CREATE, 13, PaymentChannelCreate, Delegation::Delegable, uint256{}, NoPriv, + Emittance::emitable, ({ {sfDestination, SoeRequired}, {sfAmount, SoeRequired}, @@ -206,6 +220,7 @@ TRANSACTION(ttPAYCHAN_FUND, 14, PaymentChannelFund, Delegation::Delegable, uint256{}, NoPriv, + Emittance::emitable, ({ {sfChannel, SoeRequired}, {sfAmount, SoeRequired}, @@ -220,6 +235,7 @@ TRANSACTION(ttPAYCHAN_CLAIM, 15, PaymentChannelClaim, Delegation::Delegable, uint256{}, NoPriv, + Emittance::emitable, ({ {sfChannel, SoeRequired}, {sfAmount, SoeOptional}, @@ -237,6 +253,7 @@ TRANSACTION(ttCHECK_CREATE, 16, CheckCreate, Delegation::Delegable, uint256{}, NoPriv, + Emittance::emitable, ({ {sfDestination, SoeRequired}, {sfSendMax, SoeRequired, SoeMptSupported}, @@ -253,6 +270,7 @@ TRANSACTION(ttCHECK_CASH, 17, CheckCash, Delegation::Delegable, uint256{}, MayCreateMpt, + Emittance::emitable, ({ {sfCheckID, SoeRequired}, {sfAmount, SoeOptional, SoeMptSupported}, @@ -267,6 +285,7 @@ TRANSACTION(ttCHECK_CANCEL, 18, CheckCancel, Delegation::Delegable, uint256{}, NoPriv, + Emittance::emitable, ({ {sfCheckID, SoeRequired}, })) @@ -279,6 +298,7 @@ TRANSACTION(ttDEPOSIT_PREAUTH, 19, DepositPreauth, Delegation::Delegable, uint256{}, NoPriv, + Emittance::notEmitable, ({ {sfAuthorize, SoeOptional}, {sfUnauthorize, SoeOptional}, @@ -294,6 +314,7 @@ TRANSACTION(ttTRUST_SET, 20, TrustSet, Delegation::Delegable, uint256{}, NoPriv, + Emittance::emitable, ({ {sfLimitAmount, SoeOptional}, {sfQualityIn, SoeOptional}, @@ -308,6 +329,7 @@ TRANSACTION(ttACCOUNT_DELETE, 21, AccountDelete, Delegation::NotDelegable, uint256{}, MustDeleteAcct, + Emittance::notEmitable, ({ {sfDestination, SoeRequired}, {sfDestinationTag, SoeOptional}, @@ -324,6 +346,7 @@ TRANSACTION(ttNFTOKEN_MINT, 25, NFTokenMint, Delegation::Delegable, uint256{}, ChangeNftCounts, + Emittance::emitable, ({ {sfNFTokenTaxon, SoeRequired}, {sfTransferFee, SoeOptional}, @@ -342,6 +365,7 @@ TRANSACTION(ttNFTOKEN_BURN, 26, NFTokenBurn, Delegation::Delegable, uint256{}, ChangeNftCounts, + Emittance::emitable, ({ {sfNFTokenID, SoeRequired}, {sfOwner, SoeOptional}, @@ -355,6 +379,7 @@ TRANSACTION(ttNFTOKEN_CREATE_OFFER, 27, NFTokenCreateOffer, Delegation::Delegable, uint256{}, NoPriv, + Emittance::emitable, ({ {sfNFTokenID, SoeRequired}, {sfAmount, SoeRequired}, @@ -371,6 +396,7 @@ TRANSACTION(ttNFTOKEN_CANCEL_OFFER, 28, NFTokenCancelOffer, Delegation::Delegable, uint256{}, NoPriv, + Emittance::emitable, ({ {sfNFTokenOffers, SoeRequired}, })) @@ -383,6 +409,7 @@ TRANSACTION(ttNFTOKEN_ACCEPT_OFFER, 29, NFTokenAcceptOffer, Delegation::Delegable, uint256{}, NoPriv, + Emittance::emitable, ({ {sfNFTokenBuyOffer, SoeOptional}, {sfNFTokenSellOffer, SoeOptional}, @@ -397,6 +424,7 @@ TRANSACTION(ttCLAWBACK, 30, Clawback, Delegation::Delegable, featureClawback, NoPriv, + Emittance::emitable, ({ {sfAmount, SoeRequired, SoeMptSupported}, {sfHolder, SoeOptional}, @@ -410,6 +438,7 @@ TRANSACTION(ttAMM_CLAWBACK, 31, AMMClawback, Delegation::Delegable, featureAMMClawback, MayDeleteAcct | OverrideFreeze | MayAuthorizeMpt, + Emittance::emitable, ({ {sfHolder, SoeRequired}, {sfAsset, SoeRequired, SoeMptSupported}, @@ -425,6 +454,7 @@ TRANSACTION(ttAMM_CREATE, 35, AMMCreate, Delegation::Delegable, featureAMM, CreatePseudoAcct | MayCreateMpt, + Emittance::emitable, ({ {sfAmount, SoeRequired, SoeMptSupported}, {sfAmount2, SoeRequired, SoeMptSupported}, @@ -439,6 +469,7 @@ TRANSACTION(ttAMM_DEPOSIT, 36, AMMDeposit, Delegation::Delegable, featureAMM, NoPriv, + Emittance::emitable, ({ {sfAsset, SoeRequired, SoeMptSupported}, {sfAsset2, SoeRequired, SoeMptSupported}, @@ -457,6 +488,7 @@ TRANSACTION(ttAMM_WITHDRAW, 37, AMMWithdraw, Delegation::Delegable, featureAMM, MayDeleteAcct | MayAuthorizeMpt, + Emittance::emitable, ({ {sfAsset, SoeRequired, SoeMptSupported}, {sfAsset2, SoeRequired, SoeMptSupported}, @@ -474,6 +506,7 @@ TRANSACTION(ttAMM_VOTE, 38, AMMVote, Delegation::Delegable, featureAMM, NoPriv, + Emittance::emitable, ({ {sfAsset, SoeRequired, SoeMptSupported}, {sfAsset2, SoeRequired, SoeMptSupported}, @@ -488,6 +521,7 @@ TRANSACTION(ttAMM_BID, 39, AMMBid, Delegation::Delegable, featureAMM, NoPriv, + Emittance::emitable, ({ {sfAsset, SoeRequired, SoeMptSupported}, {sfAsset2, SoeRequired, SoeMptSupported}, @@ -504,6 +538,7 @@ TRANSACTION(ttAMM_DELETE, 40, AMMDelete, Delegation::Delegable, featureAMM, MustDeleteAcct | MayDeleteMpt, + Emittance::emitable, ({ {sfAsset, SoeRequired, SoeMptSupported}, {sfAsset2, SoeRequired, SoeMptSupported}, @@ -517,6 +552,7 @@ TRANSACTION(ttXCHAIN_CREATE_CLAIM_ID, 41, XChainCreateClaimID, Delegation::Delegable, featureXChainBridge, NoPriv, + Emittance::emitable, ({ {sfXChainBridge, SoeRequired}, {sfSignatureReward, SoeRequired}, @@ -528,6 +564,7 @@ TRANSACTION(ttXCHAIN_COMMIT, 42, XChainCommit, Delegation::Delegable, featureXChainBridge, NoPriv, + Emittance::emitable, ({ {sfXChainBridge, SoeRequired}, {sfXChainClaimID, SoeRequired}, @@ -540,6 +577,7 @@ TRANSACTION(ttXCHAIN_CLAIM, 43, XChainClaim, Delegation::Delegable, featureXChainBridge, NoPriv, + Emittance::emitable, ({ {sfXChainBridge, SoeRequired}, {sfXChainClaimID, SoeRequired}, @@ -553,6 +591,7 @@ TRANSACTION(ttXCHAIN_ACCOUNT_CREATE_COMMIT, 44, XChainAccountCreateCommit, Delegation::Delegable, featureXChainBridge, NoPriv, + Emittance::emitable, ({ {sfXChainBridge, SoeRequired}, {sfDestination, SoeRequired}, @@ -565,6 +604,7 @@ TRANSACTION(ttXCHAIN_ADD_CLAIM_ATTESTATION, 45, XChainAddClaimAttestation, Delegation::Delegable, featureXChainBridge, CreateAcct, + Emittance::emitable, ({ {sfXChainBridge, SoeRequired}, @@ -586,6 +626,7 @@ TRANSACTION(ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION, 46, Delegation::Delegable, featureXChainBridge, CreateAcct, + Emittance::emitable, ({ {sfXChainBridge, SoeRequired}, @@ -607,6 +648,7 @@ TRANSACTION(ttXCHAIN_MODIFY_BRIDGE, 47, XChainModifyBridge, Delegation::Delegable, featureXChainBridge, NoPriv, + Emittance::emitable, ({ {sfXChainBridge, SoeRequired}, {sfSignatureReward, SoeOptional}, @@ -618,6 +660,7 @@ TRANSACTION(ttXCHAIN_CREATE_BRIDGE, 48, XChainCreateBridge, Delegation::Delegable, featureXChainBridge, NoPriv, + Emittance::emitable, ({ {sfXChainBridge, SoeRequired}, {sfSignatureReward, SoeRequired}, @@ -632,6 +675,7 @@ TRANSACTION(ttDID_SET, 49, DIDSet, Delegation::Delegable, featureDID, NoPriv, + Emittance::emitable, ({ {sfDIDDocument, SoeOptional}, {sfURI, SoeOptional}, @@ -646,6 +690,7 @@ TRANSACTION(ttDID_DELETE, 50, DIDDelete, Delegation::Delegable, featureDID, NoPriv, + Emittance::emitable, ({})) /** This transaction type creates an Oracle instance */ @@ -656,6 +701,7 @@ TRANSACTION(ttORACLE_SET, 51, OracleSet, Delegation::Delegable, featurePriceOracle, NoPriv, + Emittance::emitable, ({ {sfOracleDocumentID, SoeRequired}, {sfProvider, SoeOptional}, @@ -673,6 +719,7 @@ TRANSACTION(ttORACLE_DELETE, 52, OracleDelete, Delegation::Delegable, featurePriceOracle, NoPriv, + Emittance::emitable, ({ {sfOracleDocumentID, SoeRequired}, })) @@ -685,6 +732,7 @@ TRANSACTION(ttLEDGER_STATE_FIX, 53, LedgerStateFix, Delegation::Delegable, fixNFTokenPageLinks, NoPriv, + Emittance::emitable, ({ {sfLedgerFixType, SoeRequired}, {sfOwner, SoeOptional}, @@ -699,6 +747,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, Delegation::Delegable, featureMPTokensV1, CreateMptIssuance, + Emittance::emitable, ({ {sfAssetScale, SoeOptional}, {sfTransferFee, SoeOptional}, @@ -716,6 +765,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, Delegation::Delegable, featureMPTokensV1, DestroyMptIssuance, + Emittance::emitable, ({ {sfMPTokenIssuanceID, SoeRequired}, })) @@ -728,6 +778,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, Delegation::Delegable, featureMPTokensV1, NoPriv, + Emittance::emitable, ({ {sfMPTokenIssuanceID, SoeRequired}, {sfHolder, SoeOptional}, @@ -745,6 +796,7 @@ TRANSACTION(ttMPTOKEN_AUTHORIZE, 57, MPTokenAuthorize, Delegation::Delegable, featureMPTokensV1, MustAuthorizeMpt, + Emittance::emitable, ({ {sfMPTokenIssuanceID, SoeRequired}, {sfHolder, SoeOptional}, @@ -758,6 +810,7 @@ TRANSACTION(ttCREDENTIAL_CREATE, 58, CredentialCreate, Delegation::Delegable, featureCredentials, NoPriv, + Emittance::emitable, ({ {sfSubject, SoeRequired}, {sfCredentialType, SoeRequired}, @@ -773,6 +826,7 @@ TRANSACTION(ttCREDENTIAL_ACCEPT, 59, CredentialAccept, Delegation::Delegable, featureCredentials, NoPriv, + Emittance::emitable, ({ {sfIssuer, SoeRequired}, {sfCredentialType, SoeRequired}, @@ -786,6 +840,7 @@ TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, Delegation::Delegable, featureCredentials, NoPriv, + Emittance::emitable, ({ {sfSubject, SoeOptional}, {sfIssuer, SoeOptional}, @@ -800,6 +855,7 @@ TRANSACTION(ttNFTOKEN_MODIFY, 61, NFTokenModify, Delegation::Delegable, featureDynamicNFT, NoPriv, + Emittance::emitable, ({ {sfNFTokenID, SoeRequired}, {sfOwner, SoeOptional}, @@ -814,6 +870,7 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_SET, 62, PermissionedDomainSet, Delegation::Delegable, featurePermissionedDomains, NoPriv, + Emittance::emitable, ({ {sfDomainID, SoeOptional}, {sfAcceptedCredentials, SoeRequired}, @@ -827,6 +884,7 @@ TRANSACTION(ttPERMISSIONED_DOMAIN_DELETE, 63, PermissionedDomainDelete, Delegation::Delegable, featurePermissionedDomains, NoPriv, + Emittance::emitable, ({ {sfDomainID, SoeRequired}, })) @@ -839,6 +897,7 @@ TRANSACTION(ttDELEGATE_SET, 64, DelegateSet, Delegation::NotDelegable, featurePermissionDelegationV1_1, NoPriv, + Emittance::notEmitable, ({ {sfAuthorize, SoeRequired}, {sfPermissions, SoeRequired}, @@ -852,6 +911,7 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, Delegation::NotDelegable, featureSingleAssetVault, CreatePseudoAcct | CreateMptIssuance | MustModifyVault, + Emittance::emitable, ({ {sfAsset, SoeRequired, SoeMptSupported}, {sfAssetsMaximum, SoeOptional}, @@ -870,6 +930,7 @@ TRANSACTION(ttVAULT_SET, 66, VaultSet, Delegation::NotDelegable, featureSingleAssetVault, MustModifyVault, + Emittance::emitable, ({ {sfVaultID, SoeRequired}, {sfAssetsMaximum, SoeOptional}, @@ -885,6 +946,7 @@ TRANSACTION(ttVAULT_DELETE, 67, VaultDelete, Delegation::NotDelegable, featureSingleAssetVault, MustDeleteAcct | DestroyMptIssuance | MustModifyVault, + Emittance::emitable, ({ {sfVaultID, SoeRequired}, })) @@ -897,6 +959,7 @@ TRANSACTION(ttVAULT_DEPOSIT, 68, VaultDeposit, Delegation::NotDelegable, featureSingleAssetVault, MayAuthorizeMpt | MustModifyVault, + Emittance::emitable, ({ {sfVaultID, SoeRequired}, {sfAmount, SoeRequired, SoeMptSupported}, @@ -910,6 +973,7 @@ TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, Delegation::NotDelegable, featureSingleAssetVault, MayDeleteMpt | MayAuthorizeMpt | MustModifyVault, + Emittance::emitable, ({ {sfVaultID, SoeRequired}, {sfAmount, SoeRequired, SoeMptSupported}, @@ -925,6 +989,7 @@ TRANSACTION(ttVAULT_CLAWBACK, 70, VaultClawback, Delegation::NotDelegable, featureSingleAssetVault, MayDeleteMpt | MustModifyVault, + Emittance::emitable, ({ {sfVaultID, SoeRequired}, {sfHolder, SoeRequired}, @@ -939,6 +1004,7 @@ TRANSACTION(ttBATCH, 71, Batch, Delegation::NotDelegable, featureBatch, NoPriv, + Emittance::notEmitable, ({ {sfRawTransactions, SoeRequired}, {sfBatchSigners, SoeOptional}, @@ -953,7 +1019,9 @@ TRANSACTION(ttBATCH, 71, Batch, TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet, Delegation::NotDelegable, featureLendingProtocol, - CreatePseudoAcct | MayAuthorizeMpt, ({ + CreatePseudoAcct | MayAuthorizeMpt, + Emittance::emitable, + ({ {sfVaultID, SoeRequired}, {sfLoanBrokerID, SoeOptional}, {sfData, SoeOptional}, @@ -970,7 +1038,9 @@ TRANSACTION(ttLOAN_BROKER_SET, 74, LoanBrokerSet, TRANSACTION(ttLOAN_BROKER_DELETE, 75, LoanBrokerDelete, Delegation::NotDelegable, featureLendingProtocol, - MustDeleteAcct | MayAuthorizeMpt, ({ + MustDeleteAcct | MayAuthorizeMpt, + Emittance::emitable, + ({ {sfLoanBrokerID, SoeRequired}, })) @@ -981,7 +1051,9 @@ TRANSACTION(ttLOAN_BROKER_DELETE, 75, LoanBrokerDelete, TRANSACTION(ttLOAN_BROKER_COVER_DEPOSIT, 76, LoanBrokerCoverDeposit, Delegation::NotDelegable, featureLendingProtocol, - NoPriv, ({ + NoPriv, + Emittance::emitable, + ({ {sfLoanBrokerID, SoeRequired}, {sfAmount, SoeRequired, SoeMptSupported}, })) @@ -993,7 +1065,9 @@ TRANSACTION(ttLOAN_BROKER_COVER_DEPOSIT, 76, LoanBrokerCoverDeposit, TRANSACTION(ttLOAN_BROKER_COVER_WITHDRAW, 77, LoanBrokerCoverWithdraw, Delegation::NotDelegable, featureLendingProtocol, - MayAuthorizeMpt, ({ + MayAuthorizeMpt, + Emittance::emitable, + ({ {sfLoanBrokerID, SoeRequired}, {sfAmount, SoeRequired, SoeMptSupported}, {sfDestination, SoeOptional}, @@ -1008,7 +1082,9 @@ TRANSACTION(ttLOAN_BROKER_COVER_WITHDRAW, 77, LoanBrokerCoverWithdraw, TRANSACTION(ttLOAN_BROKER_COVER_CLAWBACK, 78, LoanBrokerCoverClawback, Delegation::NotDelegable, featureLendingProtocol, - NoPriv, ({ + NoPriv, + Emittance::emitable, + ({ {sfLoanBrokerID, SoeOptional}, {sfAmount, SoeOptional, SoeMptSupported}, })) @@ -1020,7 +1096,9 @@ TRANSACTION(ttLOAN_BROKER_COVER_CLAWBACK, 78, LoanBrokerCoverClawback, TRANSACTION(ttLOAN_SET, 80, LoanSet, Delegation::NotDelegable, featureLendingProtocol, - MayAuthorizeMpt | MustModifyVault, ({ + MayAuthorizeMpt | MustModifyVault, + Emittance::emitable, + ({ {sfLoanBrokerID, SoeRequired}, {sfData, SoeOptional}, {sfCounterparty, SoeOptional}, @@ -1047,7 +1125,9 @@ TRANSACTION(ttLOAN_SET, 80, LoanSet, TRANSACTION(ttLOAN_DELETE, 81, LoanDelete, Delegation::NotDelegable, featureLendingProtocol, - NoPriv, ({ + NoPriv, + Emittance::emitable, + ({ {sfLoanID, SoeRequired}, })) @@ -1061,7 +1141,9 @@ TRANSACTION(ttLOAN_MANAGE, 82, LoanManage, // All of the LoanManage options will modify the vault, but the // transaction can succeed without options, essentially making it // a noop. - MayModifyVault, ({ + MayModifyVault, + Emittance::emitable, + ({ {sfLoanID, SoeRequired}, })) @@ -1072,11 +1154,108 @@ TRANSACTION(ttLOAN_MANAGE, 82, LoanManage, TRANSACTION(ttLOAN_PAY, 84, LoanPay, Delegation::NotDelegable, featureLendingProtocol, - MayAuthorizeMpt | MustModifyVault, ({ + MayAuthorizeMpt | MustModifyVault, + Emittance::emitable, + ({ {sfLoanID, SoeRequired}, {sfAmount, SoeRequired, SoeMptSupported}, })) +/** This transaction type creates the smart contract. */ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttCONTRACT_CREATE, 85, ContractCreate, + Delegation::Delegable, + featureSmartContract, + CreatePseudoAcct, + Emittance::emitable, + ({ + {sfContractCode, SoeOptional}, + {sfContractHash, SoeOptional}, + {sfFunctions, SoeOptional}, + {sfInstanceParameters, SoeOptional}, + {sfInstanceParameterValues, SoeOptional}, + {sfURI, SoeOptional}, +})) + +/** This transaction type modifies the smart contract. */ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttCONTRACT_MODIFY, 86, ContractModify, + Delegation::Delegable, + featureSmartContract, + NoPriv, + Emittance::emitable, + ({ + {sfContractAccount, SoeOptional}, + {sfOwner, SoeOptional}, + {sfContractCode, SoeOptional}, + {sfContractHash, SoeOptional}, + {sfFunctions, SoeOptional}, + {sfInstanceParameters, SoeOptional}, + {sfInstanceParameterValues, SoeOptional}, + {sfURI, SoeOptional}, +})) + +/** This transaction type deletes the smart contract. */ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttCONTRACT_DELETE, 87, ContractDelete, + Delegation::Delegable, + featureSmartContract, + MustDeleteAcct, + Emittance::emitable, + ({ + {sfContractAccount, SoeRequired}, +})) + +/** This transaction type claws back funds from the contract. */ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttCONTRACT_CLAWBACK, 88, ContractClawback, + Delegation::Delegable, + featureSmartContract, + NoPriv, + Emittance::emitable, + ({ + {sfContractAccount, SoeOptional}, + {sfAmount, SoeRequired, SoeMptSupported}, +})) + +/** This transaction type deletes user data. */ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttCONTRACT_USER_DELETE, 89, ContractUserDelete, + Delegation::Delegable, + featureSmartContract, + NoPriv, + Emittance::notEmitable, + ({ + {sfContractAccount, SoeRequired}, + {sfComputationAllowance, SoeRequired}, +})) + +/** This transaction type calls the smart contract. */ +#if TRANSACTION_INCLUDE +# include +#endif +TRANSACTION(ttCONTRACT_CALL, 90, ContractCall, + Delegation::Delegable, + featureSmartContract, + NoPriv, + Emittance::notEmitable, + ({ + {sfContractAccount, SoeRequired}, + {sfFunctionName, SoeRequired}, + {sfParameters, SoeOptional}, + {sfComputationAllowance, SoeRequired}, +})) + /** This system-generated transaction type is used to update the status of the various amendments. For details, see: https://xrpl.org/amendments.html @@ -1088,6 +1267,7 @@ TRANSACTION(ttAMENDMENT, 100, EnableAmendment, Delegation::NotDelegable, uint256{}, NoPriv, + Emittance::notEmitable, ({ {sfLedgerSequence, SoeRequired}, {sfAmendment, SoeRequired}, @@ -1100,6 +1280,7 @@ TRANSACTION(ttFEE, 101, SetFee, Delegation::NotDelegable, uint256{}, NoPriv, + Emittance::notEmitable, ({ {sfLedgerSequence, SoeOptional}, // Old version uses raw numbers @@ -1111,6 +1292,10 @@ TRANSACTION(ttFEE, 101, SetFee, {sfBaseFeeDrops, SoeOptional}, {sfReserveBaseDrops, SoeOptional}, {sfReserveIncrementDrops, SoeOptional}, + // Smart Escrow fields + {sfExtensionComputeLimit, SoeOptional}, + {sfExtensionSizeLimit, SoeOptional}, + {sfGasPrice, SoeOptional}, })) /** This system-generated transaction type is used to update the network's negative UNL @@ -1121,6 +1306,7 @@ TRANSACTION(ttUNL_MODIFY, 102, UNLModify, Delegation::NotDelegable, uint256{}, NoPriv, + Emittance::notEmitable, ({ {sfUNLModifyDisabling, SoeRequired}, {sfLedgerSequence, SoeRequired}, diff --git a/include/xrpl/protocol/jss.h b/include/xrpl/protocol/jss.h index 8a2a1125427..912184c3896 100644 --- a/include/xrpl/protocol/jss.h +++ b/include/xrpl/protocol/jss.h @@ -186,6 +186,7 @@ JSS(common); // out: RPC server_definitions JSS(complete); // out: NetworkOPs, InboundLedger JSS(complete_ledgers); // out: NetworkOPs, PeerImp JSS(consensus); // out: NetworkOPs, LedgerConsensus +JSS(contract_account); // out: ContractInfo JSS(converge_time); // out: NetworkOPs JSS(converge_time_s); // out: NetworkOPs JSS(cookie); // out: NetworkOPs @@ -248,6 +249,9 @@ JSS(expected_date); // out: any (warnings) JSS(expected_date_UTC); // out: any (warnings) JSS(expected_ledger_size); // out: TxQ JSS(expiration); // out: AccountOffers, AccountChannels, ValidatorList, amm_info +JSS(extension_compute); // out: NetworkOPs +JSS(extension_size); // out: NetworkOPs +JSS(gas_price); // out: NetworkOPs JSS(fail_hard); // in: Sign, Submit JSS(failed); // out: InboundLedger JSS(feature); // in: Feature @@ -267,6 +271,8 @@ JSS(flags); // out: AccountOffers, NetworkOPs JSS(forward); // in: AccountTx JSS(freeze); // out: AccountLines JSS(freeze_peer); // out: AccountLines +JSS(function); // in: ContractInfo +JSS(functions); // out: ContractInfo JSS(deep_freeze); // out: AccountLines JSS(deep_freeze_peer); // out: AccountLines JSS(frozen_balances); // out: GatewayBalances @@ -550,6 +556,7 @@ JSS(size); // out: get_aggregate_price JSS(snapshot); // in: Subscribe JSS(source_account); // in: PathRequest, RipplePathFind JSS(source_amount); // in: PathRequest, RipplePathFind +JSS(source_code_uri); // out: ContractInfo JSS(source_currencies); // in: PathRequest, RipplePathFind JSS(source_tag); // out: AccountChannels JSS(stand_alone); // out: NetworkOPs @@ -647,6 +654,7 @@ JSS(url); // in/out: Subscribe, Unsubscribe JSS(url_password); // in: Subscribe JSS(url_username); // in: Subscribe JSS(urlgravatar); // +JSS(user_data); // out: ContractInfo JSS(username); // in: Subscribe JSS(validated); // out: NetworkOPs, RPCHelpers, AccountTx*, Tx JSS(validator_list_expires); // out: NetworkOps, ValidatorList diff --git a/include/xrpl/protocol/st.h b/include/xrpl/protocol/st.h index 61571196f2d..c2f0b1bae6b 100644 --- a/include/xrpl/protocol/st.h +++ b/include/xrpl/protocol/st.h @@ -7,7 +7,12 @@ #include #include #include +#include +#include +#include #include +#include +#include #include #include #include diff --git a/include/xrpl/protocol_autogen/ledger_entries/AccountRoot.h b/include/xrpl/protocol_autogen/ledger_entries/AccountRoot.h index e20259330d7..bda1b4c21dd 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/AccountRoot.h +++ b/include/xrpl/protocol_autogen/ledger_entries/AccountRoot.h @@ -518,6 +518,30 @@ class AccountRoot : public LedgerEntryBase { return this->sle_->isFieldPresent(sfLoanBrokerID); } + + /** + * @brief Get sfContractID (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getContractID() const + { + if (hasContractID()) + return this->sle_->at(sfContractID); + return std::nullopt; + } + + /** + * @brief Check if sfContractID is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasContractID() const + { + return this->sle_->isFieldPresent(sfContractID); + } }; /** @@ -819,6 +843,17 @@ class AccountRootBuilder : public LedgerEntryBuilderBase return *this; } + /** + * @brief Set sfContractID (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + AccountRootBuilder& + setContractID(std::decay_t const& value) + { + object_[sfContractID] = value; + return *this; + } + /** * @brief Build and return the completed AccountRoot wrapper. * @param index The ledger entry index. diff --git a/include/xrpl/protocol_autogen/ledger_entries/Contract.h b/include/xrpl/protocol_autogen/ledger_entries/Contract.h new file mode 100644 index 00000000000..5bea9655d6d --- /dev/null +++ b/include/xrpl/protocol_autogen/ledger_entries/Contract.h @@ -0,0 +1,334 @@ +// This file is auto-generated. Do not edit. +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::ledger_entries { + +class ContractBuilder; + +/** + * @brief Ledger Entry: Contract + * + * Type: ltCONTRACT (0x0086) + * RPC Name: contract + * + * Immutable wrapper around SLE providing type-safe field access. + * Use ContractBuilder to construct new ledger entries. + */ +class Contract : public LedgerEntryBase +{ +public: + static constexpr LedgerEntryType entryType = ltCONTRACT; + + /** + * @brief Construct a Contract ledger entry wrapper from an existing SLE object. + * @throws std::runtime_error if the ledger entry type doesn't match. + */ + explicit Contract(std::shared_ptr sle) + : LedgerEntryBase(std::move(sle)) + { + // Verify ledger entry type + if (sle_->getType() != entryType) + { + throw std::runtime_error("Invalid ledger entry type for Contract"); + } + } + + // Ledger entry-specific field getters + + /** + * @brief Get sfPreviousTxnID (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_UINT256::type::value_type + getPreviousTxnID() const + { + return this->sle_->at(sfPreviousTxnID); + } + + /** + * @brief Get sfPreviousTxnLgrSeq (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_UINT32::type::value_type + getPreviousTxnLgrSeq() const + { + return this->sle_->at(sfPreviousTxnLgrSeq); + } + + /** + * @brief Get sfSequence (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_UINT32::type::value_type + getSequence() const + { + return this->sle_->at(sfSequence); + } + + /** + * @brief Get sfOwnerNode (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_UINT64::type::value_type + getOwnerNode() const + { + return this->sle_->at(sfOwnerNode); + } + + /** + * @brief Get sfOwner (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_ACCOUNT::type::value_type + getOwner() const + { + return this->sle_->at(sfOwner); + } + + /** + * @brief Get sfContractAccount (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_ACCOUNT::type::value_type + getContractAccount() const + { + return this->sle_->at(sfContractAccount); + } + + /** + * @brief Get sfContractHash (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_UINT256::type::value_type + getContractHash() const + { + return this->sle_->at(sfContractHash); + } + + /** + * @brief Get sfInstanceParameterValues (soeOPTIONAL) + * @note This is an untyped field (unknown). + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + std::optional> + getInstanceParameterValues() const + { + if (this->sle_->isFieldPresent(sfInstanceParameterValues)) + return this->sle_->getFieldArray(sfInstanceParameterValues); + return std::nullopt; + } + + /** + * @brief Check if sfInstanceParameterValues is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasInstanceParameterValues() const + { + return this->sle_->isFieldPresent(sfInstanceParameterValues); + } + + /** + * @brief Get sfURI (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getURI() const + { + if (hasURI()) + return this->sle_->at(sfURI); + return std::nullopt; + } + + /** + * @brief Check if sfURI is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasURI() const + { + return this->sle_->isFieldPresent(sfURI); + } +}; + +/** + * @brief Builder for Contract ledger entries. + * + * Provides a fluent interface for constructing ledger entries with method chaining. + * Uses json::Value internally for flexible ledger entry construction. + * Inherits common field setters from LedgerEntryBuilderBase. + */ +class ContractBuilder : public LedgerEntryBuilderBase +{ +public: + /** + * @brief Construct a new ContractBuilder with required fields. + * @param previousTxnID The sfPreviousTxnID field value. + * @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value. + * @param sequence The sfSequence field value. + * @param ownerNode The sfOwnerNode field value. + * @param owner The sfOwner field value. + * @param contractAccount The sfContractAccount field value. + * @param contractHash The sfContractHash field value. + */ + ContractBuilder(std::decay_t const& previousTxnID,std::decay_t const& previousTxnLgrSeq,std::decay_t const& sequence,std::decay_t const& ownerNode,std::decay_t const& owner,std::decay_t const& contractAccount,std::decay_t const& contractHash) + : LedgerEntryBuilderBase(ltCONTRACT) + { + setPreviousTxnID(previousTxnID); + setPreviousTxnLgrSeq(previousTxnLgrSeq); + setSequence(sequence); + setOwnerNode(ownerNode); + setOwner(owner); + setContractAccount(contractAccount); + setContractHash(contractHash); + } + + /** + * @brief Construct a ContractBuilder from an existing SLE object. + * @param sle The existing ledger entry to copy from. + * @throws std::runtime_error if the ledger entry type doesn't match. + */ + ContractBuilder(std::shared_ptr sle) + { + if (sle->at(sfLedgerEntryType) != ltCONTRACT) + { + throw std::runtime_error("Invalid ledger entry type for Contract"); + } + object_ = *sle; + } + + /** @brief Ledger entry-specific field setters */ + + /** + * @brief Set sfPreviousTxnID (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractBuilder& + setPreviousTxnID(std::decay_t const& value) + { + object_[sfPreviousTxnID] = value; + return *this; + } + + /** + * @brief Set sfPreviousTxnLgrSeq (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractBuilder& + setPreviousTxnLgrSeq(std::decay_t const& value) + { + object_[sfPreviousTxnLgrSeq] = value; + return *this; + } + + /** + * @brief Set sfSequence (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractBuilder& + setSequence(std::decay_t const& value) + { + object_[sfSequence] = value; + return *this; + } + + /** + * @brief Set sfOwnerNode (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractBuilder& + setOwnerNode(std::decay_t const& value) + { + object_[sfOwnerNode] = value; + return *this; + } + + /** + * @brief Set sfOwner (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractBuilder& + setOwner(std::decay_t const& value) + { + object_[sfOwner] = value; + return *this; + } + + /** + * @brief Set sfContractAccount (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractBuilder& + setContractAccount(std::decay_t const& value) + { + object_[sfContractAccount] = value; + return *this; + } + + /** + * @brief Set sfContractHash (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractBuilder& + setContractHash(std::decay_t const& value) + { + object_[sfContractHash] = value; + return *this; + } + + /** + * @brief Set sfInstanceParameterValues (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractBuilder& + setInstanceParameterValues(STArray const& value) + { + object_.setFieldArray(sfInstanceParameterValues, value); + return *this; + } + + /** + * @brief Set sfURI (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractBuilder& + setURI(std::decay_t const& value) + { + object_[sfURI] = value; + return *this; + } + + /** + * @brief Build and return the completed Contract wrapper. + * @param index The ledger entry index. + * @return The constructed ledger entry wrapper. + */ + Contract + build(uint256 const& index) + { + return Contract{std::make_shared(std::move(object_), index)}; + } +}; + +} // namespace xrpl::ledger_entries diff --git a/include/xrpl/protocol_autogen/ledger_entries/ContractData.h b/include/xrpl/protocol_autogen/ledger_entries/ContractData.h new file mode 100644 index 00000000000..0011368e71e --- /dev/null +++ b/include/xrpl/protocol_autogen/ledger_entries/ContractData.h @@ -0,0 +1,239 @@ +// This file is auto-generated. Do not edit. +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::ledger_entries { + +class ContractDataBuilder; + +/** + * @brief Ledger Entry: ContractData + * + * Type: ltCONTRACT_DATA (0x0087) + * RPC Name: contract_data + * + * Immutable wrapper around SLE providing type-safe field access. + * Use ContractDataBuilder to construct new ledger entries. + */ +class ContractData : public LedgerEntryBase +{ +public: + static constexpr LedgerEntryType entryType = ltCONTRACT_DATA; + + /** + * @brief Construct a ContractData ledger entry wrapper from an existing SLE object. + * @throws std::runtime_error if the ledger entry type doesn't match. + */ + explicit ContractData(std::shared_ptr sle) + : LedgerEntryBase(std::move(sle)) + { + // Verify ledger entry type + if (sle_->getType() != entryType) + { + throw std::runtime_error("Invalid ledger entry type for ContractData"); + } + } + + // Ledger entry-specific field getters + + /** + * @brief Get sfPreviousTxnID (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_UINT256::type::value_type + getPreviousTxnID() const + { + return this->sle_->at(sfPreviousTxnID); + } + + /** + * @brief Get sfPreviousTxnLgrSeq (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_UINT32::type::value_type + getPreviousTxnLgrSeq() const + { + return this->sle_->at(sfPreviousTxnLgrSeq); + } + + /** + * @brief Get sfOwnerNode (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_UINT64::type::value_type + getOwnerNode() const + { + return this->sle_->at(sfOwnerNode); + } + + /** + * @brief Get sfOwner (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_ACCOUNT::type::value_type + getOwner() const + { + return this->sle_->at(sfOwner); + } + + /** + * @brief Get sfContractAccount (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_ACCOUNT::type::value_type + getContractAccount() const + { + return this->sle_->at(sfContractAccount); + } + + /** + * @brief Get sfContractJson (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_JSON::type::value_type + getContractJson() const + { + return this->sle_->at(sfContractJson); + } +}; + +/** + * @brief Builder for ContractData ledger entries. + * + * Provides a fluent interface for constructing ledger entries with method chaining. + * Uses json::Value internally for flexible ledger entry construction. + * Inherits common field setters from LedgerEntryBuilderBase. + */ +class ContractDataBuilder : public LedgerEntryBuilderBase +{ +public: + /** + * @brief Construct a new ContractDataBuilder with required fields. + * @param previousTxnID The sfPreviousTxnID field value. + * @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value. + * @param ownerNode The sfOwnerNode field value. + * @param owner The sfOwner field value. + * @param contractAccount The sfContractAccount field value. + * @param contractJson The sfContractJson field value. + */ + ContractDataBuilder(std::decay_t const& previousTxnID,std::decay_t const& previousTxnLgrSeq,std::decay_t const& ownerNode,std::decay_t const& owner,std::decay_t const& contractAccount,std::decay_t const& contractJson) + : LedgerEntryBuilderBase(ltCONTRACT_DATA) + { + setPreviousTxnID(previousTxnID); + setPreviousTxnLgrSeq(previousTxnLgrSeq); + setOwnerNode(ownerNode); + setOwner(owner); + setContractAccount(contractAccount); + setContractJson(contractJson); + } + + /** + * @brief Construct a ContractDataBuilder from an existing SLE object. + * @param sle The existing ledger entry to copy from. + * @throws std::runtime_error if the ledger entry type doesn't match. + */ + ContractDataBuilder(std::shared_ptr sle) + { + if (sle->at(sfLedgerEntryType) != ltCONTRACT_DATA) + { + throw std::runtime_error("Invalid ledger entry type for ContractData"); + } + object_ = *sle; + } + + /** @brief Ledger entry-specific field setters */ + + /** + * @brief Set sfPreviousTxnID (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractDataBuilder& + setPreviousTxnID(std::decay_t const& value) + { + object_[sfPreviousTxnID] = value; + return *this; + } + + /** + * @brief Set sfPreviousTxnLgrSeq (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractDataBuilder& + setPreviousTxnLgrSeq(std::decay_t const& value) + { + object_[sfPreviousTxnLgrSeq] = value; + return *this; + } + + /** + * @brief Set sfOwnerNode (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractDataBuilder& + setOwnerNode(std::decay_t const& value) + { + object_[sfOwnerNode] = value; + return *this; + } + + /** + * @brief Set sfOwner (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractDataBuilder& + setOwner(std::decay_t const& value) + { + object_[sfOwner] = value; + return *this; + } + + /** + * @brief Set sfContractAccount (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractDataBuilder& + setContractAccount(std::decay_t const& value) + { + object_[sfContractAccount] = value; + return *this; + } + + /** + * @brief Set sfContractJson (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractDataBuilder& + setContractJson(std::decay_t const& value) + { + object_[sfContractJson] = value; + return *this; + } + + /** + * @brief Build and return the completed ContractData wrapper. + * @param index The ledger entry index. + * @return The constructed ledger entry wrapper. + */ + ContractData + build(uint256 const& index) + { + return ContractData{std::make_shared(std::move(object_), index)}; + } +}; + +} // namespace xrpl::ledger_entries diff --git a/include/xrpl/protocol_autogen/ledger_entries/ContractSource.h b/include/xrpl/protocol_autogen/ledger_entries/ContractSource.h new file mode 100644 index 00000000000..411c5899f5c --- /dev/null +++ b/include/xrpl/protocol_autogen/ledger_entries/ContractSource.h @@ -0,0 +1,276 @@ +// This file is auto-generated. Do not edit. +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::ledger_entries { + +class ContractSourceBuilder; + +/** + * @brief Ledger Entry: ContractSource + * + * Type: ltCONTRACT_SOURCE (0x0085) + * RPC Name: contract_source + * + * Immutable wrapper around SLE providing type-safe field access. + * Use ContractSourceBuilder to construct new ledger entries. + */ +class ContractSource : public LedgerEntryBase +{ +public: + static constexpr LedgerEntryType entryType = ltCONTRACT_SOURCE; + + /** + * @brief Construct a ContractSource ledger entry wrapper from an existing SLE object. + * @throws std::runtime_error if the ledger entry type doesn't match. + */ + explicit ContractSource(std::shared_ptr sle) + : LedgerEntryBase(std::move(sle)) + { + // Verify ledger entry type + if (sle_->getType() != entryType) + { + throw std::runtime_error("Invalid ledger entry type for ContractSource"); + } + } + + // Ledger entry-specific field getters + + /** + * @brief Get sfPreviousTxnID (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_UINT256::type::value_type + getPreviousTxnID() const + { + return this->sle_->at(sfPreviousTxnID); + } + + /** + * @brief Get sfPreviousTxnLgrSeq (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_UINT32::type::value_type + getPreviousTxnLgrSeq() const + { + return this->sle_->at(sfPreviousTxnLgrSeq); + } + + /** + * @brief Get sfContractHash (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_UINT256::type::value_type + getContractHash() const + { + return this->sle_->at(sfContractHash); + } + + /** + * @brief Get sfContractCode (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_VL::type::value_type + getContractCode() const + { + return this->sle_->at(sfContractCode); + } + + /** + * @brief Get sfFunctions (soeREQUIRED) + * @note This is an untyped field (unknown). + * @return The field value. + */ + [[nodiscard]] + STArray const& + getFunctions() const + { + return this->sle_->getFieldArray(sfFunctions); + } + + /** + * @brief Get sfInstanceParameters (soeOPTIONAL) + * @note This is an untyped field (unknown). + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + std::optional> + getInstanceParameters() const + { + if (this->sle_->isFieldPresent(sfInstanceParameters)) + return this->sle_->getFieldArray(sfInstanceParameters); + return std::nullopt; + } + + /** + * @brief Check if sfInstanceParameters is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasInstanceParameters() const + { + return this->sle_->isFieldPresent(sfInstanceParameters); + } + + /** + * @brief Get sfReferenceCount (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_UINT64::type::value_type + getReferenceCount() const + { + return this->sle_->at(sfReferenceCount); + } +}; + +/** + * @brief Builder for ContractSource ledger entries. + * + * Provides a fluent interface for constructing ledger entries with method chaining. + * Uses json::Value internally for flexible ledger entry construction. + * Inherits common field setters from LedgerEntryBuilderBase. + */ +class ContractSourceBuilder : public LedgerEntryBuilderBase +{ +public: + /** + * @brief Construct a new ContractSourceBuilder with required fields. + * @param previousTxnID The sfPreviousTxnID field value. + * @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value. + * @param contractHash The sfContractHash field value. + * @param contractCode The sfContractCode field value. + * @param functions The sfFunctions field value. + * @param referenceCount The sfReferenceCount field value. + */ + ContractSourceBuilder(std::decay_t const& previousTxnID,std::decay_t const& previousTxnLgrSeq,std::decay_t const& contractHash,std::decay_t const& contractCode,STArray const& functions,std::decay_t const& referenceCount) + : LedgerEntryBuilderBase(ltCONTRACT_SOURCE) + { + setPreviousTxnID(previousTxnID); + setPreviousTxnLgrSeq(previousTxnLgrSeq); + setContractHash(contractHash); + setContractCode(contractCode); + setFunctions(functions); + setReferenceCount(referenceCount); + } + + /** + * @brief Construct a ContractSourceBuilder from an existing SLE object. + * @param sle The existing ledger entry to copy from. + * @throws std::runtime_error if the ledger entry type doesn't match. + */ + ContractSourceBuilder(std::shared_ptr sle) + { + if (sle->at(sfLedgerEntryType) != ltCONTRACT_SOURCE) + { + throw std::runtime_error("Invalid ledger entry type for ContractSource"); + } + object_ = *sle; + } + + /** @brief Ledger entry-specific field setters */ + + /** + * @brief Set sfPreviousTxnID (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractSourceBuilder& + setPreviousTxnID(std::decay_t const& value) + { + object_[sfPreviousTxnID] = value; + return *this; + } + + /** + * @brief Set sfPreviousTxnLgrSeq (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractSourceBuilder& + setPreviousTxnLgrSeq(std::decay_t const& value) + { + object_[sfPreviousTxnLgrSeq] = value; + return *this; + } + + /** + * @brief Set sfContractHash (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractSourceBuilder& + setContractHash(std::decay_t const& value) + { + object_[sfContractHash] = value; + return *this; + } + + /** + * @brief Set sfContractCode (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractSourceBuilder& + setContractCode(std::decay_t const& value) + { + object_[sfContractCode] = value; + return *this; + } + + /** + * @brief Set sfFunctions (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractSourceBuilder& + setFunctions(STArray const& value) + { + object_.setFieldArray(sfFunctions, value); + return *this; + } + + /** + * @brief Set sfInstanceParameters (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractSourceBuilder& + setInstanceParameters(STArray const& value) + { + object_.setFieldArray(sfInstanceParameters, value); + return *this; + } + + /** + * @brief Set sfReferenceCount (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractSourceBuilder& + setReferenceCount(std::decay_t const& value) + { + object_[sfReferenceCount] = value; + return *this; + } + + /** + * @brief Build and return the completed ContractSource wrapper. + * @param index The ledger entry index. + * @return The constructed ledger entry wrapper. + */ + ContractSource + build(uint256 const& index) + { + return ContractSource{std::make_shared(std::move(object_), index)}; + } +}; + +} // namespace xrpl::ledger_entries diff --git a/include/xrpl/protocol_autogen/ledger_entries/Escrow.h b/include/xrpl/protocol_autogen/ledger_entries/Escrow.h index ae8219a4f06..8acd5392493 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/Escrow.h +++ b/include/xrpl/protocol_autogen/ledger_entries/Escrow.h @@ -174,6 +174,54 @@ class Escrow : public LedgerEntryBase return this->sle_->isFieldPresent(sfFinishAfter); } + /** + * @brief Get sfFinishFunction (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getFinishFunction() const + { + if (hasFinishFunction()) + return this->sle_->at(sfFinishFunction); + return std::nullopt; + } + + /** + * @brief Check if sfFinishFunction is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasFinishFunction() const + { + return this->sle_->isFieldPresent(sfFinishFunction); + } + + /** + * @brief Get sfData (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getData() const + { + if (hasData()) + return this->sle_->at(sfData); + return std::nullopt; + } + + /** + * @brief Check if sfData is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasData() const + { + return this->sle_->isFieldPresent(sfData); + } + /** * @brief Get sfSourceTag (SoeOptional) * @return The field value, or std::nullopt if not present. @@ -451,6 +499,28 @@ class EscrowBuilder : public LedgerEntryBuilderBase return *this; } + /** + * @brief Set sfFinishFunction (SoeOptional) + * @return Reference to this builder for method chaining. + */ + EscrowBuilder& + setFinishFunction(std::decay_t const& value) + { + object_[sfFinishFunction] = value; + return *this; + } + + /** + * @brief Set sfData (SoeOptional) + * @return Reference to this builder for method chaining. + */ + EscrowBuilder& + setData(std::decay_t const& value) + { + object_[sfData] = value; + return *this; + } + /** * @brief Set sfSourceTag (SoeOptional) * @return Reference to this builder for method chaining. diff --git a/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h b/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h index cfd7a591e89..58a8887b11e 100644 --- a/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h +++ b/include/xrpl/protocol_autogen/ledger_entries/FeeSettings.h @@ -213,6 +213,78 @@ class FeeSettings : public LedgerEntryBase return this->sle_->isFieldPresent(sfReserveIncrementDrops); } + /** + * @brief Get sfExtensionComputeLimit (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getExtensionComputeLimit() const + { + if (hasExtensionComputeLimit()) + return this->sle_->at(sfExtensionComputeLimit); + return std::nullopt; + } + + /** + * @brief Check if sfExtensionComputeLimit is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasExtensionComputeLimit() const + { + return this->sle_->isFieldPresent(sfExtensionComputeLimit); + } + + /** + * @brief Get sfExtensionSizeLimit (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getExtensionSizeLimit() const + { + if (hasExtensionSizeLimit()) + return this->sle_->at(sfExtensionSizeLimit); + return std::nullopt; + } + + /** + * @brief Check if sfExtensionSizeLimit is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasExtensionSizeLimit() const + { + return this->sle_->isFieldPresent(sfExtensionSizeLimit); + } + + /** + * @brief Get sfGasPrice (SoeOptional) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getGasPrice() const + { + if (hasGasPrice()) + return this->sle_->at(sfGasPrice); + return std::nullopt; + } + + /** + * @brief Check if sfGasPrice is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasGasPrice() const + { + return this->sle_->isFieldPresent(sfGasPrice); + } + /** * @brief Get sfPreviousTxnID (SoeOptional) * @return The field value, or std::nullopt if not present. @@ -373,6 +445,39 @@ class FeeSettingsBuilder : public LedgerEntryBuilderBase return *this; } + /** + * @brief Set sfExtensionComputeLimit (SoeOptional) + * @return Reference to this builder for method chaining. + */ + FeeSettingsBuilder& + setExtensionComputeLimit(std::decay_t const& value) + { + object_[sfExtensionComputeLimit] = value; + return *this; + } + + /** + * @brief Set sfExtensionSizeLimit (SoeOptional) + * @return Reference to this builder for method chaining. + */ + FeeSettingsBuilder& + setExtensionSizeLimit(std::decay_t const& value) + { + object_[sfExtensionSizeLimit] = value; + return *this; + } + + /** + * @brief Set sfGasPrice (SoeOptional) + * @return Reference to this builder for method chaining. + */ + FeeSettingsBuilder& + setGasPrice(std::decay_t const& value) + { + object_[sfGasPrice] = value; + return *this; + } + /** * @brief Set sfPreviousTxnID (SoeOptional) * @return Reference to this builder for method chaining. diff --git a/include/xrpl/protocol_autogen/transactions/ContractCall.h b/include/xrpl/protocol_autogen/transactions/ContractCall.h new file mode 100644 index 00000000000..c866bc416a1 --- /dev/null +++ b/include/xrpl/protocol_autogen/transactions/ContractCall.h @@ -0,0 +1,212 @@ +// This file is auto-generated. Do not edit. +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::transactions { + +class ContractCallBuilder; + +/** + * @brief Transaction: ContractCall + * + * Type: ttCONTRACT_CALL (90) + * Delegable: Delegation::delegable + * Amendment: featureSmartContract + * Privileges: noPriv + * + * Immutable wrapper around STTx providing type-safe field access. + * Use ContractCallBuilder to construct new transactions. + */ +class ContractCall : public TransactionBase +{ +public: + static constexpr xrpl::TxType txType = ttCONTRACT_CALL; + + /** + * @brief Construct a ContractCall transaction wrapper from an existing STTx object. + * @throws std::runtime_error if the transaction type doesn't match. + */ + explicit ContractCall(std::shared_ptr tx) + : TransactionBase(std::move(tx)) + { + // Verify transaction type + if (tx_->getTxnType() != txType) + { + throw std::runtime_error("Invalid transaction type for ContractCall"); + } + } + + // Transaction-specific field getters + + /** + * @brief Get sfContractAccount (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_ACCOUNT::type::value_type + getContractAccount() const + { + return this->tx_->at(sfContractAccount); + } + + /** + * @brief Get sfFunctionName (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_VL::type::value_type + getFunctionName() const + { + return this->tx_->at(sfFunctionName); + } + /** + * @brief Get sfParameters (soeOPTIONAL) + * @note This is an untyped field. + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + std::optional> + getParameters() const + { + if (this->tx_->isFieldPresent(sfParameters)) + return this->tx_->getFieldArray(sfParameters); + return std::nullopt; + } + + /** + * @brief Check if sfParameters is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasParameters() const + { + return this->tx_->isFieldPresent(sfParameters); + } + + /** + * @brief Get sfComputationAllowance (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_UINT32::type::value_type + getComputationAllowance() const + { + return this->tx_->at(sfComputationAllowance); + } +}; + +/** + * @brief Builder for ContractCall transactions. + * + * Provides a fluent interface for constructing transactions with method chaining. + * Uses json::Value internally for flexible transaction construction. + * Inherits common field setters from TransactionBuilderBase. + */ +class ContractCallBuilder : public TransactionBuilderBase +{ +public: + /** + * @brief Construct a new ContractCallBuilder with required fields. + * @param account The account initiating the transaction. + * @param contractAccount The sfContractAccount field value. + * @param functionName The sfFunctionName field value. + * @param computationAllowance The sfComputationAllowance field value. + * @param sequence Optional sequence number for the transaction. + * @param fee Optional fee for the transaction. + */ + ContractCallBuilder(SF_ACCOUNT::type::value_type account, + std::decay_t const& contractAccount, std::decay_t const& functionName, std::decay_t const& computationAllowance, std::optional sequence = std::nullopt, + std::optional fee = std::nullopt +) + : TransactionBuilderBase(ttCONTRACT_CALL, account, sequence, fee) + { + setContractAccount(contractAccount); + setFunctionName(functionName); + setComputationAllowance(computationAllowance); + } + + /** + * @brief Construct a ContractCallBuilder from an existing STTx object. + * @param tx The existing transaction to copy from. + * @throws std::runtime_error if the transaction type doesn't match. + */ + ContractCallBuilder(std::shared_ptr tx) + { + if (tx->getTxnType() != ttCONTRACT_CALL) + { + throw std::runtime_error("Invalid transaction type for ContractCallBuilder"); + } + object_ = *tx; + } + + /** @brief Transaction-specific field setters */ + + /** + * @brief Set sfContractAccount (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractCallBuilder& + setContractAccount(std::decay_t const& value) + { + object_[sfContractAccount] = value; + return *this; + } + + /** + * @brief Set sfFunctionName (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractCallBuilder& + setFunctionName(std::decay_t const& value) + { + object_[sfFunctionName] = value; + return *this; + } + + /** + * @brief Set sfParameters (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractCallBuilder& + setParameters(STArray const& value) + { + object_.setFieldArray(sfParameters, value); + return *this; + } + + /** + * @brief Set sfComputationAllowance (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractCallBuilder& + setComputationAllowance(std::decay_t const& value) + { + object_[sfComputationAllowance] = value; + return *this; + } + + /** + * @brief Build and return the ContractCall wrapper. + * @param publicKey The public key for signing. + * @param secretKey The secret key for signing. + * @return The constructed transaction wrapper. + */ + ContractCall + build(PublicKey const& publicKey, SecretKey const& secretKey) + { + sign(publicKey, secretKey); + return ContractCall{std::make_shared(std::move(object_))}; + } +}; + +} // namespace xrpl::transactions diff --git a/include/xrpl/protocol_autogen/transactions/ContractClawback.h b/include/xrpl/protocol_autogen/transactions/ContractClawback.h new file mode 100644 index 00000000000..1f2bfd3d351 --- /dev/null +++ b/include/xrpl/protocol_autogen/transactions/ContractClawback.h @@ -0,0 +1,168 @@ +// This file is auto-generated. Do not edit. +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::transactions { + +class ContractClawbackBuilder; + +/** + * @brief Transaction: ContractClawback + * + * Type: ttCONTRACT_CLAWBACK (88) + * Delegable: Delegation::delegable + * Amendment: featureSmartContract + * Privileges: noPriv + * + * Immutable wrapper around STTx providing type-safe field access. + * Use ContractClawbackBuilder to construct new transactions. + */ +class ContractClawback : public TransactionBase +{ +public: + static constexpr xrpl::TxType txType = ttCONTRACT_CLAWBACK; + + /** + * @brief Construct a ContractClawback transaction wrapper from an existing STTx object. + * @throws std::runtime_error if the transaction type doesn't match. + */ + explicit ContractClawback(std::shared_ptr tx) + : TransactionBase(std::move(tx)) + { + // Verify transaction type + if (tx_->getTxnType() != txType) + { + throw std::runtime_error("Invalid transaction type for ContractClawback"); + } + } + + // Transaction-specific field getters + + /** + * @brief Get sfContractAccount (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getContractAccount() const + { + if (hasContractAccount()) + { + return this->tx_->at(sfContractAccount); + } + return std::nullopt; + } + + /** + * @brief Check if sfContractAccount is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasContractAccount() const + { + return this->tx_->isFieldPresent(sfContractAccount); + } + + /** + * @brief Get sfAmount (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. + * @return The field value. + */ + [[nodiscard]] + SF_AMOUNT::type::value_type + getAmount() const + { + return this->tx_->at(sfAmount); + } +}; + +/** + * @brief Builder for ContractClawback transactions. + * + * Provides a fluent interface for constructing transactions with method chaining. + * Uses json::Value internally for flexible transaction construction. + * Inherits common field setters from TransactionBuilderBase. + */ +class ContractClawbackBuilder : public TransactionBuilderBase +{ +public: + /** + * @brief Construct a new ContractClawbackBuilder with required fields. + * @param account The account initiating the transaction. + * @param amount The sfAmount field value. + * @param sequence Optional sequence number for the transaction. + * @param fee Optional fee for the transaction. + */ + ContractClawbackBuilder(SF_ACCOUNT::type::value_type account, + std::decay_t const& amount, std::optional sequence = std::nullopt, + std::optional fee = std::nullopt +) + : TransactionBuilderBase(ttCONTRACT_CLAWBACK, account, sequence, fee) + { + setAmount(amount); + } + + /** + * @brief Construct a ContractClawbackBuilder from an existing STTx object. + * @param tx The existing transaction to copy from. + * @throws std::runtime_error if the transaction type doesn't match. + */ + ContractClawbackBuilder(std::shared_ptr tx) + { + if (tx->getTxnType() != ttCONTRACT_CLAWBACK) + { + throw std::runtime_error("Invalid transaction type for ContractClawbackBuilder"); + } + object_ = *tx; + } + + /** @brief Transaction-specific field setters */ + + /** + * @brief Set sfContractAccount (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractClawbackBuilder& + setContractAccount(std::decay_t const& value) + { + object_[sfContractAccount] = value; + return *this; + } + + /** + * @brief Set sfAmount (soeREQUIRED) + * @note This field supports MPT (Multi-Purpose Token) amounts. + * @return Reference to this builder for method chaining. + */ + ContractClawbackBuilder& + setAmount(std::decay_t const& value) + { + object_[sfAmount] = value; + return *this; + } + + /** + * @brief Build and return the ContractClawback wrapper. + * @param publicKey The public key for signing. + * @param secretKey The secret key for signing. + * @return The constructed transaction wrapper. + */ + ContractClawback + build(PublicKey const& publicKey, SecretKey const& secretKey) + { + sign(publicKey, secretKey); + return ContractClawback{std::make_shared(std::move(object_))}; + } +}; + +} // namespace xrpl::transactions diff --git a/include/xrpl/protocol_autogen/transactions/ContractCreate.h b/include/xrpl/protocol_autogen/transactions/ContractCreate.h new file mode 100644 index 00000000000..7ccd5de657d --- /dev/null +++ b/include/xrpl/protocol_autogen/transactions/ContractCreate.h @@ -0,0 +1,321 @@ +// This file is auto-generated. Do not edit. +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::transactions { + +class ContractCreateBuilder; + +/** + * @brief Transaction: ContractCreate + * + * Type: ttCONTRACT_CREATE (85) + * Delegable: Delegation::delegable + * Amendment: featureSmartContract + * Privileges: createPseudoAcct + * + * Immutable wrapper around STTx providing type-safe field access. + * Use ContractCreateBuilder to construct new transactions. + */ +class ContractCreate : public TransactionBase +{ +public: + static constexpr xrpl::TxType txType = ttCONTRACT_CREATE; + + /** + * @brief Construct a ContractCreate transaction wrapper from an existing STTx object. + * @throws std::runtime_error if the transaction type doesn't match. + */ + explicit ContractCreate(std::shared_ptr tx) + : TransactionBase(std::move(tx)) + { + // Verify transaction type + if (tx_->getTxnType() != txType) + { + throw std::runtime_error("Invalid transaction type for ContractCreate"); + } + } + + // Transaction-specific field getters + + /** + * @brief Get sfContractCode (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getContractCode() const + { + if (hasContractCode()) + { + return this->tx_->at(sfContractCode); + } + return std::nullopt; + } + + /** + * @brief Check if sfContractCode is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasContractCode() const + { + return this->tx_->isFieldPresent(sfContractCode); + } + + /** + * @brief Get sfContractHash (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getContractHash() const + { + if (hasContractHash()) + { + return this->tx_->at(sfContractHash); + } + return std::nullopt; + } + + /** + * @brief Check if sfContractHash is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasContractHash() const + { + return this->tx_->isFieldPresent(sfContractHash); + } + /** + * @brief Get sfFunctions (soeOPTIONAL) + * @note This is an untyped field. + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + std::optional> + getFunctions() const + { + if (this->tx_->isFieldPresent(sfFunctions)) + return this->tx_->getFieldArray(sfFunctions); + return std::nullopt; + } + + /** + * @brief Check if sfFunctions is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasFunctions() const + { + return this->tx_->isFieldPresent(sfFunctions); + } + /** + * @brief Get sfInstanceParameters (soeOPTIONAL) + * @note This is an untyped field. + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + std::optional> + getInstanceParameters() const + { + if (this->tx_->isFieldPresent(sfInstanceParameters)) + return this->tx_->getFieldArray(sfInstanceParameters); + return std::nullopt; + } + + /** + * @brief Check if sfInstanceParameters is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasInstanceParameters() const + { + return this->tx_->isFieldPresent(sfInstanceParameters); + } + /** + * @brief Get sfInstanceParameterValues (soeOPTIONAL) + * @note This is an untyped field. + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + std::optional> + getInstanceParameterValues() const + { + if (this->tx_->isFieldPresent(sfInstanceParameterValues)) + return this->tx_->getFieldArray(sfInstanceParameterValues); + return std::nullopt; + } + + /** + * @brief Check if sfInstanceParameterValues is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasInstanceParameterValues() const + { + return this->tx_->isFieldPresent(sfInstanceParameterValues); + } + + /** + * @brief Get sfURI (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getURI() const + { + if (hasURI()) + { + return this->tx_->at(sfURI); + } + return std::nullopt; + } + + /** + * @brief Check if sfURI is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasURI() const + { + return this->tx_->isFieldPresent(sfURI); + } +}; + +/** + * @brief Builder for ContractCreate transactions. + * + * Provides a fluent interface for constructing transactions with method chaining. + * Uses json::Value internally for flexible transaction construction. + * Inherits common field setters from TransactionBuilderBase. + */ +class ContractCreateBuilder : public TransactionBuilderBase +{ +public: + /** + * @brief Construct a new ContractCreateBuilder with required fields. + * @param account The account initiating the transaction. + * @param sequence Optional sequence number for the transaction. + * @param fee Optional fee for the transaction. + */ + ContractCreateBuilder(SF_ACCOUNT::type::value_type account, + std::optional sequence = std::nullopt, + std::optional fee = std::nullopt +) + : TransactionBuilderBase(ttCONTRACT_CREATE, account, sequence, fee) + { + } + + /** + * @brief Construct a ContractCreateBuilder from an existing STTx object. + * @param tx The existing transaction to copy from. + * @throws std::runtime_error if the transaction type doesn't match. + */ + ContractCreateBuilder(std::shared_ptr tx) + { + if (tx->getTxnType() != ttCONTRACT_CREATE) + { + throw std::runtime_error("Invalid transaction type for ContractCreateBuilder"); + } + object_ = *tx; + } + + /** @brief Transaction-specific field setters */ + + /** + * @brief Set sfContractCode (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractCreateBuilder& + setContractCode(std::decay_t const& value) + { + object_[sfContractCode] = value; + return *this; + } + + /** + * @brief Set sfContractHash (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractCreateBuilder& + setContractHash(std::decay_t const& value) + { + object_[sfContractHash] = value; + return *this; + } + + /** + * @brief Set sfFunctions (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractCreateBuilder& + setFunctions(STArray const& value) + { + object_.setFieldArray(sfFunctions, value); + return *this; + } + + /** + * @brief Set sfInstanceParameters (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractCreateBuilder& + setInstanceParameters(STArray const& value) + { + object_.setFieldArray(sfInstanceParameters, value); + return *this; + } + + /** + * @brief Set sfInstanceParameterValues (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractCreateBuilder& + setInstanceParameterValues(STArray const& value) + { + object_.setFieldArray(sfInstanceParameterValues, value); + return *this; + } + + /** + * @brief Set sfURI (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractCreateBuilder& + setURI(std::decay_t const& value) + { + object_[sfURI] = value; + return *this; + } + + /** + * @brief Build and return the ContractCreate wrapper. + * @param publicKey The public key for signing. + * @param secretKey The secret key for signing. + * @return The constructed transaction wrapper. + */ + ContractCreate + build(PublicKey const& publicKey, SecretKey const& secretKey) + { + sign(publicKey, secretKey); + return ContractCreate{std::make_shared(std::move(object_))}; + } +}; + +} // namespace xrpl::transactions diff --git a/include/xrpl/protocol_autogen/transactions/ContractDelete.h b/include/xrpl/protocol_autogen/transactions/ContractDelete.h new file mode 100644 index 00000000000..a6bbf9a60e8 --- /dev/null +++ b/include/xrpl/protocol_autogen/transactions/ContractDelete.h @@ -0,0 +1,129 @@ +// This file is auto-generated. Do not edit. +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::transactions { + +class ContractDeleteBuilder; + +/** + * @brief Transaction: ContractDelete + * + * Type: ttCONTRACT_DELETE (87) + * Delegable: Delegation::delegable + * Amendment: featureSmartContract + * Privileges: mustDeleteAcct + * + * Immutable wrapper around STTx providing type-safe field access. + * Use ContractDeleteBuilder to construct new transactions. + */ +class ContractDelete : public TransactionBase +{ +public: + static constexpr xrpl::TxType txType = ttCONTRACT_DELETE; + + /** + * @brief Construct a ContractDelete transaction wrapper from an existing STTx object. + * @throws std::runtime_error if the transaction type doesn't match. + */ + explicit ContractDelete(std::shared_ptr tx) + : TransactionBase(std::move(tx)) + { + // Verify transaction type + if (tx_->getTxnType() != txType) + { + throw std::runtime_error("Invalid transaction type for ContractDelete"); + } + } + + // Transaction-specific field getters + + /** + * @brief Get sfContractAccount (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_ACCOUNT::type::value_type + getContractAccount() const + { + return this->tx_->at(sfContractAccount); + } +}; + +/** + * @brief Builder for ContractDelete transactions. + * + * Provides a fluent interface for constructing transactions with method chaining. + * Uses json::Value internally for flexible transaction construction. + * Inherits common field setters from TransactionBuilderBase. + */ +class ContractDeleteBuilder : public TransactionBuilderBase +{ +public: + /** + * @brief Construct a new ContractDeleteBuilder with required fields. + * @param account The account initiating the transaction. + * @param contractAccount The sfContractAccount field value. + * @param sequence Optional sequence number for the transaction. + * @param fee Optional fee for the transaction. + */ + ContractDeleteBuilder(SF_ACCOUNT::type::value_type account, + std::decay_t const& contractAccount, std::optional sequence = std::nullopt, + std::optional fee = std::nullopt +) + : TransactionBuilderBase(ttCONTRACT_DELETE, account, sequence, fee) + { + setContractAccount(contractAccount); + } + + /** + * @brief Construct a ContractDeleteBuilder from an existing STTx object. + * @param tx The existing transaction to copy from. + * @throws std::runtime_error if the transaction type doesn't match. + */ + ContractDeleteBuilder(std::shared_ptr tx) + { + if (tx->getTxnType() != ttCONTRACT_DELETE) + { + throw std::runtime_error("Invalid transaction type for ContractDeleteBuilder"); + } + object_ = *tx; + } + + /** @brief Transaction-specific field setters */ + + /** + * @brief Set sfContractAccount (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractDeleteBuilder& + setContractAccount(std::decay_t const& value) + { + object_[sfContractAccount] = value; + return *this; + } + + /** + * @brief Build and return the ContractDelete wrapper. + * @param publicKey The public key for signing. + * @param secretKey The secret key for signing. + * @return The constructed transaction wrapper. + */ + ContractDelete + build(PublicKey const& publicKey, SecretKey const& secretKey) + { + sign(publicKey, secretKey); + return ContractDelete{std::make_shared(std::move(object_))}; + } +}; + +} // namespace xrpl::transactions diff --git a/include/xrpl/protocol_autogen/transactions/ContractModify.h b/include/xrpl/protocol_autogen/transactions/ContractModify.h new file mode 100644 index 00000000000..8c478c354a6 --- /dev/null +++ b/include/xrpl/protocol_autogen/transactions/ContractModify.h @@ -0,0 +1,395 @@ +// This file is auto-generated. Do not edit. +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::transactions { + +class ContractModifyBuilder; + +/** + * @brief Transaction: ContractModify + * + * Type: ttCONTRACT_MODIFY (86) + * Delegable: Delegation::delegable + * Amendment: featureSmartContract + * Privileges: noPriv + * + * Immutable wrapper around STTx providing type-safe field access. + * Use ContractModifyBuilder to construct new transactions. + */ +class ContractModify : public TransactionBase +{ +public: + static constexpr xrpl::TxType txType = ttCONTRACT_MODIFY; + + /** + * @brief Construct a ContractModify transaction wrapper from an existing STTx object. + * @throws std::runtime_error if the transaction type doesn't match. + */ + explicit ContractModify(std::shared_ptr tx) + : TransactionBase(std::move(tx)) + { + // Verify transaction type + if (tx_->getTxnType() != txType) + { + throw std::runtime_error("Invalid transaction type for ContractModify"); + } + } + + // Transaction-specific field getters + + /** + * @brief Get sfContractAccount (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getContractAccount() const + { + if (hasContractAccount()) + { + return this->tx_->at(sfContractAccount); + } + return std::nullopt; + } + + /** + * @brief Check if sfContractAccount is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasContractAccount() const + { + return this->tx_->isFieldPresent(sfContractAccount); + } + + /** + * @brief Get sfOwner (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getOwner() const + { + if (hasOwner()) + { + return this->tx_->at(sfOwner); + } + return std::nullopt; + } + + /** + * @brief Check if sfOwner is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasOwner() const + { + return this->tx_->isFieldPresent(sfOwner); + } + + /** + * @brief Get sfContractCode (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getContractCode() const + { + if (hasContractCode()) + { + return this->tx_->at(sfContractCode); + } + return std::nullopt; + } + + /** + * @brief Check if sfContractCode is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasContractCode() const + { + return this->tx_->isFieldPresent(sfContractCode); + } + + /** + * @brief Get sfContractHash (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getContractHash() const + { + if (hasContractHash()) + { + return this->tx_->at(sfContractHash); + } + return std::nullopt; + } + + /** + * @brief Check if sfContractHash is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasContractHash() const + { + return this->tx_->isFieldPresent(sfContractHash); + } + /** + * @brief Get sfFunctions (soeOPTIONAL) + * @note This is an untyped field. + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + std::optional> + getFunctions() const + { + if (this->tx_->isFieldPresent(sfFunctions)) + return this->tx_->getFieldArray(sfFunctions); + return std::nullopt; + } + + /** + * @brief Check if sfFunctions is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasFunctions() const + { + return this->tx_->isFieldPresent(sfFunctions); + } + /** + * @brief Get sfInstanceParameters (soeOPTIONAL) + * @note This is an untyped field. + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + std::optional> + getInstanceParameters() const + { + if (this->tx_->isFieldPresent(sfInstanceParameters)) + return this->tx_->getFieldArray(sfInstanceParameters); + return std::nullopt; + } + + /** + * @brief Check if sfInstanceParameters is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasInstanceParameters() const + { + return this->tx_->isFieldPresent(sfInstanceParameters); + } + /** + * @brief Get sfInstanceParameterValues (soeOPTIONAL) + * @note This is an untyped field. + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + std::optional> + getInstanceParameterValues() const + { + if (this->tx_->isFieldPresent(sfInstanceParameterValues)) + return this->tx_->getFieldArray(sfInstanceParameterValues); + return std::nullopt; + } + + /** + * @brief Check if sfInstanceParameterValues is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasInstanceParameterValues() const + { + return this->tx_->isFieldPresent(sfInstanceParameterValues); + } + + /** + * @brief Get sfURI (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getURI() const + { + if (hasURI()) + { + return this->tx_->at(sfURI); + } + return std::nullopt; + } + + /** + * @brief Check if sfURI is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasURI() const + { + return this->tx_->isFieldPresent(sfURI); + } +}; + +/** + * @brief Builder for ContractModify transactions. + * + * Provides a fluent interface for constructing transactions with method chaining. + * Uses json::Value internally for flexible transaction construction. + * Inherits common field setters from TransactionBuilderBase. + */ +class ContractModifyBuilder : public TransactionBuilderBase +{ +public: + /** + * @brief Construct a new ContractModifyBuilder with required fields. + * @param account The account initiating the transaction. + * @param sequence Optional sequence number for the transaction. + * @param fee Optional fee for the transaction. + */ + ContractModifyBuilder(SF_ACCOUNT::type::value_type account, + std::optional sequence = std::nullopt, + std::optional fee = std::nullopt +) + : TransactionBuilderBase(ttCONTRACT_MODIFY, account, sequence, fee) + { + } + + /** + * @brief Construct a ContractModifyBuilder from an existing STTx object. + * @param tx The existing transaction to copy from. + * @throws std::runtime_error if the transaction type doesn't match. + */ + ContractModifyBuilder(std::shared_ptr tx) + { + if (tx->getTxnType() != ttCONTRACT_MODIFY) + { + throw std::runtime_error("Invalid transaction type for ContractModifyBuilder"); + } + object_ = *tx; + } + + /** @brief Transaction-specific field setters */ + + /** + * @brief Set sfContractAccount (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractModifyBuilder& + setContractAccount(std::decay_t const& value) + { + object_[sfContractAccount] = value; + return *this; + } + + /** + * @brief Set sfOwner (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractModifyBuilder& + setOwner(std::decay_t const& value) + { + object_[sfOwner] = value; + return *this; + } + + /** + * @brief Set sfContractCode (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractModifyBuilder& + setContractCode(std::decay_t const& value) + { + object_[sfContractCode] = value; + return *this; + } + + /** + * @brief Set sfContractHash (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractModifyBuilder& + setContractHash(std::decay_t const& value) + { + object_[sfContractHash] = value; + return *this; + } + + /** + * @brief Set sfFunctions (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractModifyBuilder& + setFunctions(STArray const& value) + { + object_.setFieldArray(sfFunctions, value); + return *this; + } + + /** + * @brief Set sfInstanceParameters (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractModifyBuilder& + setInstanceParameters(STArray const& value) + { + object_.setFieldArray(sfInstanceParameters, value); + return *this; + } + + /** + * @brief Set sfInstanceParameterValues (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractModifyBuilder& + setInstanceParameterValues(STArray const& value) + { + object_.setFieldArray(sfInstanceParameterValues, value); + return *this; + } + + /** + * @brief Set sfURI (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractModifyBuilder& + setURI(std::decay_t const& value) + { + object_[sfURI] = value; + return *this; + } + + /** + * @brief Build and return the ContractModify wrapper. + * @param publicKey The public key for signing. + * @param secretKey The secret key for signing. + * @return The constructed transaction wrapper. + */ + ContractModify + build(PublicKey const& publicKey, SecretKey const& secretKey) + { + sign(publicKey, secretKey); + return ContractModify{std::make_shared(std::move(object_))}; + } +}; + +} // namespace xrpl::transactions diff --git a/include/xrpl/protocol_autogen/transactions/ContractUserDelete.h b/include/xrpl/protocol_autogen/transactions/ContractUserDelete.h new file mode 100644 index 00000000000..d1c5e68b10b --- /dev/null +++ b/include/xrpl/protocol_autogen/transactions/ContractUserDelete.h @@ -0,0 +1,212 @@ +// This file is auto-generated. Do not edit. +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl::transactions { + +class ContractUserDeleteBuilder; + +/** + * @brief Transaction: ContractUserDelete + * + * Type: ttCONTRACT_USER_DELETE (89) + * Delegable: Delegation::delegable + * Amendment: featureSmartContract + * Privileges: noPriv + * + * Immutable wrapper around STTx providing type-safe field access. + * Use ContractUserDeleteBuilder to construct new transactions. + */ +class ContractUserDelete : public TransactionBase +{ +public: + static constexpr xrpl::TxType txType = ttCONTRACT_USER_DELETE; + + /** + * @brief Construct a ContractUserDelete transaction wrapper from an existing STTx object. + * @throws std::runtime_error if the transaction type doesn't match. + */ + explicit ContractUserDelete(std::shared_ptr tx) + : TransactionBase(std::move(tx)) + { + // Verify transaction type + if (tx_->getTxnType() != txType) + { + throw std::runtime_error("Invalid transaction type for ContractUserDelete"); + } + } + + // Transaction-specific field getters + + /** + * @brief Get sfContractAccount (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_ACCOUNT::type::value_type + getContractAccount() const + { + return this->tx_->at(sfContractAccount); + } + + /** + * @brief Get sfFunctionName (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_VL::type::value_type + getFunctionName() const + { + return this->tx_->at(sfFunctionName); + } + /** + * @brief Get sfParameters (soeOPTIONAL) + * @note This is an untyped field. + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + std::optional> + getParameters() const + { + if (this->tx_->isFieldPresent(sfParameters)) + return this->tx_->getFieldArray(sfParameters); + return std::nullopt; + } + + /** + * @brief Check if sfParameters is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasParameters() const + { + return this->tx_->isFieldPresent(sfParameters); + } + + /** + * @brief Get sfComputationAllowance (soeREQUIRED) + * @return The field value. + */ + [[nodiscard]] + SF_UINT32::type::value_type + getComputationAllowance() const + { + return this->tx_->at(sfComputationAllowance); + } +}; + +/** + * @brief Builder for ContractUserDelete transactions. + * + * Provides a fluent interface for constructing transactions with method chaining. + * Uses json::Value internally for flexible transaction construction. + * Inherits common field setters from TransactionBuilderBase. + */ +class ContractUserDeleteBuilder : public TransactionBuilderBase +{ +public: + /** + * @brief Construct a new ContractUserDeleteBuilder with required fields. + * @param account The account initiating the transaction. + * @param contractAccount The sfContractAccount field value. + * @param functionName The sfFunctionName field value. + * @param computationAllowance The sfComputationAllowance field value. + * @param sequence Optional sequence number for the transaction. + * @param fee Optional fee for the transaction. + */ + ContractUserDeleteBuilder(SF_ACCOUNT::type::value_type account, + std::decay_t const& contractAccount, std::decay_t const& functionName, std::decay_t const& computationAllowance, std::optional sequence = std::nullopt, + std::optional fee = std::nullopt +) + : TransactionBuilderBase(ttCONTRACT_USER_DELETE, account, sequence, fee) + { + setContractAccount(contractAccount); + setFunctionName(functionName); + setComputationAllowance(computationAllowance); + } + + /** + * @brief Construct a ContractUserDeleteBuilder from an existing STTx object. + * @param tx The existing transaction to copy from. + * @throws std::runtime_error if the transaction type doesn't match. + */ + ContractUserDeleteBuilder(std::shared_ptr tx) + { + if (tx->getTxnType() != ttCONTRACT_USER_DELETE) + { + throw std::runtime_error("Invalid transaction type for ContractUserDeleteBuilder"); + } + object_ = *tx; + } + + /** @brief Transaction-specific field setters */ + + /** + * @brief Set sfContractAccount (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractUserDeleteBuilder& + setContractAccount(std::decay_t const& value) + { + object_[sfContractAccount] = value; + return *this; + } + + /** + * @brief Set sfFunctionName (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractUserDeleteBuilder& + setFunctionName(std::decay_t const& value) + { + object_[sfFunctionName] = value; + return *this; + } + + /** + * @brief Set sfParameters (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + ContractUserDeleteBuilder& + setParameters(STArray const& value) + { + object_.setFieldArray(sfParameters, value); + return *this; + } + + /** + * @brief Set sfComputationAllowance (soeREQUIRED) + * @return Reference to this builder for method chaining. + */ + ContractUserDeleteBuilder& + setComputationAllowance(std::decay_t const& value) + { + object_[sfComputationAllowance] = value; + return *this; + } + + /** + * @brief Build and return the ContractUserDelete wrapper. + * @param publicKey The public key for signing. + * @param secretKey The secret key for signing. + * @return The constructed transaction wrapper. + */ + ContractUserDelete + build(PublicKey const& publicKey, SecretKey const& secretKey) + { + sign(publicKey, secretKey); + return ContractUserDelete{std::make_shared(std::move(object_))}; + } +}; + +} // namespace xrpl::transactions diff --git a/include/xrpl/protocol_autogen/transactions/EscrowCreate.h b/include/xrpl/protocol_autogen/transactions/EscrowCreate.h index 35775c31ae7..857e0dedf3b 100644 --- a/include/xrpl/protocol_autogen/transactions/EscrowCreate.h +++ b/include/xrpl/protocol_autogen/transactions/EscrowCreate.h @@ -149,29 +149,55 @@ class EscrowCreate : public TransactionBase } /** - * @brief Get sfDestinationTag (SoeOptional) + * @brief Get sfFinishFunction (SoeOptional) * @return The field value, or std::nullopt if not present. */ [[nodiscard]] - protocol_autogen::Optional - getDestinationTag() const + protocol_autogen::Optional + getFinishFunction() const { - if (hasDestinationTag()) + if (hasFinishFunction()) { - return this->tx_->at(sfDestinationTag); + return this->tx_->at(sfFinishFunction); } return std::nullopt; } /** - * @brief Check if sfDestinationTag is present. + * @brief Check if sfFinishFunction is present. * @return True if the field is present, false otherwise. */ [[nodiscard]] bool - hasDestinationTag() const + hasFinishFunction() const { - return this->tx_->isFieldPresent(sfDestinationTag); + return this->tx_->isFieldPresent(sfFinishFunction); + } + + /** + * @brief Get sfData (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getData() const + { + if (hasData()) + { + return this->tx_->at(sfData); + } + return std::nullopt; + } + + /** + * @brief Check if sfData is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasData() const + { + return this->tx_->isFieldPresent(sfData); } }; @@ -276,13 +302,24 @@ class EscrowCreateBuilder : public TransactionBuilderBase } /** - * @brief Set sfDestinationTag (SoeOptional) + * @brief Set sfFinishFunction (SoeOptional) + * @return Reference to this builder for method chaining. + */ + EscrowCreateBuilder& + setFinishFunction(std::decay_t const& value) + { + object_[sfFinishFunction] = value; + return *this; + } + + /** + * @brief Set sfData (soeOPTIONAL) * @return Reference to this builder for method chaining. */ EscrowCreateBuilder& - setDestinationTag(std::decay_t const& value) + setData(std::decay_t const& value) { - object_[sfDestinationTag] = value; + object_[sfData] = value; return *this; } diff --git a/include/xrpl/protocol_autogen/transactions/EscrowFinish.h b/include/xrpl/protocol_autogen/transactions/EscrowFinish.h index f6ca73d2098..4db1d12c1da 100644 --- a/include/xrpl/protocol_autogen/transactions/EscrowFinish.h +++ b/include/xrpl/protocol_autogen/transactions/EscrowFinish.h @@ -146,6 +146,32 @@ class EscrowFinish : public TransactionBase { return this->tx_->isFieldPresent(sfCredentialIDs); } + + /** + * @brief Get sfComputationAllowance (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getComputationAllowance() const + { + if (hasComputationAllowance()) + { + return this->tx_->at(sfComputationAllowance); + } + return std::nullopt; + } + + /** + * @brief Check if sfComputationAllowance is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasComputationAllowance() const + { + return this->tx_->isFieldPresent(sfComputationAllowance); + } }; /** @@ -247,6 +273,17 @@ class EscrowFinishBuilder : public TransactionBuilderBase return *this; } + /** + * @brief Set sfComputationAllowance (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + EscrowFinishBuilder& + setComputationAllowance(std::decay_t const& value) + { + object_[sfComputationAllowance] = value; + return *this; + } + /** * @brief Build and return the EscrowFinish wrapper. * @param publicKey The public key for signing. diff --git a/include/xrpl/protocol_autogen/transactions/SetFee.h b/include/xrpl/protocol_autogen/transactions/SetFee.h index bc5fc0e6034..fb4b91e4619 100644 --- a/include/xrpl/protocol_autogen/transactions/SetFee.h +++ b/include/xrpl/protocol_autogen/transactions/SetFee.h @@ -254,6 +254,84 @@ class SetFee : public TransactionBase { return this->tx_->isFieldPresent(sfReserveIncrementDrops); } + + /** + * @brief Get sfExtensionComputeLimit (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getExtensionComputeLimit() const + { + if (hasExtensionComputeLimit()) + { + return this->tx_->at(sfExtensionComputeLimit); + } + return std::nullopt; + } + + /** + * @brief Check if sfExtensionComputeLimit is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasExtensionComputeLimit() const + { + return this->tx_->isFieldPresent(sfExtensionComputeLimit); + } + + /** + * @brief Get sfExtensionSizeLimit (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getExtensionSizeLimit() const + { + if (hasExtensionSizeLimit()) + { + return this->tx_->at(sfExtensionSizeLimit); + } + return std::nullopt; + } + + /** + * @brief Check if sfExtensionSizeLimit is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasExtensionSizeLimit() const + { + return this->tx_->isFieldPresent(sfExtensionSizeLimit); + } + + /** + * @brief Get sfGasPrice (soeOPTIONAL) + * @return The field value, or std::nullopt if not present. + */ + [[nodiscard]] + protocol_autogen::Optional + getGasPrice() const + { + if (hasGasPrice()) + { + return this->tx_->at(sfGasPrice); + } + return std::nullopt; + } + + /** + * @brief Check if sfGasPrice is present. + * @return True if the field is present, false otherwise. + */ + [[nodiscard]] + bool + hasGasPrice() const + { + return this->tx_->isFieldPresent(sfGasPrice); + } }; /** @@ -384,6 +462,39 @@ class SetFeeBuilder : public TransactionBuilderBase return *this; } + /** + * @brief Set sfExtensionComputeLimit (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + SetFeeBuilder& + setExtensionComputeLimit(std::decay_t const& value) + { + object_[sfExtensionComputeLimit] = value; + return *this; + } + + /** + * @brief Set sfExtensionSizeLimit (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + SetFeeBuilder& + setExtensionSizeLimit(std::decay_t const& value) + { + object_[sfExtensionSizeLimit] = value; + return *this; + } + + /** + * @brief Set sfGasPrice (soeOPTIONAL) + * @return Reference to this builder for method chaining. + */ + SetFeeBuilder& + setGasPrice(std::decay_t const& value) + { + object_[sfGasPrice] = value; + return *this; + } + /** * @brief Build and return the SetFee wrapper. * @param publicKey The public key for signing. diff --git a/include/xrpl/server/InfoSub.h b/include/xrpl/server/InfoSub.h index e93676a9386..c63ae58ae82 100644 --- a/include/xrpl/server/InfoSub.h +++ b/include/xrpl/server/InfoSub.h @@ -148,6 +148,11 @@ class InfoSub : public CountedObject virtual bool unsubConsensus(std::uint64_t uListener) = 0; + virtual bool + subContractEvent(ref ispListener) = 0; + virtual bool + unsubContractEvent(std::uint64_t uListener) = 0; + // VFALCO TODO Remove // This was added for one particular partner, it // "pushes" subscription data to a particular URL. diff --git a/include/xrpl/server/NetworkOPs.h b/include/xrpl/server/NetworkOPs.h index e2aa17566e1..896c9cd5bac 100644 --- a/include/xrpl/server/NetworkOPs.h +++ b/include/xrpl/server/NetworkOPs.h @@ -247,6 +247,9 @@ class NetworkOPs : public InfoSub::Source virtual void pubValidation(std::shared_ptr const& val) = 0; + virtual void + pubContractEvent(std::string const& name, STJson const& event) = 0; + virtual void stateAccounting(json::Value& obj) = 0; }; diff --git a/include/xrpl/tx/ApplyContext.h b/include/xrpl/tx/ApplyContext.h index 910ec6be42b..73fdd3a5b6c 100644 --- a/include/xrpl/tx/ApplyContext.h +++ b/include/xrpl/tx/ApplyContext.h @@ -3,10 +3,12 @@ #include #include #include +#include #include #include #include +#include namespace xrpl { @@ -43,6 +45,12 @@ class ApplyContext XRPAmount const baseFee; beast::Journal const journal; + OpenView& + openView() + { + return base_.view(); + } + ApplyView& view() { @@ -76,10 +84,41 @@ class ApplyContext view_->deliver(amount); } + /** Sets the gas used in the metadata */ + void + setGasUsed(std::uint32_t const gasUsed) + { + gasUsed_ = gasUsed; + } + + /** Sets the gas used in the metadata */ + void + setWasmReturnCode(std::int32_t const wasmReturnCode) + { + wasmReturnCode_ = wasmReturnCode; + } + + /** Sets the gas used in the metadata */ + void + setEmittedTxns(std::queue> const emittedTxns) + { + emittedTxns_ = emittedTxns; + } + + std::queue> + getEmittedTxns() + { + return emittedTxns_; + } + /** Discard changes and start fresh. */ void discard(); + /** Finalize changes. */ + void + finalize(); + /** Apply the transaction result to the base. */ std::optional apply(TER); @@ -120,11 +159,13 @@ class ApplyContext TER checkInvariantsHelper(TER const result, XRPAmount const fee, std::index_sequence); - OpenView& base_; + OpenViewSandbox base_; ApplyFlags flags_; std::optional view_; - // The ID of the batch transaction we are executing under, if seated. + std::optional gasUsed_; + std::optional wasmReturnCode_; + std::queue> emittedTxns_; std::optional parentBatchId_; }; diff --git a/include/xrpl/tx/Transactor.h b/include/xrpl/tx/Transactor.h index 61d943c4d59..bc2f042dd9d 100644 --- a/include/xrpl/tx/Transactor.h +++ b/include/xrpl/tx/Transactor.h @@ -359,6 +359,9 @@ class Transactor std::pair reset(XRPAmount fee); + std::pair + checkInvariants(TER result, XRPAmount fee); + TER consumeSeqProxy(SLE::pointer const& sleAccount); TER diff --git a/include/xrpl/tx/apply.h b/include/xrpl/tx/apply.h index 49b30fea02f..96b5d734711 100644 --- a/include/xrpl/tx/apply.h +++ b/include/xrpl/tx/apply.h @@ -9,6 +9,8 @@ namespace xrpl { +class Application; + class HashRouter; class ServiceRegistry; @@ -102,6 +104,24 @@ apply( ApplyFlags flags, beast::Journal journal); +ApplyResult +apply( + ServiceRegistry& registry, + OpenView& view, + uint256 const& parentBatchId, + STTx const& tx, + ApplyFlags flags, + beast::Journal j); + +ApplyResult +apply( + Application& app, + OpenView& view, + uint256 const& parentBatchId, + STTx const& tx, + ApplyFlags flags, + beast::Journal j); + /** Enum class for return value from `applyTransaction` @see applyTransaction diff --git a/include/xrpl/tx/transactors/DeleteUtils.h b/include/xrpl/tx/transactors/DeleteUtils.h new file mode 100644 index 00000000000..6bcca1fb5f8 --- /dev/null +++ b/include/xrpl/tx/transactors/DeleteUtils.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +// Define a function pointer type that can be used to delete ledger node types. +using DeleterFuncPtr = TER (*)( + ServiceRegistry& registry, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j); + +DeleterFuncPtr +nonObligationDeleter(LedgerEntryType t); + +TER +deletePreclaim( + PreclaimContext const& ctx, + std::uint32_t seqDelta, + AccountID const account, + AccountID const dest, + bool isPseudoAccount = false); + +TER +deleteDoApply( + ApplyContext& applyCtx, + STAmount const& accountBalance, + AccountID const& account, + AccountID const& dest); + +} // namespace xrpl diff --git a/include/xrpl/tx/transactors/contract/ContractCall.h b/include/xrpl/tx/transactors/contract/ContractCall.h new file mode 100644 index 00000000000..10047993ba1 --- /dev/null +++ b/include/xrpl/tx/transactors/contract/ContractCall.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +namespace xrpl { + +class ContractCall : public Transactor +{ +public: + static constexpr ConsequencesFactoryType kConsequencesFactory = ConsequencesFactoryType::Normal; + + explicit ContractCall(ApplyContext& ctx) : Transactor(ctx) + { + } + + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/transactors/contract/ContractClawback.h b/include/xrpl/tx/transactors/contract/ContractClawback.h new file mode 100644 index 00000000000..73930c06aa6 --- /dev/null +++ b/include/xrpl/tx/transactors/contract/ContractClawback.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace xrpl { + +class ContractClawback : public Transactor +{ +public: + static constexpr ConsequencesFactoryType kConsequencesFactory = ConsequencesFactoryType::Normal; + + explicit ContractClawback(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/transactors/contract/ContractCreate.h b/include/xrpl/tx/transactors/contract/ContractCreate.h new file mode 100644 index 00000000000..6ddbf3f8604 --- /dev/null +++ b/include/xrpl/tx/transactors/contract/ContractCreate.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +namespace xrpl { + +class ContractCreate : public Transactor +{ +public: + static constexpr ConsequencesFactoryType kConsequencesFactory = ConsequencesFactoryType::Normal; + + explicit ContractCreate(ApplyContext& ctx) : Transactor(ctx) + { + } + + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + + static std::uint32_t + getFlagsMask(PreflightContext const& ctx); + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/transactors/contract/ContractDelete.h b/include/xrpl/tx/transactors/contract/ContractDelete.h new file mode 100644 index 00000000000..6fb167f3418 --- /dev/null +++ b/include/xrpl/tx/transactors/contract/ContractDelete.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +namespace xrpl { + +class ContractDelete : public Transactor +{ +public: + static constexpr ConsequencesFactoryType kConsequencesFactory = ConsequencesFactoryType::Normal; + + explicit ContractDelete(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + // Interface used by DeleteAccount + static TER + deleteContract( + ApplyView& view, + std::shared_ptr const& sle, + AccountID const& account, + beast::Journal j); + + TER + doApply() override; +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/transactors/contract/ContractModify.h b/include/xrpl/tx/transactors/contract/ContractModify.h new file mode 100644 index 00000000000..24c2d27a18a --- /dev/null +++ b/include/xrpl/tx/transactors/contract/ContractModify.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +namespace xrpl { + +class ContractModify : public Transactor +{ +public: + static constexpr ConsequencesFactoryType kConsequencesFactory = ConsequencesFactoryType::Normal; + + explicit ContractModify(ApplyContext& ctx) : Transactor(ctx) + { + } + + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/transactors/contract/ContractUserDelete.h b/include/xrpl/tx/transactors/contract/ContractUserDelete.h new file mode 100644 index 00000000000..174012a866b --- /dev/null +++ b/include/xrpl/tx/transactors/contract/ContractUserDelete.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace xrpl { + +class ContractUserDelete : public Transactor +{ +public: + static constexpr ConsequencesFactoryType kConsequencesFactory = ConsequencesFactoryType::Normal; + + explicit ContractUserDelete(ApplyContext& ctx) : Transactor(ctx) + { + } + + static NotTEC + preflight(PreflightContext const& ctx); + + static TER + preclaim(PreclaimContext const& ctx); + + TER + doApply() override; +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/transactors/escrow/EscrowCreate.h b/include/xrpl/tx/transactors/escrow/EscrowCreate.h index 8682ed73693..cc8e18ff908 100644 --- a/include/xrpl/tx/transactors/escrow/EscrowCreate.h +++ b/include/xrpl/tx/transactors/escrow/EscrowCreate.h @@ -13,12 +13,21 @@ class EscrowCreate : public Transactor { } + static bool + checkExtraFeatures(PreflightContext const& ctx); + static TxConsequences makeTxConsequences(PreflightContext const& ctx); + static XRPAmount + calculateBaseFee(ReadView const& view, STTx const& tx); + static NotTEC preflight(PreflightContext const& ctx); + static NotTEC + preflightSigValidated(PreflightContext const& ctx); + static TER preclaim(PreclaimContext const& ctx); diff --git a/include/xrpl/tx/wasm/ContractContext.h b/include/xrpl/tx/wasm/ContractContext.h new file mode 100644 index 00000000000..94845c646f4 --- /dev/null +++ b/include/xrpl/tx/wasm/ContractContext.h @@ -0,0 +1,91 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace xrpl { + +struct ParameterValueVec +{ + STData const value; +}; + +struct FunctionParameterValueVecWithName +{ + Blob const name; + STData const value; +}; + +struct ParameterTypeVec +{ + STDataType const type; +}; + +std::vector +getParameterValueVec(STArray const& functionParameters); + +std::vector +getParameterTypeVec(STArray const& functionParameters); + +enum ExitType : uint8_t { + UNSET = 0, + WASM_ERROR = 1, + ROLLBACK = 2, + ACCEPT = 3, +}; + +struct ContractResult +{ + uint256 const contractHash; // Hash of the contract code + Keylet const contractKeylet; // Keylet for the contract instance + Keylet const contractSourceKeylet; // Keylet for the contract source + Keylet const contractAccountKeylet; // Keylet for the contract account + AccountID const contractAccount; // AccountID of the contract account + std::uint32_t nextSequence; // Next sequence number for the contract account + AccountID const otxnAccount; // AccountID for the originating transaction + uint256 const otxnId; // ID for the originating transaction + std::string exitReason{""}; + int64_t exitCode{-1}; + ContractDataMap dataMap; + ContractEventMap eventMap; + std::queue> emittedTxns{}; + std::size_t changedDataCount{0}; +}; + +struct ContractContext +{ + ApplyContext& applyCtx; + std::vector instanceParameters; + std::vector functionParameters; + std::vector built_txns; + int64_t expected_etxn_count{-1}; // expected emitted transaction count + std::map nonce_used{}; // nonces used in this execution + uint32_t generation = 0; // generation of the contract being executed + uint64_t burden = 0; // computational burden used + ContractResult result; + + /// Persistent view used to track cumulative state from emitted + /// transactions so that successive emits within the same WASM + /// execution see the correct sequence numbers, balances, etc. + std::optional emitView; + + /// Return the emit view, lazily creating it on first use. + /// The view is layered on top of the transactor's ApplyViewImpl + /// (applyCtx.view()) so that reads automatically fall through to + /// the transactor's pending state (e.g. the tfSendAmount balance + /// transfer, consumed sequence number, paid fee) without needing + /// to manually copy SLE changes. + OpenView& + getEmitView() + { + if (!emitView) + emitView.emplace(static_cast(&applyCtx.view())); + return *emitView; + } +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/wasm/ContractHostFuncImpl.h b/include/xrpl/tx/wasm/ContractHostFuncImpl.h new file mode 100644 index 00000000000..a243bde6479 --- /dev/null +++ b/include/xrpl/tx/wasm/ContractHostFuncImpl.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include + +namespace xrpl { +class ContractHostFunctionsImpl : public WasmHostFunctionsImpl +{ + ContractContext& contractCtx; + uint256 const contractId = contractCtx.result.contractKeylet.key; + +public: + // Constructor for contract-specific functionality + ContractHostFunctionsImpl(ContractContext& contractContext) + : WasmHostFunctionsImpl(contractContext.applyCtx, contractContext.result.contractKeylet) + , contractCtx(contractContext) + { + } + + // Expected + // getFieldBytesFromSTData(xrpl::STData const& funcParam, std::uint32_t + // stTypeId); + + Expected + instanceParam(std::uint32_t index, std::uint32_t stTypeId) override; + + Expected + functionParam(std::uint32_t index, std::uint32_t stTypeId) override; + + Expected + getDataObjectField(AccountID const& account, std::string_view const& key) override; + + Expected + getDataNestedObjectField( + AccountID const& account, + std::string_view const& key, + std::string_view const& nestedKey) override; + + Expected + getDataArrayElementField(AccountID const& account, size_t index, std::string_view const& key) + override; + + Expected + getDataNestedArrayElementField( + AccountID const& account, + std::string_view const& key, + size_t index, + std::string_view const& nestedKey) override; + + Expected + setDataObjectField( + AccountID const& account, + std::string_view const& key, + STJson::Value const& value) override; + + Expected + setDataNestedObjectField( + AccountID const& account, + std::string_view const& nestedKey, + std::string_view const& key, + STJson::Value const& value) override; + + Expected + setDataArrayElementField( + AccountID const& account, + size_t index, + std::string_view const& key, + STJson::Value const& value) override; + + Expected + setDataNestedArrayElementField( + AccountID const& account, + std::string_view const& key, + size_t index, + std::string_view const& nestedKey, + STJson::Value const& value) override; + + Expected + buildTxn(std::uint16_t const& txType) override; + + Expected + addTxnField(std::uint32_t const& index, SField const& field, Slice const& data) override; + + Expected + emitBuiltTxn(std::uint32_t const& index) override; + + Expected + emitTxn(std::shared_ptr const& stxPtr) override; + + Expected + emitEvent(std::string_view const& eventName, STJson const& eventData) override; +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/wasm/HostFunc.h b/include/xrpl/tx/wasm/HostFunc.h new file mode 100644 index 00000000000..4ed9e7b3661 --- /dev/null +++ b/include/xrpl/tx/wasm/HostFunc.h @@ -0,0 +1,619 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +enum class HostFunctionError : int32_t { + SUCCESS = 0, + INTERNAL = -1, + FIELD_NOT_FOUND = -2, + BUFFER_TOO_SMALL = -3, + NO_ARRAY = -4, + NOT_LEAF_FIELD = -5, + LOCATOR_MALFORMED = -6, + SLOT_OUT_RANGE = -7, + SLOTS_FULL = -8, + EMPTY_SLOT = -9, + LEDGER_OBJ_NOT_FOUND = -10, + DECODING = -11, + DATA_FIELD_TOO_LARGE = -12, + POINTER_OUT_OF_BOUNDS = -13, + NO_MEM_EXPORTED = -14, + INVALID_PARAMS = -15, + INVALID_ACCOUNT = -16, + INVALID_FIELD = -17, + INDEX_OUT_OF_BOUNDS = -18, + FLOAT_INPUT_MALFORMED = -19, + FLOAT_COMPUTATION_ERROR = -20, + NO_RUNTIME = -21, + OUT_OF_GAS = -22, + SUBMIT_TXN_FAILURE = -23, + INVALID_STATE = -24, +}; + +inline int32_t +HfErrorToInt(HostFunctionError e) +{ + return static_cast(e); +} + +namespace wasm_float { + +std::string +floatToString(Slice const& data); + +Expected +floatFromIntImpl(int64_t x, int32_t mode); + +Expected +floatFromUintImpl(uint64_t x, int32_t mode); + +Expected +floatSetImpl(int64_t mantissa, int32_t exponent, int32_t mode); + +Expected +floatCompareImpl(Slice const& x, Slice const& y); + +Expected +floatAddImpl(Slice const& x, Slice const& y, int32_t mode); + +Expected +floatSubtractImpl(Slice const& x, Slice const& y, int32_t mode); + +Expected +floatMultiplyImpl(Slice const& x, Slice const& y, int32_t mode); + +Expected +floatDivideImpl(Slice const& x, Slice const& y, int32_t mode); + +Expected +floatRootImpl(Slice const& x, int32_t n, int32_t mode); + +Expected +floatPowerImpl(Slice const& x, int32_t n, int32_t mode); + +Expected +floatLogImpl(Slice const& x, int32_t mode); + +} // namespace wasm_float + +struct HostFunctions +{ + beast::Journal j_; + + HostFunctions(beast::Journal j = beast::Journal{beast::Journal::getNullSink()}) : j_(j) + { + } + + // LCOV_EXCL_START + virtual void + setRT(void const*) + { + } + + virtual void const* + getRT() const + { + return nullptr; + } + + beast::Journal + getJournal() const + { + return j_; + } + + virtual bool + checkSelf() const + { + return true; + } + + virtual Expected + getLedgerSqn() const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getParentLedgerTime() const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getParentLedgerHash() const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getBaseFee() const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + isAmendmentEnabled(uint256 const& amendmentId) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + isAmendmentEnabled(std::string_view const& amendmentName) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getTxField(SField const& fname) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getCurrentLedgerObjField(SField const& fname) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getLedgerObjField(int32_t cacheIdx, SField const& fname) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getTxNestedField(Slice const& locator) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getCurrentLedgerObjNestedField(Slice const& locator) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getTxArrayLen(SField const& fname) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getCurrentLedgerObjArrayLen(SField const& fname) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getTxNestedArrayLen(Slice const& locator) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getCurrentLedgerObjNestedArrayLen(Slice const& locator) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + updateData(Slice const& data) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + computeSha512HalfHash(Slice const& data) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + accountKeylet(AccountID const& account) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + ammKeylet(Asset const& issue1, Asset const& issue2) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + checkKeylet(AccountID const& account, std::uint32_t seq) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType) + const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + didKeylet(AccountID const& account) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + delegateKeylet(AccountID const& account, AccountID const& authorize) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + depositPreauthKeylet(AccountID const& account, AccountID const& authorize) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + escrowKeylet(AccountID const& account, std::uint32_t seq) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + lineKeylet(AccountID const& account1, AccountID const& account2, Currency const& currency) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + mptIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + mptokenKeylet(MPTID const& mptid, AccountID const& holder) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + nftOfferKeylet(AccountID const& account, std::uint32_t seq) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + offerKeylet(AccountID const& account, std::uint32_t seq) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + oracleKeylet(AccountID const& account, std::uint32_t docId) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + paychanKeylet(AccountID const& account, AccountID const& destination, std::uint32_t seq) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + signersKeylet(AccountID const& account) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + ticketKeylet(AccountID const& account, std::uint32_t seq) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + vaultKeylet(AccountID const& account, std::uint32_t seq) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getNFT(AccountID const& account, uint256 const& nftId) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getNFTIssuer(uint256 const& nftId) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getNFTTaxon(uint256 const& nftId) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getNFTFlags(uint256 const& nftId) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getNFTTransferFee(uint256 const& nftId) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getNFTSerial(uint256 const& nftId) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + trace(std::string_view const& msg, Slice const& data, bool asHex) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + traceNum(std::string_view const& msg, int64_t data) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + traceAccount(std::string_view const& msg, AccountID const& account) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + traceFloat(std::string_view const& msg, Slice const& data) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + traceAmount(std::string_view const& msg, STAmount const& amount) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatFromInt(int64_t x, int32_t mode) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatFromUint(uint64_t x, int32_t mode) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatSet(int64_t mantissa, int32_t exponent, int32_t mode) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatCompare(Slice const& x, Slice const& y) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatAdd(Slice const& x, Slice const& y, int32_t mode) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatSubtract(Slice const& x, Slice const& y, int32_t mode) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatMultiply(Slice const& x, Slice const& y, int32_t mode) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatDivide(Slice const& x, Slice const& y, int32_t mode) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatRoot(Slice const& x, int32_t n, int32_t mode) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatPower(Slice const& x, int32_t n, int32_t mode) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + floatLog(Slice const& x, int32_t mode) const + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + instanceParam(std::uint32_t index, std::uint32_t stTypeId) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + functionParam(std::uint32_t index, std::uint32_t stTypeId) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getDataObjectField(AccountID const& account, std::string_view const& key) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getDataNestedObjectField( + AccountID const& account, + std::string_view const& key, + std::string_view const& nestedKey) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getDataArrayElementField(AccountID const& account, size_t index, std::string_view const& key) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + getDataNestedArrayElementField( + AccountID const& account, + std::string_view const& key, + size_t index, + std::string_view const& nestedKey) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + setDataObjectField( + AccountID const& account, + std::string_view const& keyName, + STJson::Value const& value) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + setDataNestedObjectField( + AccountID const& account, + std::string_view const& nestedKey, + std::string_view const& key, + STJson::Value const& value) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + setDataArrayElementField( + AccountID const& account, + size_t index, + std::string_view const& key, + STJson::Value const& value) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + setDataNestedArrayElementField( + AccountID const& account, + std::string_view const& key, + size_t index, + std::string_view const& nestedKey, + STJson::Value const& value) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + buildTxn(std::uint16_t const& txType) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + addTxnField(std::uint32_t const& index, SField const& field, Slice const& data) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + emitBuiltTxn(std::uint32_t const& index) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + emitTxn(std::shared_ptr const& stxPtr) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual Expected + emitEvent(std::string_view const& eventName, STJson const& eventData) + { + return Unexpected(HostFunctionError::INTERNAL); + } + + virtual ~HostFunctions() = default; + // LCOV_EXCL_STOP +}; + +} // namespace xrpl diff --git a/include/xrpl/tx/wasm/HostFuncImpl.h b/include/xrpl/tx/wasm/HostFuncImpl.h new file mode 100644 index 00000000000..6992e1f5231 --- /dev/null +++ b/include/xrpl/tx/wasm/HostFuncImpl.h @@ -0,0 +1,295 @@ +#pragma once + +#include +#include + +namespace xrpl { +class WasmHostFunctionsImpl : public HostFunctions +{ + ApplyContext& ctx_; + + Keylet leKey_; + mutable std::optional> currentLedgerObj_; + + static int constexpr MAX_CACHE = 256; + std::array, MAX_CACHE> cache_; + + std::optional data_; + + void const* rt_ = nullptr; + + Expected, HostFunctionError> + getCurrentLedgerObj() const + { + if (!currentLedgerObj_) + currentLedgerObj_ = ctx_.view().read(leKey_); + if (*currentLedgerObj_) + return *currentLedgerObj_; + return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND); + } + + Expected + normalizeCacheIndex(int32_t cacheIdx) const + { + --cacheIdx; + if (cacheIdx < 0 || cacheIdx >= MAX_CACHE) + return Unexpected(HostFunctionError::SLOT_OUT_RANGE); + if (!cache_[cacheIdx]) + return Unexpected(HostFunctionError::EMPTY_SLOT); + 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::severities::kTrace)) + return; + auto j = getJournal().trace(); +#endif + j << "WasmTrace[" << to_short_string(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) + { + } + + virtual void + setRT(void const* rt) override + { + rt_ = rt; + } + + virtual void const* + getRT() const override + { + return rt_; + } + + virtual bool + checkSelf() const override + { + return !currentLedgerObj_ && !data_ && + std::ranges::find_if(cache_, [](auto& p) { return !!p; }) == cache_.end(); + } + + std::optional const& + getData() const + { + return data_; + } + + Expected + getLedgerSqn() const override; + + Expected + getParentLedgerTime() const override; + + Expected + getParentLedgerHash() const override; + + Expected + getBaseFee() const override; + + Expected + isAmendmentEnabled(uint256 const& amendmentId) const override; + + Expected + isAmendmentEnabled(std::string_view const& amendmentName) const override; + + Expected + cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) override; + + Expected + getTxField(SField const& fname) const override; + + Expected + getCurrentLedgerObjField(SField const& fname) const override; + + Expected + getLedgerObjField(int32_t cacheIdx, SField const& fname) const override; + + Expected + getTxNestedField(Slice const& locator) const override; + + Expected + getCurrentLedgerObjNestedField(Slice const& locator) const override; + + Expected + getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator) const override; + + Expected + getTxArrayLen(SField const& fname) const override; + + Expected + getCurrentLedgerObjArrayLen(SField const& fname) const override; + + Expected + getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const override; + + Expected + getTxNestedArrayLen(Slice const& locator) const override; + + Expected + getCurrentLedgerObjNestedArrayLen(Slice const& locator) const override; + + Expected + getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator) const override; + + Expected + updateData(Slice const& data) override; + + Expected + checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey) + const override; + + Expected + computeSha512HalfHash(Slice const& data) const override; + + Expected + accountKeylet(AccountID const& account) const override; + + Expected + ammKeylet(Asset const& issue1, Asset const& issue2) const override; + + Expected + checkKeylet(AccountID const& account, std::uint32_t seq) const override; + + Expected + credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType) + const override; + + Expected + didKeylet(AccountID const& account) const override; + + Expected + delegateKeylet(AccountID const& account, AccountID const& authorize) const override; + + Expected + depositPreauthKeylet(AccountID const& account, AccountID const& authorize) const override; + + Expected + escrowKeylet(AccountID const& account, std::uint32_t seq) const override; + + Expected + lineKeylet(AccountID const& account1, AccountID const& account2, Currency const& currency) + const override; + + Expected + mptIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const override; + + Expected + mptokenKeylet(MPTID const& mptid, AccountID const& holder) const override; + + Expected + nftOfferKeylet(AccountID const& account, std::uint32_t seq) const override; + + Expected + offerKeylet(AccountID const& account, std::uint32_t seq) const override; + + Expected + oracleKeylet(AccountID const& account, std::uint32_t docId) const override; + + Expected + paychanKeylet(AccountID const& account, AccountID const& destination, std::uint32_t seq) + const override; + + Expected + permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const override; + + Expected + signersKeylet(AccountID const& account) const override; + + Expected + ticketKeylet(AccountID const& account, std::uint32_t seq) const override; + + Expected + vaultKeylet(AccountID const& account, std::uint32_t seq) const override; + + Expected + getNFT(AccountID const& account, uint256 const& nftId) const override; + + Expected + getNFTIssuer(uint256 const& nftId) const override; + + Expected + getNFTTaxon(uint256 const& nftId) const override; + + Expected + getNFTFlags(uint256 const& nftId) const override; + + Expected + getNFTTransferFee(uint256 const& nftId) const override; + + Expected + getNFTSerial(uint256 const& nftId) const override; + + Expected + trace(std::string_view const& msg, Slice const& data, bool asHex) const override; + + Expected + traceNum(std::string_view const& msg, int64_t data) const override; + + Expected + traceAccount(std::string_view const& msg, AccountID const& account) const override; + + Expected + traceFloat(std::string_view const& msg, Slice const& data) const override; + + Expected + traceAmount(std::string_view const& msg, STAmount const& amount) const override; + + Expected + floatFromInt(int64_t x, int32_t mode) const override; + + Expected + floatFromUint(uint64_t x, int32_t mode) const override; + + Expected + floatSet(int64_t mantissa, int32_t exponent, int32_t mode) const override; + + Expected + floatCompare(Slice const& x, Slice const& y) const override; + + Expected + floatAdd(Slice const& x, Slice const& y, int32_t mode) const override; + + Expected + floatSubtract(Slice const& x, Slice const& y, int32_t mode) const override; + + Expected + floatMultiply(Slice const& x, Slice const& y, int32_t mode) const override; + + Expected + floatDivide(Slice const& x, Slice const& y, int32_t mode) const override; + + Expected + floatRoot(Slice const& x, int32_t n, int32_t mode) const override; + + Expected + floatPower(Slice const& x, int32_t n, int32_t mode) const override; + + Expected + floatLog(Slice const& x, int32_t mode) const override; +}; + +namespace wasm_float { + +// The range for the mantissa and exponent when normalized +static std::int64_t constexpr wasmMinMantissa = 1'000'000'000'000'000ll; +static std::int64_t constexpr wasmMaxMantissa = wasmMinMantissa * 10 - 1; +static int constexpr wasmMinExponent = -96; +static int constexpr wasmMaxExponent = 80; + +} // namespace wasm_float + +} // namespace xrpl diff --git a/include/xrpl/tx/wasm/HostFuncWrapper.h b/include/xrpl/tx/wasm/HostFuncWrapper.h new file mode 100644 index 00000000000..ad849ffe7f3 --- /dev/null +++ b/include/xrpl/tx/wasm/HostFuncWrapper.h @@ -0,0 +1,395 @@ +#pragma once + +#include + +namespace xrpl { + +using getLedgerSqn_proto = int32_t(uint8_t*, int32_t); +wasm_trap_t* +getLedgerSqn_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getParentLedgerTime_proto = int32_t(uint8_t*, int32_t); +wasm_trap_t* +getParentLedgerTime_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getParentLedgerHash_proto = int32_t(uint8_t*, int32_t); +wasm_trap_t* +getParentLedgerHash_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getBaseFee_proto = int32_t(uint8_t*, int32_t); +wasm_trap_t* +getBaseFee_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using isAmendmentEnabled_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* +isAmendmentEnabled_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using cacheLedgerObj_proto = int32_t(uint8_t const*, int32_t, int32_t); +wasm_trap_t* +cacheLedgerObj_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getTxField_proto = int32_t(int32_t, uint8_t*, int32_t); +wasm_trap_t* +getTxField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getCurrentLedgerObjField_proto = int32_t(int32_t, uint8_t*, int32_t); +wasm_trap_t* +getCurrentLedgerObjField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getLedgerObjField_proto = int32_t(int32_t, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getLedgerObjField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getTxNestedField_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getTxNestedField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getCurrentLedgerObjNestedField_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getCurrentLedgerObjNestedField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getLedgerObjNestedField_proto = int32_t(int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getLedgerObjNestedField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getTxArrayLen_proto = int32_t(int32_t); +wasm_trap_t* +getTxArrayLen_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getCurrentLedgerObjArrayLen_proto = int32_t(int32_t); +wasm_trap_t* +getCurrentLedgerObjArrayLen_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getLedgerObjArrayLen_proto = int32_t(int32_t, int32_t); +wasm_trap_t* +getLedgerObjArrayLen_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getTxNestedArrayLen_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* +getTxNestedArrayLen_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getCurrentLedgerObjNestedArrayLen_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* +getCurrentLedgerObjNestedArrayLen_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using getLedgerObjNestedArrayLen_proto = int32_t(int32_t, uint8_t const*, int32_t); +wasm_trap_t* +getLedgerObjNestedArrayLen_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using updateData_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* +updateData_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using computeSha512HalfHash_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +computeSha512HalfHash_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using accountKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +accountKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using ammKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +ammKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using checkKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +checkKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using delegateKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +delegateKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using depositPreauthKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +depositPreauthKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using didKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +didKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using escrowKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +escrowKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using lineKeylet_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* +lineKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using mptIssuanceKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +mptIssuanceKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using mptokenKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +mptokenKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using nftOfferKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +nftOfferKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using offerKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +offerKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using oracleKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +oracleKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using paychanKeylet_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* +paychanKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using permissionedDomainKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +permissionedDomainKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using signersKeylet_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +signersKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using ticketKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +ticketKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using vaultKeylet_proto = + int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +vaultKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getNFT_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getNFT_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getNFTIssuer_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getNFTIssuer_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getNFTTaxon_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getNFTTaxon_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getNFTFlags_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* +getNFTFlags_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getNFTTransferFee_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* +getNFTTransferFee_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getNFTSerial_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getNFTSerial_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using trace_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t, int32_t); +wasm_trap_t* +trace_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using traceNum_proto = int32_t(uint8_t const*, int32_t, int64_t); +wasm_trap_t* +traceNum_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using traceAccount_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t); +wasm_trap_t* +traceAccount_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using traceFloat_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t); +wasm_trap_t* +traceFloat_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using traceAmount_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t); +wasm_trap_t* +traceAmount_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using floatFromInt_proto = int32_t(int64_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* +floatFromInt_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using floatFromUint_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* +floatFromUint_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using floatSet_proto = int32_t(int32_t, int64_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* +floatSet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using floatCompare_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t); +wasm_trap_t* +floatCompare_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using floatRoot_proto = int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* +floatRoot_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using floatPower_proto = int32_t(uint8_t const*, int32_t, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* +floatPower_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using floatLog_proto = int32_t(uint8_t const*, int32_t, uint8_t*, int32_t, int32_t); +wasm_trap_t* +floatLog_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +// Contract-specific host function wrappers + +using instanceParam_proto = int32_t(int32_t, int32_t, uint8_t*, int32_t); +wasm_trap_t* +instanceParam_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using functionParam_proto = int32_t(int32_t, int32_t, uint8_t*, int32_t); +wasm_trap_t* +functionParam_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getDataObjectField_proto = + int32_t(uint8_t*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getDataObjectField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getDataNestedObjectField_proto = + int32_t(uint8_t*, int32_t, uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getDataNestedObjectField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using setDataObjectField_proto = + int32_t(uint8_t*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +setDataObjectField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using setDataNestedObjectField_proto = + int32_t(uint8_t*, int32_t, uint8_t const*, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +setDataNestedObjectField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getDataArrayElementField_proto = + int32_t(uint8_t*, int32_t, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +getDataArrayElementField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using getDataNestedArrayElementField_proto = int32_t( + uint8_t*, + int32_t, + uint8_t const*, + int32_t, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* +getDataNestedArrayElementField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using setDataArrayElementField_proto = + int32_t(uint8_t*, int32_t, int32_t, uint8_t const*, int32_t, uint8_t*, int32_t); +wasm_trap_t* +setDataArrayElementField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using setDataNestedArrayElementField_proto = int32_t( + uint8_t*, + int32_t, + uint8_t const*, + int32_t, + int32_t, + uint8_t const*, + int32_t, + uint8_t*, + int32_t); +wasm_trap_t* +setDataNestedArrayElementField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results); + +using buildTxn_proto = int32_t(int32_t); +wasm_trap_t* +buildTxn_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using addTxnField_proto = int32_t(int32_t, int32_t, uint8_t const*, int32_t); +wasm_trap_t* +addTxnField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using emitBuiltTxn_proto = int32_t(int32_t); +wasm_trap_t* +emitBuiltTxn_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using emitTxn_proto = int32_t(uint8_t const*, int32_t); +wasm_trap_t* +emitTxn_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +using emitEvent_proto = int32_t(uint8_t const*, int32_t, uint8_t const*, int32_t); +wasm_trap_t* +emitEvent_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results); + +} // namespace xrpl diff --git a/include/xrpl/tx/wasm/ParamsHelper.h b/include/xrpl/tx/wasm/ParamsHelper.h new file mode 100644 index 00000000000..ca019c2d7fa --- /dev/null +++ b/include/xrpl/tx/wasm/ParamsHelper.h @@ -0,0 +1,269 @@ +#pragma once + +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace bft = boost::function_types; + +namespace xrpl { + +using Bytes = std::vector; +using Hash = xrpl::uint256; + +struct wmem +{ + std::uint8_t* p = nullptr; + std::size_t s = 0; +}; + +template +struct WasmResult +{ + T result; + int64_t cost; +}; +typedef WasmResult EscrowResult; +typedef WasmResult WasmRunResult; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +enum WasmTypes { WT_I32, WT_I64, WT_U8V }; + +struct WasmImportFunc +{ + std::string name; + std::optional result; + std::vector params; + // void* udata = nullptr; + // wasm_func_callback_with_env_t + void* wrap = nullptr; + uint32_t gas = 0; +}; + +typedef std::pair WasmUserData; +typedef std::vector ImportVec; + +#define WASM_IMPORT_FUNC(v, f, ...) \ + WasmImpFunc(v, #f, reinterpret_cast(&f##_wrap), ##__VA_ARGS__) + +#define WASM_IMPORT_FUNC2(v, f, n, ...) \ + WasmImpFunc(v, n, reinterpret_cast(&f##_wrap), ##__VA_ARGS__) + +template +void +WasmImpArgs(WasmImportFunc& e) +{ + if constexpr (N < C) + { + using at = typename boost::mpl::at_c::type; + if constexpr (std::is_pointer_v) + e.params.push_back(WT_I32); + else if constexpr (std::is_same_v) + e.params.push_back(WT_I32); + else if constexpr (std::is_same_v) + e.params.push_back(WT_I64); + else + static_assert(std::is_pointer_v, "Unsupported argument type"); + + return WasmImpArgs(e); + } + return; +} + +template +void +WasmImpRet(WasmImportFunc& e) +{ + if constexpr (std::is_pointer_v) + e.result = WT_I32; + else if constexpr (std::is_same_v) + e.result = WT_I32; + else if constexpr (std::is_same_v) + e.result = WT_I64; + else if constexpr (std::is_void_v) + e.result.reset(); +#if (defined(__GNUC__) && (__GNUC__ >= 14)) || \ + ((defined(__clang_major__)) && (__clang_major__ >= 18)) + else + static_assert(false, "Unsupported return type"); +#endif +} + +template +void +WasmImpFuncHelper(WasmImportFunc& e) +{ + using rt = typename bft::result_type::type; + using pt = typename 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)); +} + +template +void +WasmImpFunc( + ImportVec& v, + std::string_view imp_name, + void* f_wrap, + void* data = nullptr, + uint32_t gas = 0) +{ + WasmImportFunc e; + e.name = imp_name; + e.wrap = f_wrap; + e.gas = gas; + WasmImpFuncHelper(e); + v.push_back(std::make_pair(data, std::move(e))); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +struct WasmParamVec +{ + std::uint8_t const* d = nullptr; + std::int32_t sz = 0; +}; + +struct WasmParam +{ + WasmTypes type = WT_I32; + union + { + std::int32_t i32; + std::int64_t i64 = 0; + float f32; + double f64; + WasmParamVec u8v; + } of; +}; + +template +inline void +wasmParamsHlp(std::vector& v, std::int32_t p, Types&&... args) +{ + v.push_back({.type = WT_I32, .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 = WT_I64, .of = {.i64 = p}}); + wasmParamsHlp(v, std::forward(args)...); +} + +// We are not supporting float/double for now +// Leaving this code here so that it is easier to add later if needed +// template +// inline void +// wasmParamsHlp(std::vector& v, float p, Types&&... args) +// { +// v.push_back({.type = WT_F32, .of = {.f32 = p}}); +// wasmParamsHlp(v, std::forward(args)...); +// } + +// template +// inline void +// wasmParamsHlp(std::vector& v, double p, Types&&... args) +// { +// v.push_back({.type = WT_F64, .of = {.f64 = p}}); +// wasmParamsHlp(v, std::forward(args)...); +// } + +template +inline void +wasmParamsHlp(std::vector& v, std::uint8_t const* dt, std::int32_t sz, Types&&... args) +{ + v.push_back({.type = WT_U8V, .of = {.u8v = {.d = dt, .sz = sz}}}); + wasmParamsHlp(v, std::forward(args)...); +} + +template +inline void +wasmParamsHlp(std::vector& v, Bytes const& p, Types&&... args) +{ + wasmParamsHlp(v, p.data(), static_cast(p.size()), std::forward(args)...); +} + +template +inline void +wasmParamsHlp(std::vector& v, std::string_view const& p, Types&&... args) +{ + wasmParamsHlp( + v, + reinterpret_cast(p.data()), + static_cast(p.size()), + std::forward(args)...); +} + +template +inline void +wasmParamsHlp(std::vector& v, std::string const& p, Types&&... args) +{ + wasmParamsHlp( + v, + reinterpret_cast(p.c_str()), + static_cast(p.size()), + std::forward(args)...); +} + +inline void +wasmParamsHlp(std::vector& v) +{ + return; +} + +template +inline std::vector +wasmParams(Types&&... args) +{ + std::vector v; + v.reserve(sizeof...(args)); + wasmParamsHlp(v, std::forward(args)...); + return v; +} + +template +inline constexpr T +adjustWasmEndianessHlp(T x) +{ + static_assert(std::is_integral::value, "Only integral types"); + if constexpr (size > 1) + { + using U = std::make_unsigned::type; + 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 +inline constexpr T +adjustWasmEndianess(T x) +{ + // LCOV_EXCL_START + static_assert(std::is_integral::value, "Only integral types"); + if constexpr (std::endian::native == std::endian::big) + { + return adjustWasmEndianessHlp(x); + } + return x; + // LCOV_EXCL_STOP +} + +} // namespace xrpl diff --git a/include/xrpl/tx/wasm/README.md b/include/xrpl/tx/wasm/README.md new file mode 100644 index 00000000000..3275011b497 --- /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 + `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 +`ESCROW_FUNCTION_NAME` (currently `"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 Expected +myNewFunction(ParamType1 param1, ParamType2 param2) +{ + return Unexpected(HostFunctionError::INTERNAL); +} +``` + +### 2. Add to HostFuncImpl.h (Declaration) + +Add the method override declaration in `WasmHostFunctionsImpl`: + +```cpp +Expected +myNewFunction(ParamType1 param1, ParamType2 param2) override; +``` + +### 3. Implement in detail/HostFuncImpl\*.cpp + +Add the implementation in the appropriate file: + +```cpp +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/WasmVM.h b/include/xrpl/tx/wasm/WasmVM.h new file mode 100644 index 00000000000..b872adfc954 --- /dev/null +++ b/include/xrpl/tx/wasm/WasmVM.h @@ -0,0 +1,90 @@ +#pragma once + +#include + +#include + +namespace xrpl { + +static std::string_view const W_ENV = "env"; +static std::string_view const W_HOST_LIB = "host_lib"; +static std::string_view const W_MEM = "memory"; +static std::string_view const W_STORE = "store"; +static std::string_view const W_LOAD = "load"; +static std::string_view const W_SIZE = "size"; +static std::string_view const W_ALLOC = "allocate"; +static std::string_view const W_DEALLOC = "deallocate"; +static std::string_view const W_PROC_EXIT = "proc_exit"; + +static std::string_view const ESCROW_FUNCTION_NAME = "finish"; + +uint32_t const MAX_PAGES = 128; // 8MB = 64KB*128 + +class WasmiEngine; + +class WasmEngine +{ + std::unique_ptr const impl_; + + WasmEngine(); + + WasmEngine(WasmEngine const&) = delete; + WasmEngine(WasmEngine&&) = delete; + WasmEngine& + operator=(WasmEngine const&) = delete; + WasmEngine& + operator=(WasmEngine&&) = delete; + +public: + ~WasmEngine() = default; + + static WasmEngine& + instance(); + + Expected, TER> + 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()); + + beast::Journal + getJournal() const; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +ImportVec +createWasmImport(HostFunctions& hfs); + +Expected +runEscrowWasm( + Bytes const& wasmCode, + HostFunctions& hfs, + int64_t gasLimit, + std::string_view funcName = ESCROW_FUNCTION_NAME, + std::vector const& params = {}); + +NotTEC +preflightEscrowWasm( + Bytes const& wasmCode, + HostFunctions& hfs, + std::string_view funcName = ESCROW_FUNCTION_NAME, + 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 00000000000..daa139cad64 --- /dev/null +++ b/include/xrpl/tx/wasm/WasmiVM.h @@ -0,0 +1,321 @@ +#pragma once + +#include + +#include +#include + +namespace xrpl { + +template +struct WasmVec +{ + T vec_; + + 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; + } +}; + +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; + bool f{false}; // failure flag + + WasmiResult(unsigned N = 0) : r(N) + { + } + + ~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; + +struct InstanceWrapper +{ + wasm_store_t* store_ = nullptr; + WasmExternVec exports_; + mutable int memIdx_ = -1; + InstancePtr instance_; + beast::Journal j_ = beast::Journal(beast::Journal::getNullSink()); + +private: + static InstancePtr + init( + StorePtr& s, + ModulePtr& m, + WasmExternVec& expt, + WasmExternVec const& imports, + beast::Journal j); + +public: + InstanceWrapper(); + + InstanceWrapper(InstanceWrapper&& o); + + InstanceWrapper& + operator=(InstanceWrapper&& o); + + InstanceWrapper(StorePtr& s, ModulePtr& m, WasmExternVec const& imports, beast::Journal j); + + ~InstanceWrapper() = default; + + operator bool() const; + + 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; +}; + +struct ModuleWrapper +{ + ModulePtr module_; + InstanceWrapper instanceWrap_; + WasmExporttypeVec exportTypes_; + beast::Journal j_ = beast::Journal(beast::Journal::getNullSink()); + +private: + static ModulePtr + init(StorePtr& s, Bytes const& wasmBin, beast::Journal j); + +public: + ModuleWrapper(); + ModuleWrapper(ModuleWrapper&& o); + ModuleWrapper& + operator=(ModuleWrapper&& o); + ModuleWrapper( + StorePtr& s, + Bytes const& wasmBin, + bool instantiate, + ImportVec const& imports, + beast::Journal j); + ~ModuleWrapper() = default; + + operator bool() const; + + FuncInfo + getFunc(std::string_view funcName) const; + + wasm_functype_t* + getFuncType(std::string_view funcName) const; + + wmem + getMem() const; + + InstanceWrapper const& + getInstance(int i = 0) const; + + int + addInstance(StorePtr& s, WasmExternVec const& imports); + + std::int64_t + getGas() const; + +private: + 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(); + ~WasmiEngine() = default; + + static EnginePtr + init(); + + Expected, TER> + 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); + + std::int64_t + getGas() const; + + // Host functions helper functionality + wasm_trap_t* + newTrap(std::string const& msg); + + beast::Journal + getJournal() const; + +private: + InstanceWrapper const& + getRT(int m = 0, int i = 0) const; + + wmem + getMem() const; + + Expected, TER> + runHlp( + Bytes const& wasmCode, + HostFunctions& hfs, + int64_t gas, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports); + + NotTEC + checkHlp( + Bytes const& wasmCode, + HostFunctions& hfs, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports); + + 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 = {}); + + FuncInfo + getFunc(std::string_view funcName) const; + + static std::vector + convertParams(std::vector const& params); + + static int + compareParamTypes(wasm_valtype_vec_t const* ftp, std::vector const& p); + + static void + add_param(std::vector& in, int32_t p); + static void + add_param(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/basics/Number.cpp b/src/libxrpl/basics/Number.cpp index 06bd78d8b02..eb4ea6dd98c 100644 --- a/src/libxrpl/basics/Number.cpp +++ b/src/libxrpl/basics/Number.cpp @@ -947,6 +947,81 @@ power(Number const& f, unsigned n) return r; } +// Series expansion method approximation of ln(x) +static Number +ln(Number const& x, int iterations = 50) +{ + static Number const N0(0); + static Number const N2(2, 0); + static Number const N05(5, -1); + static Number const LN2(693'147'180'559'945'309ll, -18); + + if (x <= 0) + { + throw std::runtime_error("Not a positive value"); + } + if (x == 1) + { + return N0; + } + + int exponent = 0; + Number mantissa = x; + + while (mantissa >= N2) + { + mantissa /= 2; + exponent += 1; + } + while (mantissa < N05) + { + mantissa *= 2; + exponent -= 1; + } + + Number z = (mantissa - 1) / (mantissa + 1); + Number const zz = z * z; + Number sum; + + for (int i = 1; i <= iterations; ++i) + { + sum = sum + z / ((2 * i) - 1); + z = z * zz; + } + + return 2 * sum + exponent * LN2; +} + +Number +log10(Number const& x, int iterations) +{ + static Number const N0(0); + static Number const LN10(2'302'585'092'994'046ll, -15); + + if (x <= 0) + { + throw std::runtime_error("Not a positive value"); + } + if (x == 1) + { + return N0; + } + + if (x <= Number(10)) + { + auto const r = ln(x, iterations) / LN10; + return r; + } + + // (1 <= normalX < 10) + // ln(x) = ln(normalX * 10^norm) = ln(normalX) + norm * ln(10) + int const diffExp = 15 + x.exponent(); + Number const normalX = x / Number(1, diffExp); + auto const lnX = ln(normalX, iterations) + diffExp * LN10; + auto const lgX = lnX / LN10; + return lgX; +} + // Returns f^(1/d) // Uses Newton–Raphson iterations until the result stops changing // to find the non-negative root of the polynomial g(x) = x^d - f diff --git a/src/libxrpl/ledger/ApplyStateTable.cpp b/src/libxrpl/ledger/ApplyStateTable.cpp index 70fa0aef5dc..7c98d63d952 100644 --- a/src/libxrpl/ledger/ApplyStateTable.cpp +++ b/src/libxrpl/ledger/ApplyStateTable.cpp @@ -115,6 +115,8 @@ ApplyStateTable::apply( TER ter, std::optional const& deliver, std::optional const& parentBatchId, + std::optional const& gasUsed, + std::optional const& wasmReturnCode, bool isDryRun, beast::Journal j) { @@ -129,6 +131,8 @@ ApplyStateTable::apply( meta.setDeliveredAmount(deliver); meta.setParentBatchID(parentBatchId); + meta.setGasUsed(gasUsed); + meta.setWasmReturnCode(wasmReturnCode); Mods newMod; for (auto& item : items_) diff --git a/src/libxrpl/ledger/ApplyViewImpl.cpp b/src/libxrpl/ledger/ApplyViewImpl.cpp index 9650190a3ef..26218af7594 100644 --- a/src/libxrpl/ledger/ApplyViewImpl.cpp +++ b/src/libxrpl/ledger/ApplyViewImpl.cpp @@ -31,7 +31,8 @@ ApplyViewImpl::apply( bool isDryRun, beast::Journal j) { - return items_.apply(to, tx, ter, deliver_, parentBatchId, isDryRun, j); + return items_.apply( + to, tx, ter, deliver_, parentBatchId, gasUsed_, wasmReturnCode_, isDryRun, j); } std::size_t diff --git a/src/libxrpl/ledger/Ledger.cpp b/src/libxrpl/ledger/Ledger.cpp index fe7db9a1585..4b083b74307 100644 --- a/src/libxrpl/ledger/Ledger.cpp +++ b/src/libxrpl/ledger/Ledger.cpp @@ -198,6 +198,12 @@ Ledger::Ledger( sle->at(sfReserveIncrement) = *f; sle->at(sfReferenceFeeUnits) = kFeeUnitsDeprecated; } + if (std::find(amendments.begin(), amendments.end(), featureSmartEscrow) != amendments.end()) + { + sle->at(sfExtensionComputeLimit) = fees.extensionComputeLimit; + sle->at(sfExtensionSizeLimit) = fees.extensionSizeLimit; + sle->at(sfGasPrice) = fees.gasPrice; + } rawInsert(sle); } @@ -564,6 +570,7 @@ Ledger::setup() { bool oldFees = false; bool newFees = false; + bool extensionFees = false; { auto const baseFee = sle->at(~sfBaseFee); auto const reserveBase = sle->at(~sfReserveBase); @@ -580,6 +587,7 @@ Ledger::setup() auto const baseFeeXRP = sle->at(~sfBaseFeeDrops); auto const reserveBaseXRP = sle->at(~sfReserveBaseDrops); auto const reserveIncrementXRP = sle->at(~sfReserveIncrementDrops); + auto assign = [&ret](XRPAmount& dest, std::optional const& src) { if (src) { @@ -598,6 +606,22 @@ Ledger::setup() assign(fees_.increment, reserveIncrementXRP); newFees = baseFeeXRP || reserveBaseXRP || reserveIncrementXRP; } + { + auto const extensionComputeLimit = sle->at(~sfExtensionComputeLimit); + auto const extensionSizeLimit = sle->at(~sfExtensionSizeLimit); + auto const gasPrice = sle->at(~sfGasPrice); + + auto assign = [](std::uint32_t& dest, std::optional const& src) { + if (src) + { + dest = src.value(); + } + }; + assign(fees_.extensionComputeLimit, extensionComputeLimit); + assign(fees_.extensionSizeLimit, extensionSizeLimit); + assign(fees_.gasPrice, gasPrice); + extensionFees = extensionComputeLimit || extensionSizeLimit || gasPrice; + } if (oldFees && newFees) { // Should be all of one or the other, but not both @@ -608,6 +632,12 @@ Ledger::setup() // Can't populate the new fees before the amendment is enabled ret = false; } + if (!rules_.enabled(featureSmartEscrow) && extensionFees) + { + // Can't populate the extension fees before the amendment is + // enabled + ret = false; + } } } catch (SHAMapMissingNode const&) diff --git a/src/libxrpl/ledger/View.cpp b/src/libxrpl/ledger/View.cpp index c62d79dcac4..dfb7f391cdb 100644 --- a/src/libxrpl/ledger/View.cpp +++ b/src/libxrpl/ledger/View.cpp @@ -479,6 +479,271 @@ doWithdraw( return accountSend(view, sourceAcct, dstAcct, amount, j, WaiveTransferFee::Yes); } +static TER +canTransferIOU( + ReadView const& view, + AccountID const& sender, + AccountID const& receiver, + STAmount const& amount, + beast::Journal j, + SendIssuerHandling issuerHandling, + SendEscrowHandling escrowHandling, + SendAuthHandling authHandling, + SendFreezeHandling freezeHandling, + SendTransferHandling transferHandling, + SendBalanceHandling balanceHandling) +{ + AccountID const issuer = amount.getIssuer(); + // If the issuer is the same as the sender + if (issuerHandling == SendIssuerHandling::ihSENDER_NOT_ALLOWED && issuer == sender) + return tecNO_PERMISSION; + + // If the issuer is the same as the receiver + if (issuerHandling == SendIssuerHandling::ihRECEIVER_NOT_ALLOWED && issuer == receiver) + return tecNO_PERMISSION; + + // If the lsfAllowTrustLineLocking is not enabled + auto const sleIssuer = view.read(keylet::account(issuer)); + if (!sleIssuer) + return tecNO_ISSUER; + + if (issuerHandling != SendIssuerHandling::ihSENDER_NOT_ALLOWED && + issuerHandling != SendIssuerHandling::ihRECEIVER_NOT_ALLOWED && + !sleIssuer->isFlag(lsfDefaultRipple)) + return terNO_RIPPLE; + + if (escrowHandling == SendEscrowHandling::ehCHECK && + !sleIssuer->isFlag(lsfAllowTrustLineLocking)) + return tecNO_PERMISSION; + + // If the sender does not have a trustline to the issuer + auto const sleRippleState = view.read(keylet::line(sender, issuer, amount.getCurrency())); + + if (!sleRippleState) + return tecNO_LINE; + + STAmount const balance = (*sleRippleState)[sfBalance]; + + // If balance is positive, issuer must have higher address than sender + if (balance > beast::Zero && issuer < sender) + return tecNO_PERMISSION; // LCOV_EXCL_LINE + + // If balance is negative, issuer must have lower address than sender + if (balance < beast::Zero && issuer > sender) + return tecNO_PERMISSION; // LCOV_EXCL_LINE + + // // If the account trustline has no-ripple set for the issuer + // if (auto const ter = requireNoRipple(ctx.view, amount.issue(), account); + // ter != tesSUCCESS) + // return ter; + + // // If the dest trustline has no-ripple set for the issuer + // if (auto const ter = requireNoRipple(ctx.view, amount.issue(), dest); + // ter != tesSUCCESS) + // return ter; + + // If the issuer has requireAuth set, check if the sender is authorized + if (authHandling == SendAuthHandling::ahCHECK_SENDER || + authHandling == SendAuthHandling::ahBOTH) + { + if (auto const ter = requireAuth(view, amount.issue(), sender); ter != tesSUCCESS) + return ter; + } + + // If the issuer has requireAuth set, check if the receiver is authorized + if (authHandling == SendAuthHandling::ahCHECK_RECEIVER || + authHandling == SendAuthHandling::ahBOTH) + { + if (auto const ter = requireAuth(view, amount.issue(), receiver); ter != tesSUCCESS) + return ter; + } + + // If the issuer has frozen the sender + if ((freezeHandling == SendFreezeHandling::fhCHECK_SENDER || + freezeHandling == SendFreezeHandling::fhBOTH) && + isFrozen(view, sender, amount.issue())) + return tecFROZEN; + + // If the issuer has frozen the receiver + if ((freezeHandling == SendFreezeHandling::fhCHECK_RECEIVER || + freezeHandling == SendFreezeHandling::fhBOTH) && + isFrozen(view, receiver, amount.issue())) + return tecFROZEN; + + if (balanceHandling == SendBalanceHandling::bhIGNORE) + return tesSUCCESS; + + STAmount const spendableAmount = accountHolds( + view, + sender, + amount.get(), + fhIGNORE_FREEZE, // already checked freeze above + ahIGNORE_AUTH, // already checked auth above + j); + + // If the balance is less than or equal to 0 + if (spendableAmount <= beast::Zero) + return tecINSUFFICIENT_FUNDS; + + // If the spendable amount is less than the amount + if (spendableAmount < amount) + return tecINSUFFICIENT_FUNDS; + + // If the amount is not addable to the balance + if (!canAdd(spendableAmount, amount)) + return tecPRECISION_LOSS; + + return tesSUCCESS; +} + +static TER +canTransferMPT( + ReadView const& view, + AccountID const& sender, + AccountID const& receiver, + STAmount const& amount, + beast::Journal j, + SendIssuerHandling issuerHandling, + SendEscrowHandling escrowHandling, + SendAuthHandling authHandling, + SendFreezeHandling freezeHandling, + SendTransferHandling transferHandling, + SendBalanceHandling balanceHandling) +{ + AccountID const issuer = amount.getIssuer(); + // If the issuer is the same as the sender + if (issuerHandling == SendIssuerHandling::ihSENDER_NOT_ALLOWED && issuer == sender) + return tecNO_PERMISSION; + + // If the issuer is the same as the receiver + if (issuerHandling == SendIssuerHandling::ihRECEIVER_NOT_ALLOWED && issuer == receiver) + return tecNO_PERMISSION; + + // If the mpt does not exist + auto const issuanceKey = keylet::mptIssuance(amount.get().getMptID()); + auto const sleIssuance = view.read(issuanceKey); + if (!sleIssuance) + return tecOBJECT_NOT_FOUND; + + // If the lsfMPTCanEscrow is not enabled + if (escrowHandling == SendEscrowHandling::ehCHECK && !sleIssuance->isFlag(lsfMPTCanEscrow)) + return tecNO_PERMISSION; + + // If the issuer is not the same as the issuer of the mpt + if (sleIssuance->getAccountID(sfIssuer) != issuer) + return tecNO_PERMISSION; // LCOV_EXCL_LINE + + // If the sender does not have the mpt + if (!view.exists(keylet::mptoken(issuanceKey.key, sender))) + return tecOBJECT_NOT_FOUND; + + auto const& mptIssue = amount.get(); + + // If the issuer has requireAuth set, check if the sender is authorized + if (authHandling == SendAuthHandling::ahCHECK_SENDER || + authHandling == SendAuthHandling::ahBOTH) + { + if (auto const ter = requireAuth(view, mptIssue, sender, AuthType::WeakAuth); + ter != tesSUCCESS) + return ter; + } + + // If the issuer has requireAuth set, check if the receiver is authorized + if (authHandling == SendAuthHandling::ahCHECK_RECEIVER || + authHandling == SendAuthHandling::ahBOTH) + { + if (auto const ter = requireAuth(view, mptIssue, receiver, AuthType::WeakAuth); + ter != tesSUCCESS) + return ter; + } + + // If the issuer has frozen the sender, return tecLOCKED + if ((freezeHandling == SendFreezeHandling::fhCHECK_SENDER || + freezeHandling == SendFreezeHandling::fhBOTH) && + isFrozen(view, sender, mptIssue)) + return tecLOCKED; + + // If the issuer has frozen the receiver, return tecLOCKED + if ((freezeHandling == SendFreezeHandling::fhCHECK_RECEIVER || + freezeHandling == SendFreezeHandling::fhBOTH) && + isFrozen(view, receiver, mptIssue)) + return tecLOCKED; + + // If the mpt cannot be transferred, return tecNO_AUTH + if (transferHandling == SendTransferHandling::thCHECK) + { + if (auto const ter = canTransfer(view, mptIssue, sender, receiver); ter != tesSUCCESS) + return ter; + } + + if (balanceHandling == SendBalanceHandling::bhIGNORE) + return tesSUCCESS; + + STAmount const spendableAmount = accountHolds( + view, + sender, + amount.get(), + fhIGNORE_FREEZE, // already checked freeze above + ahIGNORE_AUTH, // already checked auth above + j); + + // If the balance is less than or equal to 0, return tecINSUFFICIENT_FUNDS + if (spendableAmount <= beast::Zero) + return tecINSUFFICIENT_FUNDS; + + // If the spendable amount is less than the amount, return + // tecINSUFFICIENT_FUNDS + if (spendableAmount < amount) + return tecINSUFFICIENT_FUNDS; + + return tesSUCCESS; +} + +TER +canTransferFT( + ReadView const& view, + AccountID const& sender, + AccountID const& receiver, + STAmount const& amount, + beast::Journal j, + SendIssuerHandling issuerHandling = SendIssuerHandling::ihIGNORE, + SendEscrowHandling escrowHandling = SendEscrowHandling::ehIGNORE, + SendAuthHandling authHandling = SendAuthHandling::ahBOTH, + SendFreezeHandling freezeHandling = SendFreezeHandling::fhBOTH, + SendTransferHandling transferHandling = SendTransferHandling::thIGNORE, + SendBalanceHandling balanceHandling = SendBalanceHandling::bhCHECK) +{ + return std::visit( + [&](TIss const& issue) -> TER { + if constexpr (std::is_same_v) + return canTransferIOU( + view, + sender, + receiver, + amount, + j, + issuerHandling, + escrowHandling, + authHandling, + freezeHandling, + transferHandling, + balanceHandling); + else + return canTransferMPT( + view, + sender, + receiver, + amount, + j, + issuerHandling, + escrowHandling, + authHandling, + freezeHandling, + transferHandling, + balanceHandling); + }, + amount.asset().value()); +} TER cleanupOnAccountDelete( ApplyView& view, diff --git a/src/libxrpl/ledger/helpers/ContractUtils.cpp b/src/libxrpl/ledger/helpers/ContractUtils.cpp new file mode 100644 index 00000000000..e28256684be --- /dev/null +++ b/src/libxrpl/ledger/helpers/ContractUtils.cpp @@ -0,0 +1,629 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { +namespace contract { + +struct BlobHash +{ + std::size_t + operator()(Blob const& b) const noexcept + { + if (b.empty()) + return 0; + return std::hash{}( + std::string_view(reinterpret_cast(b.data()), b.size())); + } +}; + +int64_t +contractCreateFee(uint64_t byteCount) +{ + constexpr uint64_t mul = static_cast(createByteMultiplier); + if (byteCount > std::numeric_limits::max() / mul) + return feeCalculationFailed; // overflow + uint64_t const uf = byteCount * mul; + if (uf > static_cast(std::numeric_limits::max())) + return feeCalculationFailed; + return static_cast(uf); +} + +NotTEC +preflightFunctions(STTx const& tx, beast::Journal j) +{ + // Functions must be present if ContractCode is present. + if (!tx.isFieldPresent(sfContractCode)) + return tesSUCCESS; + + if (!tx.isFieldPresent(sfFunctions)) + { + JLOG(j.trace()) << "ContractCreate/Modify: ContractCode present but " + "Functions missing."; + return temARRAY_EMPTY; + } + + auto const& functions = tx.getFieldArray(sfFunctions); + + if (functions.empty()) + { + JLOG(j.trace()) << "ContractCreate/Modify: Functions array empty."; + return temARRAY_EMPTY; + } + + // Functions must not exceed n entries. + if (functions.size() > contract::maxContractFunctions) + { + JLOG(j.trace()) << "ContractCreate/Modify: Functions array too large."; + return temARRAY_TOO_LARGE; + } + + std::unordered_set uniqueFunctions; + uniqueFunctions.reserve(functions.size()); + for (auto const& function : functions) + { + // Functions must be unique by name. + auto const& functionName = function.getFieldVL(sfFunctionName); + if (!uniqueFunctions.insert(functionName).second) + { + JLOG(j.trace()) << "Duplicate function name: " << strHex(functionName); + return temREDUNDANT; + } + + auto const& parameters = function.getFieldArray(sfParameters); + + // Function Parameters must not exceed n entries each. + if (parameters.size() > contract::maxContractParams) + { + JLOG(j.trace()) << "ContractCreate/Modify: Function Parameters " + "array is too large."; + return temARRAY_TOO_LARGE; + } + + std::unordered_set uniqueParameters; + uniqueParameters.reserve(parameters.size()); + + for (auto const& param : parameters) + { + // Function Parameter must have a flag. + if (!param.isFieldPresent(sfParameterFlag)) + { + JLOG(j.trace()) << "ContractCreate/Modify: Function Parameter " + "is missing flag."; + return temMALFORMED; + } + + // Function Parameter must have a type. + if (!param.isFieldPresent(sfParameterType)) + { + JLOG(j.trace()) << "ContractCreate/Modify: Function Parameter " + "is missing type."; + return temMALFORMED; + } + + // Function Parameter flags must be valid. + auto const flags = param.getFieldU32(sfParameterFlag); + if (flags & tfContractParameterMask) + { + JLOG(j.trace()) << "ContractCreate/Modify: Invalid parameter " + "flag in Function."; + return temINVALID_FLAG; + } + } + } + return tesSUCCESS; +} + +NotTEC +preflightInstanceParameters(STTx const& tx, beast::Journal j) +{ + if (!tx.isFieldPresent(sfInstanceParameters)) + return tesSUCCESS; + + auto const& instanceParameters = tx.getFieldArray(sfInstanceParameters); + + // InstanceParameters must not be empty. + if (instanceParameters.empty()) + { + JLOG(j.trace()) << "ContractCreate/Modify: InstanceParameters empty array."; + return temARRAY_EMPTY; + } + + // InstanceParameters must not exceed n entries. + if (instanceParameters.size() > contract::maxContractParams) + { + JLOG(j.trace()) << "ContractCreate/Modify: InstanceParameters array " + "is too large."; + return temARRAY_TOO_LARGE; + } + + std::unordered_set uniqueParameters; + uniqueParameters.reserve(instanceParameters.size()); + for (auto const& param : instanceParameters) + { + // Instance Parameter must have a flag. + if (!param.isFieldPresent(sfParameterFlag)) + { + JLOG(j.trace()) << "ContractCreate/Modify: Instance Parameter is missing flag."; + return temMALFORMED; + } + + // Instance Parameter must have a type. + if (!param.isFieldPresent(sfParameterType)) + { + JLOG(j.trace()) << "ContractCreate/Modify: Instance Parameter is missing type."; + return temMALFORMED; + } + + // Instance Parameter flags must be valid. + auto const flags = param.getFieldU32(sfParameterFlag); + if (flags & tfContractParameterMask) + { + JLOG(j.trace()) << "ContractCreate/Modify: Invalid parameter " + "flag in Instance Parameter."; + return temINVALID_FLAG; + } + } + return tesSUCCESS; +} + +bool +validateParameterMapping(STArray const& params, STArray const& values, beast::Journal j) +{ + if (params.size() != values.size()) + { + JLOG(j.trace()) << "ContractCreate/Modify: InstanceParameterValues size " + "does not match InstanceParameters size."; + return false; + } + return true; +} + +NotTEC +preflightInstanceParameterValues(STTx const& tx, beast::Journal j) +{ + if (!tx.isFieldPresent(sfInstanceParameterValues)) + return tesSUCCESS; + + auto const& instanceParameterValues = tx.getFieldArray(sfInstanceParameterValues); + + // InstanceParameters must not be empty. + if (instanceParameterValues.empty()) + { + JLOG(j.trace()) << "ContractCreate/Modify: InstanceParameterValues is missing."; + return temARRAY_EMPTY; + } + + // InstanceParameterValues must not exceed n entries. + if (instanceParameterValues.size() > contract::maxContractParams) + { + JLOG(j.trace()) << "ContractCreate/Modify: InstanceParameterValues " + "array is too large."; + return temARRAY_TOO_LARGE; + } + + std::unordered_set uniqueParameters; + uniqueParameters.reserve(instanceParameterValues.size()); + for (auto const& param : instanceParameterValues) + { + // Instance Parameter must have a flag. + if (!param.isFieldPresent(sfParameterFlag)) + { + JLOG(j.trace()) << "ContractCreate/Modify: Instance Parameter is missing flag."; + return temMALFORMED; + } + + // Instance Parameter must have a value. + if (!param.isFieldPresent(sfParameterValue)) + { + JLOG(j.trace()) << "ContractCreate/Modify: Instance Parameter is " + "missing value."; + return temMALFORMED; + } + + // Instance Parameter flags must be valid. + auto const flags = param.getFieldU32(sfParameterFlag); + if (flags & tfContractParameterMask) + { + JLOG(j.trace()) << "ContractCreate/Modify: Invalid parameter " + "flag in Instance Parameter."; + return temINVALID_FLAG; + } + } + + // Only validate the mapping if InstanceParameters are present + bool valid = true; + if (tx.isFieldPresent(sfInstanceParameters)) + valid = validateParameterMapping( + tx.getFieldArray(sfInstanceParameters), tx.getFieldArray(sfInstanceParameterValues), j); + if (!valid) + { + JLOG(j.trace()) << "ContractCreate/Modify: InstanceParameterValues do not match " + "InstanceParameters."; + return temMALFORMED; + } + + // Validate flags in InstanceParameterValues + if (auto const res = preflightFlagParameters(instanceParameterValues, j); !isTesSuccess(res)) + { + JLOG(j.trace()) << "ContractCreate/Modify: InstanceParameterValues flag " + "validation failed: " + << transToken(res); + return res; + } + + return tesSUCCESS; +} + +bool +isValidParameterFlag(std::uint32_t flags) +{ + return (flags & tfContractParameterMask) == 0; +} + +NotTEC +preflightFlagParameters(STArray const& parameters, beast::Journal j) +{ + for (auto const& param : parameters) + { + if (!param.isFieldPresent(sfParameterFlag) || + !isValidParameterFlag(param.getFieldU32(sfParameterFlag))) + continue; // Skip invalid flags + + switch (param.getFieldU32(sfParameterFlag)) + { + case tfSendAmount: { + if (!param.isFieldPresent(sfParameterValue)) + return temMALFORMED; + auto const& value = param.getFieldData(sfParameterValue); + STAmount const amount = value.getFieldAmount(); + // Preflight Transfer Amount + if (isXRP(amount)) + { + if (amount <= beast::Zero) + return temBAD_AMOUNT; + } + else if (amount.holds()) + { + if (amount.native() || amount <= beast::Zero) + return temBAD_AMOUNT; + + if (badCurrency() == amount.getCurrency()) + return temBAD_CURRENCY; + } + else if (amount.holds()) + { + if (amount.native() || + amount.mpt() > MPTAmount{std::numeric_limits::max()} || + amount <= beast::Zero) + return temBAD_AMOUNT; + } + break; + } + case tfSendNFToken: { + break; + } + case tfAuthorizeToken: { + return temDISABLED; + break; + } + } + } + return tesSUCCESS; +} + +TER +preclaimFlagParameters( + ReadView const& view, + AccountID const& sourceAccount, + AccountID const& contractAccount, + STArray const& parameters, + beast::Journal j) +{ + for (auto const& param : parameters) + { + if (!param.isFieldPresent(sfParameterFlag) || + !isValidParameterFlag(param.getFieldU32(sfParameterFlag))) + continue; // Skip invalid flags + + switch (param.getFieldU32(sfParameterFlag)) + { + case tfSendAmount: { + if (!param.isFieldPresent(sfParameterValue)) + return tecINTERNAL; + + auto const& value = param.getFieldData(sfParameterValue); + STAmount const amount = value.getFieldAmount(); + // Preclaim Transfer Amount + if (isXRP(amount)) + { + auto const accountSle = view.read(keylet::account(sourceAccount)); + if (!accountSle) + return tecINTERNAL; + + auto const& mSourceBalance = accountSle->getFieldAmount(sfBalance); + if (mSourceBalance < amount.xrp()) + return tecUNFUNDED; + } + else + { + if (auto ter = canTransferFT( + view, + sourceAccount, + contractAccount, + amount, + j, + SendIssuerHandling::ihIGNORE, + SendEscrowHandling::ehIGNORE, + SendAuthHandling::ahBOTH, + SendFreezeHandling::fhBOTH, + SendTransferHandling::thIGNORE, + SendBalanceHandling::bhCHECK)) + { + JLOG(j.trace()) << "preclaimFlagParameters: Cannot " + "transfer amount: " + << amount; + return ter; + } + } + break; + } + case tfSendNFToken: { + if (!param.isFieldPresent(sfParameterValue)) + return tecINTERNAL; + auto const& value = param.getFieldData(sfParameterValue); + auto const& nftokenID = value.getFieldH256(); + // Preclaim Transfer NFT Token + if (!nft::findToken(view, sourceAccount, nftokenID)) + { + JLOG(j.trace()) + << "preclaimFlagParameters: Cannot transfer NFT token: " << nftokenID; + return tecNO_ENTRY; + } + break; + } + case tfAuthorizeToken: { + break; + } + } + } + return tesSUCCESS; +} + +TER +doApplyFlagParameters( + ApplyView& view, + STTx const& tx, + AccountID const& sourceAccount, + AccountID const& contractAccount, + STArray const& parameters, + XRPAmount const& priorBalance, + beast::Journal j) +{ + for (auto const& param : parameters) + { + if (!param.isFieldPresent(sfParameterFlag) || + !isValidParameterFlag(param.getFieldU32(sfParameterFlag))) + continue; // Skip invalid flags + + switch (param.getFieldU32(sfParameterFlag)) + { + case tfSendAmount: { + if (!param.isFieldPresent(sfParameterValue)) + return tecINTERNAL; + + auto const& value = param.getFieldData(sfParameterValue); + STAmount const amount = value.getFieldAmount(); + if (auto ter = accountSend( + view, sourceAccount, contractAccount, amount, j, WaiveTransferFee::No); + !isTesSuccess(ter)) + { + JLOG(j.trace()) << "doApplyFlagParameters: Failed to send amount: " << amount; + return ter; + } + break; + } + case tfSendNFToken: { + if (!param.isFieldPresent(sfParameterValue)) + return tecINTERNAL; + auto const& value = param.getFieldData(sfParameterValue); + auto const& nftokenID = value.getFieldH256(); + if (auto ter = + nft::transferNFToken(view, sourceAccount, contractAccount, nftokenID); + !isTesSuccess(ter)) + { + JLOG(j.trace()) + << "doApplyFlagParameters: Failed to send NFT token: " << nftokenID; + return ter; + } + break; + } + case tfAuthorizeToken: { + return tecINTERNAL; + // if (!param.isFieldPresent(sfParameterValue)) + // return tecINTERNAL; + // // Handle tfAuthorizeToken if needed + // auto const& value = param.getFieldData(sfParameterValue); + // STAmount limit = value.getFieldAmount(); + // Asset asset = Asset{limit.issue()}; + // if (auto ter = canAddHolding(view, asset); + // !isTesSuccess(ter)) + // { + // JLOG(j.trace()) << "doApplyFlagParameters: Cannot add " + // "holding for asset: " + // << to_string(asset); + // return ter; + // } + // // Set the issuer to the contract account for the holding + // limit.setIssuer(contractAccount); + // if (auto ter = addEmptyHolding( + // view, contractAccount, priorBalance, asset, limit, + // j); + // !isTesSuccess(ter)) + // { + // JLOG(j.trace()) << "doApplyFlagParameters: Failed to add + // " + // "holding for asset: " + // << to_string(asset); + // return ter; + // } + // break; + } + } + } + return tesSUCCESS; +} + +uint32_t +contractDataReserve(uint32_t size) +{ + // Divide by dataByteMultiplier and round up to the nearest whole number + return (size + dataByteMultiplier - 1U) / dataByteMultiplier; +} + +TER +setContractData( + ServiceRegistry& registry, + ApplyView& view, + AccountID const& account, + AccountID const& contractAccount, + STJson const& data) +{ + auto const j = registry.getJournal("View"); + auto const sleAccount = view.peek(keylet::account(account)); + if (!sleAccount) + return tefINTERNAL; + + // if the blob is too large don't set it + if (data.size() > maxContractDataSize) + return temARRAY_TOO_LARGE; + + auto dataKeylet = keylet::contractData(account, contractAccount); + auto dataSle = view.peek(dataKeylet); + + // DELETE + if (data.size() == 0) + { + if (!dataSle) + return tesSUCCESS; + + uint32_t const oldDataReserve = + contractDataReserve(dataSle->getFieldJson(sfContractJson).size()); + + std::uint64_t const page = (*dataSle)[sfOwnerNode]; + // Remove the page from the account directory + if (!view.dirRemove(keylet::ownerDir(account), page, dataKeylet.key, false)) + return tefBAD_LEDGER; + + // remove the actual contract data sle + view.erase(dataSle); + + // reduce the owner count + adjustOwnerCount(view, sleAccount, -oldDataReserve, j); + return tesSUCCESS; + } + + std::uint32_t const ownerCount{(*sleAccount)[sfOwnerCount]}; + bool const createNew = !dataSle; + if (createNew) + { + // CREATE + uint32_t const dataReserve = contractDataReserve(data.size()); + uint32_t const newReserve = ownerCount + dataReserve; + XRPAmount const newReserveAmount{view.fees().accountReserve(newReserve)}; + if (STAmount((*sleAccount)[sfBalance]).xrp() < newReserveAmount) + return tecINSUFFICIENT_RESERVE; + + adjustOwnerCount(view, sleAccount, dataReserve, j); + // create an entry + dataSle = std::make_shared(dataKeylet); + dataSle->setFieldJson(sfContractJson, data); + dataSle->setAccountID(sfOwner, account); + dataSle->setAccountID(sfContractAccount, contractAccount); + + auto const page = + view.dirInsert(keylet::ownerDir(account), dataKeylet.key, describeOwnerDir(account)); + if (!page) + return tecDIR_FULL; + + dataSle->setFieldU64(sfOwnerNode, *page); + + // add new data to ledger + view.insert(dataSle); + } + else + { + // UPDATE + uint32_t const oldDataReserve = + contractDataReserve(dataSle->getFieldJson(sfContractJson).size()); + uint32_t const newDataReserve = contractDataReserve(data.size()); + if (newDataReserve != oldDataReserve) + { + // if the reserve changes, we need to adjust the owner count + uint32_t const newReserve = ownerCount - oldDataReserve + newDataReserve; + XRPAmount const newReserveAmount{view.fees().accountReserve(newReserve)}; + if (STAmount((*sleAccount)[sfBalance]).xrp() < newReserveAmount) + return tecINSUFFICIENT_RESERVE; + + adjustOwnerCount(view, sleAccount, newReserve, j); + } + + // update the data + dataSle->setFieldJson(sfContractJson, data); + view.update(dataSle); + } + return tesSUCCESS; +} + +TER +finalizeContractData( + ServiceRegistry& registry, + ApplyView& view, + AccountID const& contractAccount, + ContractDataMap const& dataMap, + ContractEventMap const& eventMap, + uint256 const& txnID) +{ + auto const& j = registry.getJournal("View"); + uint16_t changeCount = 0; + + for (auto const& [name, data] : eventMap) + registry.getOPs().pubContractEvent(name, data); + + for (auto const& accEntry : dataMap) + { + auto const& acc = accEntry.first; + auto const& cacheEntry = accEntry.second; + bool const is_modified = cacheEntry.first; + auto const& jsonData = cacheEntry.second; + if (is_modified) + { + changeCount++; + if (changeCount > maxDataModifications) + { + // overflow + JLOG(j.trace()) << "ContractError[TX:" << txnID + << "]: SetContractData failed: Too many data changes"; + return tecWASM_REJECTED; + } + + TER result = setContractData(registry, view, acc, contractAccount, jsonData); + if (!isTesSuccess(result)) + { + JLOG(j.warn()) << "ContractError[TX:" << txnID + << "]: SetContractData failed: " << result << " Account: " << acc; + return result; + } + } + } + return tesSUCCESS; +} + +} // namespace contract +} // namespace xrpl diff --git a/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp index bb784278ba2..d0a4e6d6f39 100644 --- a/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp +++ b/src/libxrpl/ledger/helpers/NFTokenHelpers.cpp @@ -2,11 +2,14 @@ #include #include +#include #include #include #include #include +#include #include +#include #include #include #include @@ -1095,4 +1098,55 @@ checkTrustlineDeepFrozen( return tesSUCCESS; } +TER +transferNFToken( + ApplyView& view, + AccountID const& buyer, + AccountID const& seller, + uint256 const& nftokenID) +{ + auto tokenAndPage = nft::findTokenAndPage(view, seller, nftokenID); + + if (!tokenAndPage) + return tecINTERNAL; // LCOV_EXCL_LINE + + if (auto const ret = nft::removeToken(view, seller, nftokenID, tokenAndPage->page); + !isTesSuccess(ret)) + return ret; + + auto const sleBuyer = view.read(keylet::account(buyer)); + if (!sleBuyer) + return tecINTERNAL; // LCOV_EXCL_LINE + + std::uint32_t const buyerOwnerCountBefore = sleBuyer->getFieldU32(sfOwnerCount); + + auto const insertRet = nft::insertToken(view, buyer, std::move(tokenAndPage->token)); + + // if fixNFTokenReserve is enabled, check if the buyer has sufficient + // reserve to own a new object, if their OwnerCount changed. + // + // There was an issue where the buyer accepts a sell offer, the ledger + // didn't check if the buyer has enough reserve, meaning that buyer can get + // NFTs free of reserve. + if (view.rules().enabled(fixNFTokenReserve)) + { + // To check if there is sufficient reserve, we cannot use mPriorBalance + // because NFT is sold for a price. So we must use the balance after + // the deduction of the potential offer price. A small caveat here is + // that the balance has already deducted the transaction fee, meaning + // that the reserve requirement is a few drops higher. + auto const buyerBalance = sleBuyer->getFieldAmount(sfBalance); + + auto const buyerOwnerCountAfter = sleBuyer->getFieldU32(sfOwnerCount); + if (buyerOwnerCountAfter > buyerOwnerCountBefore) + { + if (auto const reserve = view.fees().accountReserve(buyerOwnerCountAfter); + buyerBalance < reserve) + return tecINSUFFICIENT_RESERVE; + } + } + + return insertRet; +} + } // namespace xrpl::nft diff --git a/src/libxrpl/protocol/Emitable.cpp b/src/libxrpl/protocol/Emitable.cpp new file mode 100644 index 00000000000..06bf6feb7f4 --- /dev/null +++ b/src/libxrpl/protocol/Emitable.cpp @@ -0,0 +1,167 @@ +#include +#include +#include +#include +#include + +namespace xrpl { + +Emitable::Emitable() +{ + emitableTx_ = { +#pragma push_macro("TRANSACTION") +#undef TRANSACTION + +#define TRANSACTION(tag, value, name, delegatable, amendment, permissions, emitable, fields) \ + {value, emitable}, +#include + +#undef TRANSACTION +#pragma pop_macro("TRANSACTION") + }; + + granularEmitableMap_ = { +#pragma push_macro("EMITABLE") +#undef EMITABLE + +#define EMITABLE(type, txType, value) {#type, type}, + +#include + +#undef EMITABLE +#pragma pop_macro("EMITABLE") + }; + + granularNameMap_ = { +#pragma push_macro("EMITABLE") +#undef EMITABLE + +#define EMITABLE(type, txType, value) {type, #type}, + +#include + +#undef EMITABLE +#pragma pop_macro("EMITABLE") + }; + + granularTxTypeMap_ = { +#pragma push_macro("EMITABLE") +#undef EMITABLE + +#define EMITABLE(type, txType, value) {type, txType}, + +#include + +#undef EMITABLE +#pragma pop_macro("EMITABLE") + }; + + for ([[maybe_unused]] auto const& emitable : granularEmitableMap_) + XRPL_ASSERT( + emitable.second > UINT16_MAX, + "xrpl::Emitable::granularEmitableMap_ : granular emitable " + "value must not exceed the maximum uint16_t value."); +} + +Emitable const& +Emitable::getInstance() +{ + static Emitable const instance; + return instance; +} + +std::optional +Emitable::getEmitableName(std::uint32_t const value) const +{ + auto const emitableValue = static_cast(value); + if (auto const granular = getGranularName(emitableValue)) + return granular; + + // not a granular emitable, check if it maps to a transaction type + auto const txType = emitableToTxType(value); + if (auto const* item = TxFormats::getInstance().findByType(txType); item != nullptr) + return item->getName(); + + return std::nullopt; +} + +std::optional +Emitable::getGranularValue(std::string const& name) const +{ + auto const it = granularEmitableMap_.find(name); + if (it != granularEmitableMap_.end()) + return static_cast(it->second); + + return std::nullopt; +} + +std::optional +Emitable::getGranularName(GranularEmitableType const& value) const +{ + auto const it = granularNameMap_.find(value); + if (it != granularNameMap_.end()) + return it->second; + + return std::nullopt; +} + +std::optional +Emitable::getGranularTxType(GranularEmitableType const& gpType) const +{ + auto const it = granularTxTypeMap_.find(gpType); + if (it != granularTxTypeMap_.end()) + return it->second; + + return std::nullopt; +} + +bool +Emitable::isEmitable(std::uint32_t const& emitableValue) const +{ + auto const granularEmitable = getGranularName(static_cast(emitableValue)); + if (granularEmitable) + // granular emitables are always allowed to be delegated + return true; + + auto const txType = emitableToTxType(emitableValue); + auto const it = emitableTx_.find(txType); + + // if (rules.enabled(fixDelegateV1_1)) + // { + // if (it == delegatableTx_.end()) + // return false; + + // auto const txFeaturesIt = txFeatureMap_.find(txType); + // XRPL_ASSERT( + // txFeaturesIt != txFeatureMap_.end(), + // "xrpl::Emitables::isDelegatable : tx exists in txFeatureMap_"); + + // // fixDelegateV1_1: Delegation is only allowed if the required + // amendment + // // for the transaction is enabled. For transactions that do not + // require + // // an amendment, delegation is always allowed. + // if (txFeaturesIt->second != uint256{} && + // !rules.enabled(txFeaturesIt->second)) + // return false; + // } + + if (it != emitableTx_.end() && it->second == Emittance::notEmitable) + return false; + + return true; +} + +uint32_t +Emitable::txToEmitableType(TxType const& type) const +{ + return static_cast(type) + 1; +} + +TxType +Emitable::emitableToTxType(uint32_t const& value) const +{ + return static_cast(value - 1); +} + +} // namespace xrpl diff --git a/src/libxrpl/protocol/Indexes.cpp b/src/libxrpl/protocol/Indexes.cpp index ae29bd32975..7333f790b25 100644 --- a/src/libxrpl/protocol/Indexes.cpp +++ b/src/libxrpl/protocol/Indexes.cpp @@ -84,6 +84,9 @@ enum class LedgerNameSpace : std::uint16_t { Vault = 'V', LoanBroker = 'l', // lower-case L Loan = 'L', + ContractSource = 'Z', + ContractNs = 'z', + ContractData = 'b', // No longer used or supported. Left here to reserve the space to avoid accidental reuse. Contract [[deprecated]] = 'c', @@ -577,6 +580,24 @@ permissionedDomain(uint256 const& domainID) noexcept return {ltPERMISSIONED_DOMAIN, domainID}; } +Keylet +contractSource(uint256 const& contractHash) noexcept +{ + return {ltCONTRACT_SOURCE, indexHash(LedgerNameSpace::ContractSource, contractHash)}; +} + +Keylet +contract(uint256 const& contractHash, AccountID const& owner, std::uint32_t seq) noexcept +{ + return {ltCONTRACT, indexHash(LedgerNameSpace::ContractNs, contractHash, owner, seq)}; +} + +Keylet +contractData(AccountID const& owner, AccountID const& contractAccount) noexcept +{ + return {ltCONTRACT_DATA, indexHash(LedgerNameSpace::ContractData, owner, contractAccount)}; +} + } // namespace keylet } // namespace xrpl diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index 66b2822a421..9766ab73f88 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -160,6 +160,35 @@ InnerObjectFormats::InnerObjectFormats() {sfTxnSignature, SoeOptional}, {sfSigners, SoeOptional}, }); + + add(sfFunction.jsonName, + sfFunction.getCode(), + { + {sfFunctionName, SoeRequired}, + {sfParameters, SoeOptional}, + }); + + add(sfInstanceParameter.jsonName, + sfInstanceParameter.getCode(), + { + {sfParameterFlag, SoeRequired}, + {sfParameterType, SoeRequired}, + }); + + add(sfInstanceParameterValue.jsonName, + sfInstanceParameterValue.getCode(), + { + {sfParameterFlag, SoeRequired}, + {sfParameterValue, SoeRequired}, + }); + + add(sfParameter.jsonName, + sfParameter.getCode(), + { + {sfParameterFlag, SoeOptional}, + {sfParameterType, SoeOptional}, + {sfParameterValue, SoeOptional}, + }); } InnerObjectFormats const& diff --git a/src/libxrpl/protocol/STData.cpp b/src/libxrpl/protocol/STData.cpp new file mode 100644 index 00000000000..93f5b817234 --- /dev/null +++ b/src/libxrpl/protocol/STData.cpp @@ -0,0 +1,968 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl { + +template +constexpr std::enable_if_t::value && std::is_signed::value, U> +to_unsigned(S value) +{ + if (value < 0 || std::numeric_limits::max() < value) + Throw("Value out of range"); + return static_cast(value); +} + +template +constexpr std::enable_if_t::value && std::is_unsigned::value, U1> +to_unsigned(U2 value) +{ + if (std::numeric_limits::max() < value) + Throw("Value out of range"); + return static_cast(value); +} + +// TODO +STData::STData(SField const& n) : STBase(n), inner_type_(STI_NOTPRESENT), data_(STBase{}) +{ +} + +STData::STData(SField const& n, unsigned char v) + : STBase(n) + , inner_type_(STI_UINT8) + , data_(detail::STVar(detail::gDefaultObject, sfCloseResolution)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, std::uint16_t v) + : STBase(n) + , inner_type_(STI_UINT16) + , data_(detail::STVar(detail::gDefaultObject, sfSignerWeight)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, std::uint32_t v) + : STBase(n), inner_type_(STI_UINT32), data_(detail::STVar(detail::gDefaultObject, sfNetworkID)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, std::uint64_t v) + : STBase(n), inner_type_(STI_UINT64), data_(detail::STVar(detail::gDefaultObject, sfIndexNext)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, uint128 const& v) + : STBase(n), inner_type_(STI_UINT128), data_(detail::STVar(detail::gDefaultObject, sfEmailHash)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, uint160 const& v) + : STBase(n) + , inner_type_(STI_UINT160) + , data_(detail::STVar(detail::gDefaultObject, sfTakerPaysCurrency)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, uint192 const& v) + : STBase(n) + , inner_type_(STI_UINT192) + , data_(detail::STVar(detail::gDefaultObject, sfMPTokenIssuanceID)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, uint256 const& v) + : STBase(n), inner_type_(STI_UINT256), data_(detail::STVar(detail::gDefaultObject, sfLedgerHash)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, Blob const& v) + : STBase(n), inner_type_(STI_VL), data_(detail::STVar(detail::gDefaultObject, sfURI)) +{ + setFieldUsingSetValue(Buffer(v.data(), v.size())); +} + +STData::STData(SField const& n, Slice const& v) + : STBase(n), inner_type_(STI_VL), data_(detail::STVar(detail::gDefaultObject, sfURI)) +{ + setFieldUsingSetValue(Buffer(v.data(), v.size())); +} + +STData::STData(SField const& n, STAmount const& v) + : STBase(n), inner_type_(STI_AMOUNT), data_(detail::STVar(detail::gDefaultObject, sfAmount)) +{ + setFieldUsingAssignment(v); +} + +STData::STData(SField const& n, AccountID const& v) + : STBase(n), inner_type_(STI_ACCOUNT), data_(detail::STVar(detail::gDefaultObject, sfAccount)) +{ + setFieldUsingSetValue(v); +} + +STData::STData(SField const& n, STIssue const& v) + : STBase(n), inner_type_(STI_ISSUE), data_(detail::STVar(detail::gDefaultObject, sfAsset)) +{ + setFieldUsingAssignment(v); +} + +STData::STData(SField const& n, STCurrency const& v) + : STBase(n), inner_type_(STI_CURRENCY), data_(detail::STVar(detail::gDefaultObject, sfBaseAsset)) +{ + setFieldUsingAssignment(v); +} + +STData::STData(SField const& n, STNumber const& v) + : STBase(n), inner_type_(STI_NUMBER), data_(detail::STVar(detail::gDefaultObject, sfNumber)) +{ + setFieldUsingAssignment(v); +} + +STData::STData(SerialIter& sit, SField const& name) : STBase(name), data_(STBase{}) +{ + std::uint16_t const stype = SerializedTypeID(sit.get16()); + inner_type_ = stype; + SerializedTypeID const s = static_cast(stype); + switch (s) + { + case STI_UINT8: { + data_ = detail::STVar(sit, sfCloseResolution); + break; + } + case STI_UINT16: { + data_ = detail::STVar(sit, sfSignerWeight); + break; + } + case STI_UINT32: { + data_ = detail::STVar(sit, sfNetworkID); + break; + } + case STI_UINT64: { + data_ = detail::STVar(sit, sfIndexNext); + break; + } + case STI_UINT128: { + data_ = detail::STVar(sit, sfEmailHash); + break; + } + case STI_UINT160: { + data_ = detail::STVar(sit, sfTakerPaysCurrency); + break; + } + case STI_UINT192: { + data_ = detail::STVar(sit, sfMPTokenIssuanceID); + break; + } + case STI_UINT256: { + data_ = detail::STVar(sit, sfLedgerHash); + break; + } + case STI_VL: { + data_ = detail::STVar(sit, sfURI); + break; + } + case STI_AMOUNT: { + data_ = detail::STVar(sit, sfAmount); + break; + } + case STI_ACCOUNT: { + data_ = detail::STVar(sit, sfAccount); + break; + } + case STI_ISSUE: { + data_ = detail::STVar(sit, sfAsset); + break; + } + case STI_CURRENCY: { + data_ = detail::STVar(sit, sfBaseAsset); + break; + } + case STI_NUMBER: { + data_ = detail::STVar(sit, sfNumber); + break; + } + default: + Throw("STData: unknown type"); + } +} + +STBase* +STData::copy(std::size_t n, void* buf) const +{ + return emplace(n, buf, *this); +} + +STBase* +STData::move(std::size_t n, void* buf) +{ + return emplace(n, buf, std::move(*this)); +} + +std::size_t +STData::size() const +{ + switch (static_cast(inner_type_)) + { + case STI_UINT8: { + return sizeof(uint8_t); + } + case STI_UINT16: { + return sizeof(uint16_t); + } + case STI_UINT32: { + return sizeof(uint32_t); + } + case STI_UINT64: { + return sizeof(uint64_t); + } + case STI_UINT128: { + return uint128::size(); + } + case STI_UINT160: { + return uint160::size(); + } + case STI_UINT192: { + return uint192::size(); + } + case STI_UINT256: { + return uint256::size(); + } + case STI_VL: { + STBlob const& st_blob = data_.get().downcast(); + return st_blob.size(); + } + case STI_AMOUNT: { + // TODO: STAmount::size() + STAmount const& st_amt = data_.get().downcast(); + return st_amt.native() ? 8 : 48; + } + case STI_ACCOUNT: { + return uint160::size(); + } + case STI_ISSUE: { + // const STIssue& st_issue = data_.get().downcast(); + return 40; // 20 bytes for currency + 20 bytes for account + } + case STI_CURRENCY: { + // const STCurrency& st_currency = + // data_.get().downcast(); + return 20; // 20 bytes for currency + } + case STI_NUMBER: { + return sizeof(double); + } + default: + Throw("STData: unknown type"); + } +} + +SerializedTypeID +STData::getSType() const +{ + return STI_DATA; +} + +void +STData::add(Serializer& s) const +{ + s.add16(inner_type_); + + switch (static_cast(inner_type_)) + { + case STI_UINT8: { + STUInt8 const& st_uint8 = data_.get().downcast(); + st_uint8.add(s); + break; + } + case STI_UINT16: { + STUInt16 const& st_uint16 = data_.get().downcast(); + st_uint16.add(s); + break; + } + case STI_UINT32: { + STUInt32 const& st_uint32 = data_.get().downcast(); + st_uint32.add(s); + break; + } + case STI_UINT64: { + STUInt64 const& st_uint64 = data_.get().downcast(); + st_uint64.add(s); + break; + } + case STI_UINT128: { + STUInt128 const& st_uint128 = data_.get().downcast(); + st_uint128.add(s); + break; + } + case STI_UINT160: { + STUInt160 const& st_uint160 = data_.get().downcast(); + st_uint160.add(s); + break; + } + case STI_UINT192: { + STUInt192 const& st_uint192 = data_.get().downcast(); + st_uint192.add(s); + break; + } + case STI_UINT256: { + STUInt256 const& st_uint256 = data_.get().downcast(); + st_uint256.add(s); + break; + } + case STI_VL: { + STBlob const& st_blob = data_.get().downcast(); + st_blob.add(s); + break; + } + case STI_AMOUNT: { + STAmount const& st_amt = data_.get().downcast(); + st_amt.add(s); + break; + } + case STI_ACCOUNT: { + STAccount const& st_acc = data_.get().downcast(); + st_acc.add(s); + break; + } + case STI_ISSUE: { + STIssue const& st_issue = data_.get().downcast(); + st_issue.add(s); + break; + } + case STI_CURRENCY: { + STCurrency const& st_currency = data_.get().downcast(); + st_currency.add(s); + break; + } + case STI_NUMBER: { + STNumber const& st_number = data_.get().downcast(); + st_number.add(s); + break; + } + default: + Throw("STData: unknown type"); + } +} + +bool +STData::isEquivalent(STBase const& t) const +{ + auto const* const tPtr = dynamic_cast(&t); + return tPtr && (default_ == tPtr->default_) && (inner_type_ == tPtr->inner_type_) && + (data_ == tPtr->data_); +} + +bool +STData::isDefault() const +{ + return default_; +} + +std::string +STData::getInnerTypeString() const +{ + std::string inner_type_str = "Unknown"; + switch (static_cast(inner_type_)) + { + case STI_UINT8: + inner_type_str = "UINT8"; + break; + case STI_UINT16: + inner_type_str = "UINT16"; + break; + case STI_UINT32: + inner_type_str = "UINT32"; + break; + case STI_UINT64: + inner_type_str = "UINT64"; + break; + case STI_UINT128: + inner_type_str = "UINT128"; + break; + case STI_UINT160: + inner_type_str = "UINT160"; + break; + case STI_UINT192: + inner_type_str = "UINT192"; + break; + case STI_UINT256: + inner_type_str = "UINT256"; + break; + case STI_VL: + inner_type_str = "VL"; + break; + case STI_AMOUNT: + inner_type_str = "AMOUNT"; + break; + case STI_ACCOUNT: + inner_type_str = "ACCOUNT"; + break; + case STI_ISSUE: + inner_type_str = "ISSUE"; + break; + case STI_CURRENCY: + inner_type_str = "CURRENCY"; + break; + case STI_NUMBER: + inner_type_str = "NUMBER"; + break; + // Add other known types as needed + default: + inner_type_str = std::to_string(inner_type_); + } + + return inner_type_str; +} + +std::string +STData::getText() const +{ + std::string const inner_type_str = getInnerTypeString(); + return "STData{InnerType: " + inner_type_str + ", Data: " + data_.get().getText() + "}"; +} + +json::Value +STData::getJson(JsonOptions options) const +{ + json::Value ret(json::ValueType::Object); + ret[jss::type] = getInnerTypeString(); + ret[jss::value] = data_.get().getJson(options); + return ret; +} + +STBase* +STData::makeFieldPresent() +{ + STBase* f = &data_.get(); // getPIndex(index); + + if (f->getSType() != STI_NOTPRESENT) + return f; + + data_ = detail::STVar(detail::gNonPresentObject, f->getFName()); + return &data_.get(); +} + +void +STData::setFieldU8(unsigned char v) +{ + inner_type_ = STI_UINT8; + data_ = detail::STVar(detail::gDefaultObject, sfCloseResolution); + setFieldUsingSetValue(v); +} + +void +STData::setFieldU16(std::uint16_t v) +{ + inner_type_ = STI_UINT16; + data_ = detail::STVar(detail::gDefaultObject, sfSignerWeight); + setFieldUsingSetValue(v); +} + +void +STData::setFieldU32(std::uint32_t v) +{ + inner_type_ = STI_UINT32; + data_ = detail::STVar(detail::gDefaultObject, sfNetworkID); + setFieldUsingSetValue(v); +} + +void +STData::setFieldU64(std::uint64_t v) +{ + inner_type_ = STI_UINT64; + data_ = detail::STVar(detail::gDefaultObject, sfIndexNext); + setFieldUsingSetValue(v); +} + +void +STData::setFieldH128(uint128 const& v) +{ + inner_type_ = STI_UINT128; + data_ = detail::STVar(detail::gDefaultObject, sfEmailHash); + setFieldUsingSetValue(v); +} + +void +STData::setFieldH160(uint160 const& v) +{ + inner_type_ = STI_UINT160; + data_ = detail::STVar(detail::gDefaultObject, sfTakerPaysCurrency); + setFieldUsingSetValue(v); +} + +void +STData::setFieldH192(uint192 const& v) +{ + inner_type_ = STI_UINT192; + data_ = detail::STVar(detail::gDefaultObject, sfMPTokenIssuanceID); + setFieldUsingSetValue(v); +} + +void +STData::setFieldH256(uint256 const& v) +{ + inner_type_ = STI_UINT256; + data_ = detail::STVar(detail::gDefaultObject, sfLedgerHash); + setFieldUsingSetValue(v); +} + +void +STData::setFieldVL(Blob const& v) +{ + inner_type_ = STI_VL; + data_ = detail::STVar(detail::gDefaultObject, sfData); + setFieldUsingSetValue(Buffer(v.data(), v.size())); +} + +void +STData::setFieldVL(Slice const& s) +{ + inner_type_ = STI_VL; + data_ = detail::STVar(detail::gDefaultObject, sfData); + setFieldUsingSetValue(Buffer(s.data(), s.size())); +} + +void +STData::setAccountID(AccountID const& v) +{ + inner_type_ = STI_ACCOUNT; + data_ = detail::STVar(detail::gDefaultObject, sfAccount); + setFieldUsingSetValue(v); +} + +void +STData::setFieldAmount(STAmount const& v) +{ + inner_type_ = STI_AMOUNT; + data_ = detail::STVar(detail::gDefaultObject, sfAmount); + setFieldUsingAssignment(v); +} + +void +STData::setIssue(STIssue const& v) +{ + inner_type_ = STI_ISSUE; + data_ = detail::STVar(detail::gDefaultObject, sfAsset); + setFieldUsingAssignment(v); +} + +void +STData::setCurrency(STCurrency const& v) +{ + inner_type_ = STI_CURRENCY; + data_ = detail::STVar(detail::gDefaultObject, sfBaseAsset); + setFieldUsingAssignment(v); +} + +void +STData::setFieldNumber(STNumber const& v) +{ + inner_type_ = STI_NUMBER; + data_ = detail::STVar(detail::gDefaultObject, sfNumber); + setFieldUsingAssignment(v); +} + +unsigned char +STData::getFieldU8() const +{ + return getFieldByValue(); +} + +std::uint16_t +STData::getFieldU16() const +{ + return getFieldByValue(); +} + +std::uint32_t +STData::getFieldU32() const +{ + return getFieldByValue(); +} + +std::uint64_t +STData::getFieldU64() const +{ + return getFieldByValue(); +} + +uint128 +STData::getFieldH128() const +{ + return getFieldByValue(); +} + +uint160 +STData::getFieldH160() const +{ + return getFieldByValue(); +} + +uint192 +STData::getFieldH192() const +{ + return getFieldByValue(); +} + +uint256 +STData::getFieldH256() const +{ + return getFieldByValue(); +} + +Blob +STData::getFieldVL() const +{ + STBlob const empty; + STBlob const& b = getFieldByConstRef(empty); + return Blob(b.data(), b.data() + b.size()); +} + +AccountID +STData::getAccountID() const +{ + return getFieldByValue(); +} + +STAmount const& +STData::getFieldAmount() const +{ + static STAmount const empty{}; + return getFieldByConstRef(empty); +} + +STIssue +STData::getFieldIssue() const +{ + static STIssue const empty{}; + return getFieldByConstRef(empty); +} + +STCurrency +STData::getFieldCurrency() const +{ + static STCurrency const empty{}; + return getFieldByConstRef(empty); +} + +STNumber +STData::getFieldNumber() const +{ + static STNumber const empty{}; + return getFieldByConstRef(empty); +} + +STData +dataFromJson(SField const& field, json::Value const& v) +{ + json::Value type; + json::Value value; + + if (!v.isObject()) + Throw("STData: expected object"); + + type = v[jss::type]; + value = v[jss::value]; + + if (type.isNull()) + Throw("STData: type is null"); + if (value.isNull()) + Throw("STData: value is null"); + + auto typeStr = type.asString(); + + if (typeStr == "UINT8") + { + STData data(field, static_cast(value.asUInt())); + return data; + } + else if (typeStr == "UINT16") + { + STData data(field, static_cast(value.asUInt())); + return data; + } + else if (typeStr == "UINT32") + { + try + { + if (value.isString()) + { + STData data(field, beast::lexicalCastThrow(value.asString())); + return data; + } + else if (value.isInt()) + { + STData data(field, to_unsigned(value.asInt())); + return data; + } + else if (value.isUInt()) + { + STData data(field, safeCast(value.asUInt())); + return data; + } + else + { + Throw("bad type for UINT32"); + } + } + catch (std::exception const&) + { + Throw("invalid data for UINT32"); + } + } + else if (typeStr == "UINT64") + { + try + { + if (value.isString()) + { + auto const str = value.asString(); + + std::uint64_t val = 0; + + bool const useBase10 = field.shouldMeta(SField::kSmdBaseTen); + + // if the field is amount, serialize as base 10 + auto [p, ec] = + std::from_chars(str.data(), str.data() + str.size(), val, useBase10 ? 10 : 16); + + if (ec != std::errc() || (p != str.data() + str.size())) + Throw("STData: invalid UINT64 data"); + + STData data(field, val); + return data; + } + else if (value.isInt()) + { + STData data(field, to_unsigned(value.asInt())); + return data; + } + else if (value.isUInt()) + { + STData data(field, safeCast(value.asUInt())); + return data; + } + else + { + Throw("STData: bad type for UINT64"); + } + } + catch (std::exception const&) + { + Throw("STData: invalid data for UINT64"); + } + } + else if (typeStr == "UINT128") + { + if (!value.isString()) + { + Throw("STData: expected string for UINT128"); + } + + uint128 num; + + if (auto const s = value.asString(); !num.parseHex(s)) + { + if (!s.empty()) + { + Throw("STData: invalid UINT128 data"); + } + + num.zero(); + } + + STData data(field, num); + return data; + } + else if (typeStr == "UINT192") + { + if (!value.isString()) + { + Throw("STData: expected string for UINT192"); + } + + uint192 num; + + if (auto const s = value.asString(); !num.parseHex(s)) + { + if (!s.empty()) + { + Throw("STData: invalid UINT192 data"); + } + + num.zero(); + } + + STData data(field, num); + return data; + } + else if (typeStr == "UINT160") + { + if (!value.isString()) + { + Throw("STData: expected string for UINT160"); + } + + uint160 num; + + if (auto const s = value.asString(); !num.parseHex(s)) + { + if (!s.empty()) + { + Throw("STData: invalid UINT160 data"); + } + + num.zero(); + } + + STData data(field, num); + return data; + } + else if (typeStr == "UINT256") + { + if (!value.isString()) + { + Throw("STData: expected string for UINT256"); + } + + uint256 num; + + if (auto const s = value.asString(); !num.parseHex(s)) + { + if (!s.empty()) + { + Throw("STData: invalid UINT256 data"); + } + + num.zero(); + } + STData data(field, num); + return data; + } + else if (typeStr == "VL") + { + if (!value.isString()) + { + Throw("STData: expected string for VL"); + } + + try + { + if (auto vBlob = strUnHex(value.asString())) + { + STData data(field, *vBlob); + return data; + } + else + { + Throw("invalid data"); + } + } + catch (std::exception const&) + { + Throw("STData: invalid data"); + } + } + else if (typeStr == "AMOUNT") + { + try + { + STData data(field, amountFromJson(field, value)); + return data; + } + catch (std::exception const&) + { + Throw("STData: invalid data for AMOUNT"); + } + } + else if (typeStr == "ACCOUNT") + { + if (!value.isString()) + { + Throw("STData: expected string for ACCOUNT"); + } + + std::string const strValue = value.asString(); + + try + { + if (AccountID account; account.parseHex(strValue)) + { + STData data(field, account); + return data; + } + + if (auto result = parseBase58(strValue)) + { + STData data(field, *result); + return data; + } + + Throw("STData: invalid data for ACCOUNT"); + } + catch (std::exception const&) + { + Throw("STData: invalid data for ACCOUNT"); + } + } + else if (typeStr == "ISSUE") + { + try + { + STData data(field, issueFromJson(field, value)); + return data; + } + catch (std::exception const&) + { + Throw("STData: invalid data for ISSUE"); + } + } + else if (typeStr == "CURRENCY") + { + try + { + STData data(field, currencyFromJson(field, value)); + return data; + } + catch (std::exception const&) + { + Throw("STData: invalid data for CURRENCY"); + } + } + else if (typeStr == "NUMBER") + { + if (!value.isString()) + { + Throw("STData: expected string for NUMBER"); + } + + STNumber const number = numberFromJson(field, value); + STData data(field, number); + return data; + } + + // Handle unknown or unsupported type + Throw("STData: unsupported type string: " + typeStr); +} + +} // namespace xrpl diff --git a/src/libxrpl/protocol/STDataType.cpp b/src/libxrpl/protocol/STDataType.cpp new file mode 100644 index 00000000000..b711732a190 --- /dev/null +++ b/src/libxrpl/protocol/STDataType.cpp @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace xrpl { + +// TODO +STDataType::STDataType(SField const& n) : STBase(n), inner_type_(STI_NOTPRESENT) +{ +} + +STDataType::STDataType(SField const& n, SerializedTypeID v) + : STBase(n), inner_type_(v), default_(false) +{ +} + +STDataType::STDataType(SerialIter& sit, SField const& name) + : STBase(name), inner_type_(STI_DATA), default_(false) +{ + std::uint16_t const stype = SerializedTypeID(sit.get16()); + inner_type_ = stype; +} + +STBase* +STDataType::copy(std::size_t n, void* buf) const +{ + return emplace(n, buf, *this); +} + +STBase* +STDataType::move(std::size_t n, void* buf) +{ + return emplace(n, buf, std::move(*this)); +} + +SerializedTypeID +STDataType::getSType() const +{ + return STI_DATATYPE; +} + +void +STDataType::setInnerSType(SerializedTypeID v) +{ + inner_type_ = v; +} + +void +STDataType::add(Serializer& s) const +{ + s.add16(inner_type_); +} + +bool +STDataType::isEquivalent(STBase const& t) const +{ + auto const* const tPtr = dynamic_cast(&t); + return tPtr && (default_ == tPtr->default_) && (inner_type_ == tPtr->inner_type_); +} + +bool +STDataType::isDefault() const +{ + return default_; +} + +std::string +STDataType::getInnerTypeString() const +{ + std::string inner_type_str = "Unknown"; + // Optionally, convert inner_type_ to its string representation if mappings + // exist + switch (static_cast(inner_type_)) + { + case STI_UINT8: + inner_type_str = "UINT8"; + break; + case STI_UINT16: + inner_type_str = "UINT16"; + break; + case STI_UINT32: + inner_type_str = "UINT32"; + break; + case STI_UINT64: + inner_type_str = "UINT64"; + break; + case STI_UINT128: + inner_type_str = "UINT128"; + break; + case STI_UINT160: + inner_type_str = "UINT160"; + break; + case STI_UINT192: + inner_type_str = "UINT192"; + break; + case STI_UINT256: + inner_type_str = "UINT256"; + break; + case STI_VL: + inner_type_str = "VL"; + break; + case STI_ACCOUNT: + inner_type_str = "ACCOUNT"; + break; + case STI_AMOUNT: + inner_type_str = "AMOUNT"; + break; + case STI_ISSUE: + inner_type_str = "ISSUE"; + break; + case STI_CURRENCY: + inner_type_str = "CURRENCY"; + break; + case STI_NUMBER: + inner_type_str = "NUMBER"; + break; + // Add other known types as needed + default: + inner_type_str = std::to_string(inner_type_); + } + + return inner_type_str; +} + +std::string +STDataType::getText() const +{ + std::string const inner_type_str = getInnerTypeString(); + return "STDataType{InnerType: " + inner_type_str + "}"; +} + +json::Value +STDataType::getJson(JsonOptions) const +{ + json::Value ret(json::ValueType::Object); + ret[jss::type] = getInnerTypeString(); + return ret; +} + +STDataType +dataTypeFromJson(SField const& field, json::Value const& v) +{ + SerializedTypeID typeId = STI_NOTPRESENT; + json::Value type; + json::Value const value; + + if (!v.isObject()) + { + Throw("STData: expected object"); + } + + type = v[jss::type]; + auto typeStr = type.asString(); + + if (typeStr == "UINT8") + { + typeId = STI_UINT8; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "UINT16") + { + typeId = STI_UINT16; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "UINT32") + { + typeId = STI_UINT32; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "UINT64") + { + typeId = STI_UINT64; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "UINT128") + { + typeId = STI_UINT128; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "UINT160") + { + typeId = STI_UINT160; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "UINT192") + { + typeId = STI_UINT192; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "UINT256") + { + typeId = STI_UINT256; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "VL") + { + typeId = STI_VL; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "ACCOUNT") + { + typeId = STI_ACCOUNT; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "AMOUNT") + { + typeId = STI_AMOUNT; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "ISSUE") + { + typeId = STI_ISSUE; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "CURRENCY") + { + typeId = STI_CURRENCY; + STDataType data(field, typeId); + return data; + } + else if (typeStr == "NUMBER") + { + typeId = STI_NUMBER; + STDataType data(field, typeId); + return data; + } + + // Handle unknown or unsupported type + Throw("STData: unsupported type string: " + typeStr); +} + +} // namespace xrpl diff --git a/src/libxrpl/protocol/STJson.cpp b/src/libxrpl/protocol/STJson.cpp new file mode 100644 index 00000000000..ff38e3e7f7d --- /dev/null +++ b/src/libxrpl/protocol/STJson.cpp @@ -0,0 +1,765 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace xrpl { + +STJson::STJson(SField const& name) : STBase{name}, data_{Map{}} +{ +} + +STJson::STJson(SerialIter& sit, SField const& name) : STBase{name} +{ + if (sit.empty()) + { + data_ = Map{}; + return; + } + + int length = sit.getVLDataLength(); + if (length < 0) + Throw("Invalid STJson length"); + + if (length == 0) + { + data_ = Map{}; + return; + } + + // Read type byte + auto typeByte = sit.get8(); + JsonType const type = static_cast(typeByte); + length--; // Account for type byte + + int const initialBytesLeft = sit.getBytesLeft(); + + if (type == JsonType::Array) + { + Array array; + while (sit.getBytesLeft() > 0 && (initialBytesLeft - sit.getBytesLeft()) < length) + { + auto valueVL = sit.getVL(); + if (!valueVL.empty()) + { + SerialIter valueSit(valueVL.data(), valueVL.size()); + auto value = makeValueFromVLWithType(valueSit); + array.push_back(std::move(value)); + } + else + { + array.push_back(nullptr); + } + } + data_ = std::move(array); + } + else // JsonType::Object + { + Map map; + while (sit.getBytesLeft() > 0 && (initialBytesLeft - sit.getBytesLeft()) < length) + { + auto [key, value] = parsePair(sit); + map.emplace(std::move(key), std::move(value)); + } + data_ = std::move(map); + } + + int const consumedBytes = initialBytesLeft - sit.getBytesLeft(); + if (consumedBytes != length) + Throw("STJson length mismatch"); +} + +STJson::STJson(Map&& map) : data_(std::move(map)) +{ +} + +STJson::STJson(Array&& array) : data_(std::move(array)) +{ +} + +SerializedTypeID +STJson::getSType() const +{ + return STI_JSON; +} + +bool +STJson::isArray() const +{ + return std::holds_alternative(data_); +} + +bool +STJson::isObject() const +{ + return std::holds_alternative(data_); +} + +STJson::JsonType +STJson::getType() const +{ + return isArray() ? JsonType::Array : JsonType::Object; +} + +int +STJson::getDepth() const +{ + if (isArray()) + { + auto const& array = std::get(data_); + for (auto const& value : array) + { + if (value) + { + auto nested = std::dynamic_pointer_cast(value); + if (nested) + return 1 + nested->getDepth(); + } + } + return 0; + } + else // isObject() + { + auto const& map = std::get(data_); + for (auto const& [key, value] : map) + { + if (value) + { + auto nested = std::dynamic_pointer_cast(value); + if (nested) + return 1 + nested->getDepth(); + } + } + return 0; + } +} + +void +STJson::validateDepth(Value const& value, int currentDepth) const +{ + if (!value) + return; + + auto nested = std::dynamic_pointer_cast(value); + if (!nested) + return; + + // Adding an STJson value increases depth by 1 + int const totalDepth = currentDepth + 1 + nested->getDepth(); + if (totalDepth > 1) + Throw("STJson nesting depth exceeds maximum of 1"); + + // Arrays cannot contain arrays + if (isArray() && nested->isArray()) + Throw("STJson arrays cannot contain arrays"); +} + +void +STJson::setObjectField(Key const& key, Value const& value) +{ + if (!isObject()) + Throw("STJson::setObjectField called on non-object"); + validateDepth(value, 0); + std::get(data_)[key] = value; +} + +std::shared_ptr +STJson::fromBlob(void const* data, std::size_t size) +{ + SerialIter sit(static_cast(data), size); + return fromSerialIter(sit); +} + +std::shared_ptr +STJson::fromSerialIter(SerialIter& sit) +{ + if (sit.empty()) + return nullptr; + + int length = sit.getVLDataLength(); + if (length < 0) + Throw("Invalid STJson length"); + + if (length == 0) + return std::make_shared(Map{}); + + // Read type byte + auto typeByte = sit.get8(); + JsonType const type = static_cast(typeByte); + length--; // Account for type byte + + int const initialBytesLeft = sit.getBytesLeft(); + + if (type == JsonType::Array) + { + Array array; + while (sit.getBytesLeft() > 0 && (initialBytesLeft - sit.getBytesLeft()) < length) + { + auto valueVL = sit.getVL(); + if (!valueVL.empty()) + { + SerialIter valueSit(valueVL.data(), valueVL.size()); + auto value = makeValueFromVLWithType(valueSit); + array.push_back(std::move(value)); + } + else + { + array.push_back(nullptr); + } + } + + int const consumedBytes = initialBytesLeft - sit.getBytesLeft(); + if (consumedBytes != length) + Throw("STJson length mismatch"); + + return std::make_shared(std::move(array)); + } + else // JsonType::Object + { + Map map; + while (sit.getBytesLeft() > 0 && (initialBytesLeft - sit.getBytesLeft()) < length) + { + auto [key, value] = parsePair(sit); + map.emplace(std::move(key), std::move(value)); + } + + int const consumedBytes = initialBytesLeft - sit.getBytesLeft(); + if (consumedBytes != length) + Throw("STJson length mismatch"); + + return std::make_shared(std::move(map)); + } +} + +std::pair +STJson::parsePair(SerialIter& sit) +{ + auto keyBlob = sit.getVL(); + std::string key(reinterpret_cast(keyBlob.data()), keyBlob.size()); + auto valueVL = sit.getVL(); + if (valueVL.empty()) + return {std::move(key), nullptr}; + + SerialIter valueSit(valueVL.data(), valueVL.size()); + auto value = makeValueFromVLWithType(valueSit); + + return {std::move(key), std::move(value)}; +} + +STJson::Array +STJson::parseArray(SerialIter& sit, int length) +{ + Array array; + int const initialBytesLeft = sit.getBytesLeft(); + + while (sit.getBytesLeft() > 0 && (initialBytesLeft - sit.getBytesLeft()) < length) + { + auto valueVL = sit.getVL(); + if (!valueVL.empty()) + { + SerialIter valueSit(valueVL.data(), valueVL.size()); + auto value = makeValueFromVLWithType(valueSit); + array.push_back(std::move(value)); + } + else + { + array.push_back(nullptr); + } + } + + return array; +} + +STjson::Value +STJson::makeValueFromVLWithType(SerialIter& sit) +{ + if (sit.getBytesLeft() == 0) + return nullptr; + + // Read SType marker (1 byte) + auto typeCode = sit.get8(); + SerializedTypeID const stype = static_cast(typeCode); + + // Dispatch to correct SType + switch (stype) + { + case STI_UINT8: + return std::make_shared(sfCloseResolution, sit.get8()); + case STI_UINT16: + return std::make_shared(sfSignerWeight, sit.get16()); + case STI_UINT32: + return std::make_shared(sfNetworkID, sit.get32()); + case STI_UINT64: + return std::make_shared(sfIndexNext, sit.get64()); + case STI_UINT128: + return std::make_shared(sfEmailHash, sit.get128()); + case STI_UINT160: + return std::make_shared(sfTakerPaysCurrency, sit.get160()); + case STI_UINT192: + return std::make_shared(sfMPTokenIssuanceID, sit.get192()); + case STI_UINT256: + return std::make_shared(sfLedgerHash, sit.get256()); + case STI_VL: { + auto blob = sit.getVL(); + return std::make_shared(sfData, blob.data(), blob.size()); + } + case STI_ACCOUNT: + return std::make_shared(sit, sfAccount); + case STI_AMOUNT: + return std::make_shared(sit, sfAmount); + // case STI_NUMBER: + // return std::make_shared(sit, sfNumber); + case STI_ISSUE: + return std::make_shared(sit, sfAsset); + case STI_CURRENCY: + return std::make_shared(sit, sfBaseAsset); + case STI_JSON: + return std::make_shared(sit, sfContractJson); + case STI_OBJECT: + case STI_ARRAY: + case STI_PATHSET: + case STI_VECTOR256: + default: + // Unknown type, treat as blob + { + auto blob = sit.getSlice(sit.getBytesLeft()); + return std::make_shared(sfData, blob.data(), blob.size()); + } + } +} + +std::optional +STJson::getObjectField(Key const& key) const +{ + if (!isObject()) + return std::nullopt; + + auto const& map = std::get(data_); + auto it = map.find(key); + if (it == map.end() || !it->second) + return std::nullopt; + return it->second; +} + +void +STJson::setNestedObjectField(Key const& key, Key const& nestedKey, Value const& value) +{ + if (!isObject()) + Throw("STJson::setNestedObjectField called on non-object"); + + validateDepth(value, 1); // We're at depth 1 (nested) + + auto& map = std::get(data_); + auto it = map.find(key); + std::shared_ptr nested; + if (it == map.end() || !it->second) + { + // Create new nested STJson + nested = std::make_shared(); + map[key] = nested; + } + else + { + nested = std::dynamic_pointer_cast(it->second); + if (!nested) + { + // Overwrite with new STJson if not an STJson + nested = std::make_shared(); + map[key] = nested; + } + } + nested->setObjectField(nestedKey, value); +} + +std::optional +STJson::getNestedObjectField(Key const& key, Key const& nestedKey) const +{ + if (!isObject()) + return std::nullopt; + + auto const& map = std::get(data_); + auto it = map.find(key); + if (it == map.end() || !it->second) + return std::nullopt; + auto nested = std::dynamic_pointer_cast(it->second); + if (!nested) + return std::nullopt; + return nested->getObjectField(nestedKey); +} + +STJson::Map const& +STJson::getMap() const +{ + if (!isObject()) + Throw("STJson::getMap called on non-object"); + return std::get(data_); +} + +STJson::Array const& +STJson::getArray() const +{ + if (!isArray()) + Throw("STJson::getArray called on non-array"); + return std::get(data_); +} + +void +STJson::pushArrayElement(Value const& value) +{ + if (!isArray()) + Throw("STJson::pushArrayElement called on non-array"); + validateDepth(value, 0); + std::get(data_).push_back(value); +} + +std::optional +STJson::getArrayElement(size_t index) const +{ + if (!isArray()) + return std::nullopt; + + auto const& array = std::get(data_); + if (index >= array.size()) + return std::nullopt; + + return array[index]; +} + +void +STJson::setArrayElement(size_t index, Value const& value) +{ + if (!isArray()) + Throw("STJson::setArrayElement called on non-array"); + validateDepth(value, 0); + + auto& array = std::get(data_); + // Auto-resize with nulls if needed + if (index >= array.size()) + array.resize(index + 1, nullptr); + + array[index] = value; +} + +void +STJson::setArrayElementField(size_t index, Key const& key, Value const& value) +{ + if (!isArray()) + Throw("STJson::setArrayElementField called on non-array"); + + validateDepth(value, 1); // We're at depth 1 (inside array element) + + auto& array = std::get(data_); + // Auto-resize with nulls if needed + if (index >= array.size()) + array.resize(index + 1, nullptr); + + // Get or create STJson object at index + std::shared_ptr element; + if (!array[index]) + { + element = std::make_shared(Map{}); + array[index] = element; + } + else + { + element = std::dynamic_pointer_cast(array[index]); + if (!element) + { + // Replace with new STJson if not an STJson + element = std::make_shared(Map{}); + array[index] = element; + } + } + + element->setObjectField(key, value); +} + +std::optional +STJson::getArrayElementField(size_t index, Key const& key) const +{ + if (!isArray()) + return std::nullopt; + + auto const& array = std::get(data_); + if (index >= array.size()) + return std::nullopt; + + auto element = std::dynamic_pointer_cast(array[index]); + if (!element) + return std::nullopt; + + return element->getObjectField(key); +} + +size_t +STJson::arraySize() const +{ + if (!isArray()) + return 0; + return std::get(data_).size(); +} + +void +STJson::setNestedArrayElement(Key const& key, size_t index, Value const& value) +{ + if (!isObject()) + Throw("STJson::setNestedArrayElement called on non-object"); + + validateDepth(value, 1); // We're at depth 1 (nested array) + + auto& map = std::get(data_); + auto it = map.find(key); + std::shared_ptr arrayJson; + + if (it == map.end() || !it->second) + { + // Create new nested STJson array + arrayJson = std::make_shared(Array{}); + map[key] = arrayJson; + } + else + { + arrayJson = std::dynamic_pointer_cast(it->second); + if (!arrayJson) + { + // Replace with new STJson array if not an STJson + arrayJson = std::make_shared(Array{}); + map[key] = arrayJson; + } + else if (!arrayJson->isArray()) + { + // Replace with array if not an array + arrayJson = std::make_shared(Array{}); + map[key] = arrayJson; + } + } + + arrayJson->setArrayElement(index, value); +} + +void +STJson::setNestedArrayElementField( + Key const& key, + size_t index, + Key const& nestedKey, + Value const& value) +{ + if (!isObject()) + Throw("STJson::setNestedArrayElementField called on non-object"); + + validateDepth(value, 1); // We're at depth 1 (nested array element field - + // still counts as depth 1) + + auto& map = std::get(data_); + auto it = map.find(key); + std::shared_ptr arrayJson; + + if (it == map.end() || !it->second) + { + // Create new nested STJson array + arrayJson = std::make_shared(Array{}); + map[key] = arrayJson; + } + else + { + arrayJson = std::dynamic_pointer_cast(it->second); + if (!arrayJson) + { + // Replace with new STJson array if not an STJson + arrayJson = std::make_shared(Array{}); + map[key] = arrayJson; + } + else if (!arrayJson->isArray()) + { + // Replace with array if not an array + arrayJson = std::make_shared(Array{}); + map[key] = arrayJson; + } + } + + arrayJson->setArrayElementField(index, nestedKey, value); +} + +std::optional +STJson::getNestedArrayElement(Key const& key, size_t index) const +{ + if (!isObject()) + return std::nullopt; + + auto const& map = std::get(data_); + auto it = map.find(key); + if (it == map.end() || !it->second) + return std::nullopt; + + auto arrayJson = std::dynamic_pointer_cast(it->second); + if (!arrayJson || !arrayJson->isArray()) + return std::nullopt; + + return arrayJson->getArrayElement(index); +} + +std::optional +STJson::getNestedArrayElementField(Key const& key, size_t index, Key const& nestedKey) const +{ + if (!isObject()) + return std::nullopt; + + auto const& map = std::get(data_); + auto it = map.find(key); + if (it == map.end() || !it->second) + return std::nullopt; + + auto arrayJson = std::dynamic_pointer_cast(it->second); + if (!arrayJson || !arrayJson->isArray()) + return std::nullopt; + + return arrayJson->getArrayElementField(index, nestedKey); +} + +void +STJson::addVLKey(Serializer& s, std::string const& str) +{ + s.addVL(str.data(), str.size()); +} + +void +STJson::addVLValue(Serializer& s, std::shared_ptr const& value) +{ + if (!value) + { + s.addVL(nullptr, 0); + return; + } + Serializer tmp; + tmp.add8(static_cast(value->getSType())); + value->add(tmp); + s.addVL(tmp.peekData().data(), tmp.peekData().size()); +} + +void +STJson::add(Serializer& s) const +{ + Serializer inner; + + // Add type byte + inner.add8(static_cast(getType())); + + if (isArray()) + { + auto const& array = std::get(data_); + for (auto const& value : array) + { + addVLValue(inner, value); + } + } + else // isObject() + { + auto const& map = std::get(data_); + for (auto const& [key, value] : map) + { + addVLKey(inner, key); + addVLValue(inner, value); + } + } + + s.addVL(inner.peekData().data(), inner.peekData().size()); +} + +json::Value +STJson::getJson(JsonOptions options) const +{ + if (isArray()) + { + json::Value arr(json::ValueType::Array); + auto const& array = std::get(data_); + for (auto const& value : array) + { + if (value) + arr.append(value->getJson(options)); + else + arr.append(json::ValueType::Null); + } + return arr; + } + else // isObject() + { + json::Value obj(json::ValueType::Object); + auto const& map = std::get(data_); + for (auto const& [key, value] : map) + { + auto const hexKey = strHex(key); + if (value) + obj[hexKey] = value->getJson(options); + else + obj[hexKey] = json::ValueType::Null; + } + return obj; + } +} + +bool +STJson::isEquivalent(STBase const& t) const +{ + auto const* const tPtr = dynamic_cast(&t); + return tPtr && (data_ == tPtr->data_); +} + +bool +STJson::isDefault() const +{ + return default_; +} + +Blob +STJson::toBlob() const +{ + Serializer s; + add(s); + return s.peekData(); +} + +std::size_t +STJson::size() const +{ + Serializer s; + add(s); + return s.size(); +} + +void +STJson::setValue(STJson const& v) +{ + data_ = v.data_; +} + +STBase* +STJson::copy(std::size_t n, void* buf) const +{ + return emplace(n, buf, *this); +} + +STBase* +STJson::move(std::size_t n, void* buf) +{ + return emplace(n, buf, std::move(*this)); +} + +} // namespace xrpl diff --git a/src/libxrpl/protocol/STObject.cpp b/src/libxrpl/protocol/STObject.cpp index e16cbc871f5..3f235344422 100644 --- a/src/libxrpl/protocol/STObject.cpp +++ b/src/libxrpl/protocol/STObject.cpp @@ -21,8 +21,11 @@ #include #include #include +#include +#include #include #include +#include #include #include #include @@ -635,6 +638,20 @@ STObject::getAccountID(SField const& field) const return getFieldByValue(field); } +STData +STObject::getFieldData(SField const& field) const +{ + static STData const empty{field}; + return getFieldByConstRef(field, empty); +} + +STDataType +STObject::getFieldDataType(SField const& field) const +{ + static STDataType const empty{field}; + return getFieldByConstRef(field, empty); +} + Blob STObject::getFieldVL(SField const& field) const { @@ -695,6 +712,13 @@ STObject::getFieldNumber(SField const& field) const return getFieldByConstRef(field, kEmpty); } +STJson const& +STObject::getFieldJson(SField const& field) const +{ + static STJson const empty{field}; + return getFieldByConstRef(field, empty); +} + void STObject::set(std::unique_ptr v) { @@ -717,6 +741,72 @@ STObject::set(STBase&& v) } } +void +STObject::addFieldFromSlice(SField const& sfield, Slice const& data) +{ + SerialIter sit(data.data(), data.size()); + std::unique_ptr element; + + switch (sfield.fieldType) + { + case STI_AMOUNT: + element = std::make_unique(sit, sfield); + break; + case STI_ACCOUNT: + element = std::make_unique(sit, sfield); + break; + case STI_UINT8: + element = std::make_unique(sit, sfield); + break; + case STI_UINT16: + element = std::make_unique(sit, sfield); + break; + case STI_UINT32: + element = std::make_unique(sit, sfield); + break; + case STI_UINT64: + element = std::make_unique(sit, sfield); + break; + case STI_UINT128: + element = std::make_unique(sit, sfield); + break; + case STI_UINT160: + element = std::make_unique(sit, sfield); + break; + case STI_UINT192: + element = std::make_unique(sit, sfield); + break; + case STI_UINT256: + element = std::make_unique(sit, sfield); + break; + case STI_VECTOR256: + element = std::make_unique(sit, sfield); + break; + case STI_VL: + element = std::make_unique(sit, sfield); + break; + case STI_CURRENCY: + element = std::make_unique(sit, sfield); + break; + case STI_ISSUE: + element = std::make_unique(sit, sfield); + break; + case STI_PATHSET: + element = std::make_unique(sit, sfield); + break; + case STI_ARRAY: + element = std::make_unique(sit, sfield); + break; + case STI_OBJECT: + element = std::make_unique(sit, sfield, 0); + break; + default: + throw std::runtime_error("Unsupported SField type"); + } + + this->set(std::move(element)); +} + void STObject::setFieldU8(SField const& field, unsigned char v) { @@ -831,6 +921,12 @@ STObject::setFieldObject(SField const& field, STObject const& v) setFieldUsingAssignment(field, v); } +void +STObject::setFieldJson(SField const& field, STJson const& v) +{ + setFieldUsingAssignment(field, v); +} + json::Value STObject::getJson(JsonOptions options) const { diff --git a/src/libxrpl/protocol/STParsedJSON.cpp b/src/libxrpl/protocol/STParsedJSON.cpp index 64c4dfd1bee..c5b9d5a6fd2 100644 --- a/src/libxrpl/protocol/STParsedJSON.cpp +++ b/src/libxrpl/protocol/STParsedJSON.cpp @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include #include @@ -967,6 +969,52 @@ parseLeaf( } break; + case STI_DATA: { + try + { + ret = detail::make_stvar(dataFromJson(field, value)); + } + catch (std::exception const&) + { + std::cout << "STI_DATA failed for field: " << fieldName + << " in object: " << json_name << "\n"; + error = invalid_data(json_name, fieldName); + return ret; + } + + break; + } + case STI_DATATYPE: { + try + { + ret = detail::make_stvar(dataTypeFromJson(field, value)); + } + catch (std::exception const&) + { + std::cout << "STI_DATATYPE failed for field: " << fieldName + << " in object: " << json_name << "\n"; + error = invalid_data(json_name, fieldName); + return ret; + } + break; + } + + case STI_JSON: + Throw("STI_JSON is not supported"); + // try + // { + // ret = detail::make_stvar( + // dataTypeFromJson(field, value)); + // } + // catch (std::exception const&) + // { + // std::cout << "STI_DATATYPE failed for field: " << fieldName + // << " in object: " << json_name << "\n"; + // error = invalid_data(json_name, fieldName); + // return ret; + // } + break; + default: error = badType(jsonName, fieldName); return ret; diff --git a/src/libxrpl/protocol/STValidation.cpp b/src/libxrpl/protocol/STValidation.cpp index 5eafb407ec0..df2c384cccc 100644 --- a/src/libxrpl/protocol/STValidation.cpp +++ b/src/libxrpl/protocol/STValidation.cpp @@ -59,6 +59,10 @@ STValidation::validationFormat() {sfBaseFeeDrops, SoeOptional}, {sfReserveBaseDrops, SoeOptional}, {sfReserveIncrementDrops, SoeOptional}, + // featureSmartEscrow + {sfExtensionComputeLimit, SoeOptional}, + {sfExtensionSizeLimit, SoeOptional}, + {sfGasPrice, SoeOptional}, }; // clang-format on diff --git a/src/libxrpl/protocol/STVar.cpp b/src/libxrpl/protocol/STVar.cpp index 3d123e6a0ea..97189c1ae07 100644 --- a/src/libxrpl/protocol/STVar.cpp +++ b/src/libxrpl/protocol/STVar.cpp @@ -10,8 +10,11 @@ #include #include #include +#include +#include #include #include +#include #include #include #include @@ -223,6 +226,15 @@ STVar::constructST(SerializedTypeID id, int depth, Args&&... args) case STI_CURRENCY: construct(std::forward(args)...); return; + case STI_DATA: + construct(std::forward(args)...); + return; + case STI_DATATYPE: + construct(std::forward(args)...); + return; + case STI_JSON: + construct(std::forward(args)...); + return; default: Throw("Unknown object type"); } diff --git a/src/libxrpl/protocol/TER.cpp b/src/libxrpl/protocol/TER.cpp index 6b8dfc68113..0c4900657f9 100644 --- a/src/libxrpl/protocol/TER.cpp +++ b/src/libxrpl/protocol/TER.cpp @@ -106,6 +106,8 @@ transResults() MAKE_ERROR(tecLIMIT_EXCEEDED, "Limit exceeded."), MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."), MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."), + MAKE_ERROR(tecWASM_REJECTED, "The custom WASM code that was run rejected your transaction."), + MAKE_ERROR(tecINVALID_PARAMETERS, "Contract parameters do not match the expected ABI."), MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."), MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."), @@ -129,6 +131,8 @@ transResults() MAKE_ERROR(tefNO_TICKET, "Ticket is not in ledger."), MAKE_ERROR(tefNFTOKEN_IS_NOT_TRANSFERABLE, "The specified NFToken is not transferable."), MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."), + MAKE_ERROR(tefNO_WASM, "There is no WASM code to run, but a WASM-specific field was included."), + MAKE_ERROR(tefWASM_FIELD_NOT_INCLUDED, "WASM code requires a field to be included that was not included."), MAKE_ERROR(telLOCAL_ERROR, "Local failure."), MAKE_ERROR(telBAD_DOMAIN, "Domain too long."), @@ -199,6 +203,8 @@ transResults() MAKE_ERROR(temARRAY_TOO_LARGE, "Malformed: Array is too large."), 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_WASM, "Malformed: Provided WASM code is invalid."), + MAKE_ERROR(temTEMP_DISABLED, "The transaction requires logic that is currently temporarily disabled."), MAKE_ERROR(terRETRY, "Retry transaction."), MAKE_ERROR(terFUNDS_SPENT, "DEPRECATED."), diff --git a/src/libxrpl/protocol/TxFormats.cpp b/src/libxrpl/protocol/TxFormats.cpp index b926bdf0e55..a9ee9806b46 100644 --- a/src/libxrpl/protocol/TxFormats.cpp +++ b/src/libxrpl/protocol/TxFormats.cpp @@ -42,7 +42,7 @@ TxFormats::TxFormats() #undef TRANSACTION #define UNWRAP(...) __VA_ARGS__ -#define TRANSACTION(tag, value, name, delegable, amendment, privileges, fields) \ +#define TRANSACTION(tag, value, name, delegatable, amendment, privileges, emitable, fields) \ add(jss::name, tag, UNWRAP fields, getCommonFields()); #include diff --git a/src/libxrpl/protocol/TxMeta.cpp b/src/libxrpl/protocol/TxMeta.cpp index 0373706e848..401aaadb79c 100644 --- a/src/libxrpl/protocol/TxMeta.cpp +++ b/src/libxrpl/protocol/TxMeta.cpp @@ -194,6 +194,12 @@ TxMeta::getAsObject() const if (parentBatchID_.has_value()) metaData.setFieldH256(sfParentBatchID, *parentBatchID_); + if (gasUsed_.has_value()) + metaData.setFieldU32(sfGasUsed, *gasUsed_); + + if (wasmReturnCode_.has_value()) + metaData.setFieldI32(sfWasmReturnCode, *wasmReturnCode_); + return metaData; } diff --git a/src/libxrpl/server/InfoSub.cpp b/src/libxrpl/server/InfoSub.cpp index 87b48296a1f..561542cc236 100644 --- a/src/libxrpl/server/InfoSub.cpp +++ b/src/libxrpl/server/InfoSub.cpp @@ -40,6 +40,7 @@ InfoSub::~InfoSub() source_.unsubValidations(seq_); source_.unsubPeerStatus(seq_); source_.unsubConsensus(seq_); + source_.unsubContractEvent(seq_); // Use the internal unsubscribe so that it won't call // back to us and modify its own parameter diff --git a/src/libxrpl/tx/ApplyContext.cpp b/src/libxrpl/tx/ApplyContext.cpp index 88e37baf003..57dd9b701d1 100644 --- a/src/libxrpl/tx/ApplyContext.cpp +++ b/src/libxrpl/tx/ApplyContext.cpp @@ -46,18 +46,31 @@ ApplyContext::ApplyContext( XRPL_ASSERT( parentBatchId.has_value() == ((flags_ & TapBatch) == TapBatch), "Parent Batch ID should be set if batch apply flag is set"); - view_.emplace(&base_, flags_); + view_.emplace(&base_.view(), flags_); } void ApplyContext::discard() { - view_.emplace(&base_, flags_); + base_.discard(); + view_.emplace(&base_.view(), flags_); +} + +void +ApplyContext::finalize() +{ + base_.commit(); + view_.emplace(&base_.view(), flags_); } std::optional ApplyContext::apply(TER ter) { + if (wasmReturnCode_.has_value()) + { + view_->setWasmReturnCode(*wasmReturnCode_); + } + view_->setGasUsed(gasUsed_); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) view_ emplaced in constructor return view_->apply(base_, tx, ter, parentBatchId_, (flags_ & TapDryRun) != 0u, journal); } diff --git a/src/libxrpl/tx/Transactor.cpp b/src/libxrpl/tx/Transactor.cpp index 97f2cabff24..9c5d138e792 100644 --- a/src/libxrpl/tx/Transactor.cpp +++ b/src/libxrpl/tx/Transactor.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -613,6 +614,32 @@ Transactor::ticketDelete( return tesSUCCESS; } +std::pair +Transactor::checkInvariants(TER result, XRPAmount fee) +{ + // Check invariants: if `tecINVARIANT_FAILED` is not returned, we can + // proceed to apply the tx + result = ctx_.checkInvariants(result, fee); + + if (result == tecINVARIANT_FAILED) + { + // if invariants checking failed again, reset the context and + // attempt to only claim a fee. + auto const resetResult = reset(fee); + if (!isTesSuccess(resetResult.first)) + result = resetResult.first; + + fee = resetResult.second; + + // Check invariants again to ensure the fee claiming doesn't + // violate invariants. + if (isTesSuccess(result) || isTecClaim(result)) + result = ctx_.checkInvariants(result, fee); + } + + return {result, fee}; +} + // check stuff before you bother to lock the ledger void Transactor::preCompute() @@ -678,7 +705,8 @@ Transactor::checkSign( } auto const pkSigner = sigObject.getFieldVL(sfSigningPubKey); - // Ignore signature check on batch inner transactions + // Ignore signature check on batch inner transactions (e.g., emitted + // transactions from contracts) if (parentBatchId && view.rules().enabled(featureBatch)) { // Defensive Check: These values are also checked in Batch::preflight @@ -1021,6 +1049,22 @@ removeExpiredCredentials(ApplyView& view, std::vector const& creds, bea } } +static void +modifyWasmDataFields( + ApplyView& view, + std::vector> const& wasmObjects, + beast::Journal viewJ) +{ + for (auto const& [index, data] : wasmObjects) + { + if (auto const sle = view.peek(keylet::escrow(index))) + { + sle->setFieldVL(sfData, data); + view.update(sle); + } + } +} + static void removeDeletedTrustLines( ApplyView& view, @@ -1088,8 +1132,10 @@ Transactor::reset(XRPAmount fee) auto const balance = payerSle->getFieldAmount(sfBalance).xrp(); // balance should have already been checked in checkFee / preFlight. + // For batch/inner transactions (fee == 0), the balance can + // legitimately be zero (e.g. contract pseudo-accounts). XRPL_ASSERT( - balance != beast::kZero && (!view().open() || balance >= fee), + (fee == beast::kZero || balance != beast::kZero) && (!view().open() || balance >= fee), "xrpl::Transactor::reset : valid balance"); // We retry/reject the transaction if the account balance is zero or @@ -1245,7 +1291,8 @@ Transactor::operator()() } else if ( (result == tecOVERSIZE) || (result == tecKILLED) || (result == tecINCOMPLETE) || - (result == tecEXPIRED) || (isTecClaimHardFail(result, view().flags()))) + (result == tecEXPIRED) || (result == tecWASM_REJECTED) || + (isTecClaimHardFail(result, view().flags()))) { JLOG(j_.trace()) << "reapplying because of " << transToken(result); @@ -1258,12 +1305,14 @@ Transactor::operator()() std::vector removedMPTs; std::vector expiredNFTokenOffers; std::vector expiredCredentials; + std::vector> modifiedWasmObjects; bool const doOffers = ((result == tecOVERSIZE) || (result == tecKILLED)); bool const doLinesOrMPTs = (result == tecINCOMPLETE); bool const doNFTokenOffers = (result == tecEXPIRED); bool const doCredentials = (result == tecEXPIRED); - if (doOffers || doLinesOrMPTs || doNFTokenOffers || doCredentials) + bool const doWasmData = (result == tecWASM_REJECTED); + if (doOffers || doLinesOrMPTs || doNFTokenOffers || doCredentials || doWasmData) { ctx_.visit([doOffers, &removedOffers, @@ -1273,7 +1322,9 @@ Transactor::operator()() doNFTokenOffers, &expiredNFTokenOffers, doCredentials, - &expiredCredentials]( + &expiredCredentials, + doWasmData, + &modifiedWasmObjects]( uint256 const& index, bool isDelete, std::shared_ptr const& before, @@ -1311,6 +1362,11 @@ Transactor::operator()() if (doCredentials && before && after && (before->getType() == ltCREDENTIAL)) expiredCredentials.push_back(index); } + + if (doWasmData && before && after && (before->getType() == ltESCROW)) + { + modifiedWasmObjects.push_back(std::make_pair(index, after->getFieldVL(sfData))); + } }); } @@ -1348,6 +1404,10 @@ Transactor::operator()() view(), expiredCredentials, ctx_.registry.get().getJournal("View")); } + if (result == tecWASM_REJECTED) + modifyWasmDataFields( + view(), modifiedWasmObjects, ctx_.registry.get().getJournal("View")); + applied = isTecClaim(result); } @@ -1407,6 +1467,70 @@ Transactor::operator()() applied = false; } + if (metadata && ctx_.getEmittedTxns().size() > 0) + { + OpenView emittedTxnsView(batch_view, ctx_.openView()); + auto const parentBatchId = ctx_.tx.getTransactionID(); + + auto applyOneTransaction = [this, &parentBatchId, &emittedTxnsView](STTx const& tx) { + OpenView perTxBatchView(batch_view, emittedTxnsView); + + auto const ret = xrpl::apply( + ctx_.registry, perTxBatchView, parentBatchId, tx, tapBATCH, ctx_.journal); + XRPL_ASSERT( + ret.applied == (isTesSuccess(ret.ter) || isTecClaim(ret.ter)), + "Inner transaction should not be applied"); + + JLOG(ctx_.journal.debug()) + << "BatchTrace[" << parentBatchId << "]: " << tx.getTransactionID() << " " + << (ret.applied ? "applied" : "failure") << ": " << transToken(ret.ter); + + // If the transaction should be applied push its changes to the + // whole-batch view. + if (ret.applied && (isTesSuccess(ret.ter) || isTecClaim(ret.ter))) + perTxBatchView.apply(emittedTxnsView); + + return ret; + }; + + bool emitResult = true; + auto emittedTxns = ctx_.getEmittedTxns(); + while (!emittedTxns.empty()) + { + auto txn = emittedTxns.front(); + emittedTxns.pop(); + auto const result = applyOneTransaction(*txn); + XRPL_ASSERT( + result.applied == (isTesSuccess(result.ter) || isTecClaim(result.ter)), + "Outer Batch failure, inner transaction should not be applied"); + + if (!isTesSuccess(result.ter)) + emitResult = false; + } + + if (emitResult) + emittedTxnsView.apply(ctx_.openView()); + else + { + // reset context + result = tecWASM_REJECTED; + auto const resetResult = reset(fee); + if (!isTesSuccess(resetResult.first)) + result = resetResult.first; + fee = resetResult.second; + + // InvariantCheck + auto const invariantsResult = checkInvariants(result, fee); + result = invariantsResult.first; + fee = invariantsResult.second; + + // apply + metadata = ctx_.apply(result); + } + } + + ctx_.finalize(); + JLOG(j_.trace()) << (applied ? "applied " : "not applied ") << transToken(result); return {result, applied, metadata}; diff --git a/src/libxrpl/tx/invariants/InvariantCheck.cpp b/src/libxrpl/tx/invariants/InvariantCheck.cpp index 3e51b6f8773..9c0bab3bba1 100644 --- a/src/libxrpl/tx/invariants/InvariantCheck.cpp +++ b/src/libxrpl/tx/invariants/InvariantCheck.cpp @@ -924,7 +924,8 @@ ValidPseudoAccounts::visitEntry( errors_.emplace_back(error.str()); } } - if (before && before->at(sfSequence) != after->at(sfSequence)) + if (before && before->at(sfSequence) != after->at(sfSequence) && + !after->isFieldPresent(sfContractID)) { errors_.emplace_back("pseudo-account sequence changed"); } diff --git a/src/libxrpl/tx/transactors/DeleteUtils.cpp b/src/libxrpl/tx/transactors/DeleteUtils.cpp new file mode 100644 index 00000000000..d6b019073f2 --- /dev/null +++ b/src/libxrpl/tx/transactors/DeleteUtils.cpp @@ -0,0 +1,382 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +// Local function definitions that provides signature compatibility. +TER +offerDelete( + ServiceRegistry& registry, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return offerDelete(view, sleDel, j); +} + +TER +removeSignersFromLedger( + ServiceRegistry& registry, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return SignerListSet::removeFromLedger(registry, view, account, j); +} + +TER +removeTicketFromLedger( + ServiceRegistry&, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const&, + beast::Journal j) +{ + return Transactor::ticketDelete(view, account, delIndex, j); +} + +TER +removeDepositPreauthFromLedger( + ServiceRegistry&, + ApplyView& view, + AccountID const&, + uint256 const& delIndex, + std::shared_ptr const&, + beast::Journal j) +{ + return DepositPreauth::removeFromLedger(view, delIndex, j); +} + +TER +removeNFTokenOfferFromLedger( + ServiceRegistry& registry, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal) +{ + if (!nft::deleteTokenOffer(view, sleDel)) + return tefBAD_LEDGER; + + return tesSUCCESS; +} + +TER +removeDIDFromLedger( + ServiceRegistry& registry, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return DIDDelete::deleteSLE(view, sleDel, account, j); +} + +TER +removeOracleFromLedger( + ServiceRegistry&, + ApplyView& view, + AccountID const& account, + uint256 const&, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return OracleDelete::deleteOracle(view, sleDel, account, j); +} + +TER +removeCredentialFromLedger( + ServiceRegistry&, + ApplyView& view, + AccountID const&, + uint256 const&, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return credentials::deleteSLE(view, sleDel, j); +} + +TER +removeDelegateFromLedger( + ServiceRegistry& registry, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return DelegateSet::deleteDelegate(view, sleDel, account, j); +} + +TER +removeContractFromLedger( + ServiceRegistry& registry, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return ContractDelete::deleteContract(view, sleDel, account, j); +} + +// Return nullptr if the LedgerEntryType represents an obligation that can't +// be deleted. Otherwise return the pointer to the function that can delete +// the non-obligation +DeleterFuncPtr +nonObligationDeleter(LedgerEntryType t) +{ + switch (t) + { + case ltOFFER: + return offerDelete; + case ltSIGNER_LIST: + return removeSignersFromLedger; + case ltTICKET: + return removeTicketFromLedger; + case ltDEPOSIT_PREAUTH: + return removeDepositPreauthFromLedger; + case ltNFTOKEN_OFFER: + return removeNFTokenOfferFromLedger; + case ltDID: + return removeDIDFromLedger; + case ltORACLE: + return removeOracleFromLedger; + case ltCREDENTIAL: + return removeCredentialFromLedger; + case ltDELEGATE: + return removeDelegateFromLedger; + case ltCONTRACT: + return removeContractFromLedger; + default: + return nullptr; + } +} + +TER +deletePreclaim( + PreclaimContext const& ctx, + std::uint32_t seqDelta, + AccountID const account, + AccountID const dest, + bool isPseudoAccount) +{ + auto destSle = ctx.view.read(keylet::account(dest)); + + if (!destSle) + return tecNO_DST; + + if ((*destSle)[sfFlags] & lsfRequireDestTag && !ctx.tx[~sfDestinationTag]) + return tecDST_TAG_NEEDED; + + // If credentials are provided - check them anyway + if (auto const err = credentials::valid(ctx.tx, ctx.view, account, ctx.j); !isTesSuccess(err)) + return err; + + // if credentials then postpone auth check to doApply, to check for expired + // credentials + if (!ctx.tx.isFieldPresent(sfCredentialIDs)) + { + // Check whether the destination account requires deposit authorization. + if (destSle->getFlags() & lsfDepositAuth) + { + if (!ctx.view.exists(keylet::depositPreauth(dest, account)) && !isPseudoAccount) + return tecNO_PERMISSION; + } + } + + auto srcSle = ctx.view.read(keylet::account(account)); + XRPL_ASSERT(srcSle, "xrpl::DeleteAccount::preclaim : non-null account"); + if (!srcSle) + return terNO_ACCOUNT; + + { + // If an issuer has any issued NFTs resident in the ledger then it + // cannot be deleted. + if ((*srcSle)[~sfMintedNFTokens] != (*srcSle)[~sfBurnedNFTokens]) + return tecHAS_OBLIGATIONS; + + // If the account owns any NFTs it cannot be deleted. + Keylet const first = keylet::nftpage_min(account); + Keylet const last = keylet::nftpage_max(account); + + auto const cp = ctx.view.read( + Keylet(ltNFTOKEN_PAGE, ctx.view.succ(first.key, last.key.next()).value_or(last.key))); + if (cp) + return tecHAS_OBLIGATIONS; + } + + // We don't allow an account to be deleted if its sequence number + // is within 256 of the current ledger. This prevents replay of old + // transactions if this account is resurrected after it is deleted. + // + // We look at the account's Sequence rather than the transaction's + // Sequence in preparation for Tickets. + if ((*srcSle)[sfSequence] + seqDelta > ctx.view.seq()) + return tecTOO_SOON; + + // When fixNFTokenRemint is enabled, we don't allow an account to be + // deleted if is within 256 of the + // current ledger. This is to prevent having duplicate NFTokenIDs after + // account re-creation. + // + // Without this restriction, duplicate NFTokenIDs can be reproduced when + // authorized minting is involved. Because when the minter mints a NFToken, + // the issuer's sequence does not change. So when the issuer re-creates + // their account and mints a NFToken, it is possible that the + // NFTokenSequence of this NFToken is the same as the one that the + // authorized minter minted in a previous ledger. + if ((*srcSle)[~sfFirstNFTokenSequence].value_or(0) + (*srcSle)[~sfMintedNFTokens].value_or(0) + + seqDelta > + ctx.view.seq()) + return tecTOO_SOON; + + // Verify that the account does not own any objects that would prevent + // the account from being deleted. + Keylet const ownerDirKeylet{keylet::ownerDir(account)}; + if (dirIsEmpty(ctx.view, ownerDirKeylet)) + return tesSUCCESS; + + std::shared_ptr sleDirNode{}; + unsigned int uDirEntry{0}; + uint256 dirEntry{beast::Zero}; + + // Account has no directory at all. This _should_ have been caught + // by the dirIsEmpty() check earlier, but it's okay to catch it here. + if (!cdirFirst(ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry)) + return tesSUCCESS; + + std::int32_t deletableDirEntryCount{0}; + do + { + // Make sure any directory node types that we find are the kind + // we can delete. + auto sleItem = ctx.view.read(keylet::child(dirEntry)); + if (!sleItem) + { + // Directory node has an invalid index. Bail out. + JLOG(ctx.j.fatal()) << "DeleteAccount: directory node in ledger " << ctx.view.seq() + << " has index to object that is missing: " << to_string(dirEntry); + return tefBAD_LEDGER; + } + + LedgerEntryType const nodeType{safe_cast((*sleItem)[sfLedgerEntryType])}; + + if (!nonObligationDeleter(nodeType)) + return tecHAS_OBLIGATIONS; + + // We found a deletable directory entry. Count it. If we find too + // many deletable directory entries then bail out. + if (++deletableDirEntryCount > maxDeletableDirEntries) + return tefTOO_BIG; + + } while (cdirNext(ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry)); + + return tesSUCCESS; +} + +TER +deleteDoApply( + ApplyContext& applyCtx, + STAmount const& accountBalance, + AccountID const& account, + AccountID const& dest) +{ + auto& view = applyCtx.view(); + STTx const tx = applyCtx.tx; + beast::Journal j = applyCtx.journal; + + auto srcSle = view.peek(keylet::account(account)); + XRPL_ASSERT(srcSle, "xrpl::deleteDoApply : non-null source account"); + + if (!srcSle) + return tefBAD_LEDGER; + + auto destSle = view.peek(keylet::account(dest)); + XRPL_ASSERT(destSle, "xrpl::deleteDoApply : non-null destination account"); + + if (!destSle) + return tefBAD_LEDGER; + + if (tx.isFieldPresent(sfCredentialIDs)) + { + if (auto err = verifyDepositPreauth(tx, view, account, dest, destSle, j); + !isTesSuccess(err)) + return err; + } + + Keylet const ownerDirKeylet{keylet::ownerDir(account)}; + auto const ter = cleanupOnAccountDelete( + view, + ownerDirKeylet, + [&](LedgerEntryType nodeType, + uint256 const& dirEntry, + std::shared_ptr& sleItem) -> std::pair { + if (auto deleter = nonObligationDeleter(nodeType)) + { + TER const result{deleter(applyCtx.registry, view, account, dirEntry, sleItem, j)}; + + return {result, SkipEntry::No}; + } + + UNREACHABLE( + "xrpl::deleteDoApply : undeletable item not found " + "in preclaim"); + JLOG(j.error()) << "DeleteAccount undeletable item not " + "found in preclaim."; + return {tecHAS_OBLIGATIONS, SkipEntry::No}; + }, + j); + if (ter != tesSUCCESS) + return ter; + + // Transfer any XRP remaining after the fee is paid to the destination: + (*destSle)[sfBalance] = (*destSle)[sfBalance] + accountBalance; + (*srcSle)[sfBalance] = (*srcSle)[sfBalance] - accountBalance; + applyCtx.deliver(accountBalance); + + // DA: Pseudo accounts can have 0 balance, so we skip this assert. + // FIX FIX FIX: DA FIX + // XRPL_ASSERT( + // (*srcSle)[sfBalance] == XRPAmount(0), + // "xrpl::deleteDoApply : source balance is zero"); + + // If there's still an owner directory associated with the source account + // delete it. + if (view.exists(ownerDirKeylet) && !view.emptyDirDelete(ownerDirKeylet)) + { + JLOG(j.error()) << "DeleteAccount cannot delete root dir node of " << toBase58(account); + return tecHAS_OBLIGATIONS; + } + + // Re-arm the password change fee if we can and need to. + if (accountBalance > XRPAmount(0) && (*destSle).isFlag(lsfPasswordSpent)) + (*destSle).clearFlag(lsfPasswordSpent); + + view.update(destSle); + view.erase(srcSle); + + return tesSUCCESS; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/transactors/account/AccountDelete.cpp b/src/libxrpl/tx/transactors/account/AccountDelete.cpp index c0e8fe05c67..b0d3ea2f496 100644 --- a/src/libxrpl/tx/transactors/account/AccountDelete.cpp +++ b/src/libxrpl/tx/transactors/account/AccountDelete.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -187,6 +188,18 @@ removeDelegateFromLedger( return DelegateSet::deleteDelegate(view, sleDel, j); } +TER +removeContractFromLedger( + ServiceRegistry&, + ApplyView& view, + AccountID const& account, + uint256 const& /*delIndex*/, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return ContractDelete::deleteContract(view, sleDel, account, j); +} + // Return nullptr if the LedgerEntryType represents an obligation that can't // be deleted. Otherwise return the pointer to the function that can delete // the non-obligation @@ -213,6 +226,8 @@ nonObligationDeleter(LedgerEntryType t) return removeCredentialFromLedger; case ltDELEGATE: return removeDelegateFromLedger; + case ltCONTRACT: + return removeContractFromLedger; default: return nullptr; } diff --git a/src/libxrpl/tx/transactors/contract/ContractCall.cpp b/src/libxrpl/tx/transactors/contract/ContractCall.cpp new file mode 100644 index 00000000000..110d4b8dd8b --- /dev/null +++ b/src/libxrpl/tx/transactors/contract/ContractCall.cpp @@ -0,0 +1,330 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +XRPAmount +ContractCall::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + XRPAmount extraFee{0}; + if (auto const allowance = tx[~sfComputationAllowance]; allowance) + { + extraFee += (*allowance) * view.fees().gasPrice / MICRO_DROPS_PER_DROP; + } + return Transactor::calculateBaseFee(view, tx) + extraFee; +} + +NotTEC +ContractCall::preflight(PreflightContext const& ctx) +{ + auto const flags = ctx.tx.getFlags(); + if (flags & tfUniversalMask) + { + JLOG(ctx.j.trace()) << "ContractCreate: tfUniversalMask is not allowed."; + return temINVALID_FLAG; + } + + return tesSUCCESS; +} + +TER +ContractCall::preclaim(PreclaimContext const& ctx) +{ + AccountID const account = ctx.tx[sfAccount]; + auto const accountSle = ctx.view.read(keylet::account(account)); + if (!accountSle) + { + JLOG(ctx.j.trace()) << "ContractCall: Account does not exist."; + return tecNO_TARGET; + } + + // The ContractAccount doesn't exist or isn't a smart contract + // pseudo-account. + AccountID const contractAccount = ctx.tx[sfContractAccount]; + auto const caSle = ctx.view.read(keylet::account(contractAccount)); + if (!caSle) + { + JLOG(ctx.j.trace()) << "ContractCall: Contract Account does not exist."; + return tecNO_TARGET; + } + + // The function doesn't exist on the provided contract. + uint256 const contractID = caSle->getFieldH256(sfContractID); + auto const contractSle = ctx.view.read(keylet::contract(contractID)); + if (!contractSle) + { + JLOG(ctx.j.trace()) << "ContractCall: Contract does not exist."; + return tecNO_TARGET; + } + + if (!contractSle->at(sfContractHash)) + { + JLOG(ctx.j.trace()) << "ContractCall: Contract does not have a hash."; + return tecNO_TARGET; + } + + auto const contractSourceSle = + ctx.view.read(keylet::contractSource(contractSle->at(sfContractHash))); + if (!contractSourceSle) + { + JLOG(ctx.j.trace()) << "ContractCall: ContractSource does not exist."; + return tecNO_TARGET; + } + + if (!contractSourceSle->isFieldPresent(sfFunctions)) + { + JLOG(ctx.j.trace()) << "ContractCall: Contract does not have any functions defined."; + return temMALFORMED; + } + + auto const& functions = contractSourceSle->getFieldArray(sfFunctions); + auto const functionName = ctx.tx.getFieldVL(sfFunctionName); + std::string functionNameHexStr(functionName.begin(), functionName.end()); + auto it = std::find_if( + functions.begin(), functions.end(), [&functionNameHexStr](STObject const& func) { + auto const funcName = func.getFieldVL(sfFunctionName); + std::string const functionNameDefHexStr(funcName.begin(), funcName.end()); + return functionNameDefHexStr == functionNameHexStr; + }); + + if (it == functions.end()) + { + JLOG(ctx.j.trace()) << "ContractCall: FunctionName: " << functionNameHexStr + << " does not exist in contract abi."; + return temMALFORMED; + } + + if (ctx.tx.isFieldPresent(sfParameters)) + { + STArray const& params = ctx.tx.getFieldArray(sfParameters); + if (auto ter = + contract::preclaimFlagParameters(ctx.view, account, contractAccount, params, ctx.j); + !isTesSuccess(ter)) + { + JLOG(ctx.j.trace()) << "ContractCreate: Failed to preclaim flag parameters."; + return ter; + } + } + + // The parameters don't match the function's ABI. + return tesSUCCESS; +} + +TER +ContractCall::doApply() +{ + AccountID const contractAccount = ctx_.tx[sfContractAccount]; + + auto const caSle = ctx_.view().read(keylet::account(contractAccount)); + if (!caSle) + { + JLOG(j_.trace()) << "ContractCall: ContractAccount does not exist."; + return tefINTERNAL; + } + + auto const accountSle = ctx_.view().read(keylet::account(account_)); + if (!accountSle) + { + JLOG(j_.trace()) << "ContractCall: Account does not exist."; + return tefINTERNAL; + } + + uint256 const contractID = caSle->getFieldH256(sfContractID); + Keylet const k = keylet::contract(contractID); + auto const contractSle = ctx_.view().read(k); + if (!contractSle) + { + JLOG(j_.trace()) << "ContractCall: Contract does not exist."; + return tefINTERNAL; + } + + uint256 const contractHash = contractSle->at(sfContractHash); + auto const contractSourceSle = + ctx_.view().read(keylet::contractSource(contractSle->at(sfContractHash))); + if (!contractSourceSle) + { + JLOG(j_.trace()) << "ContractCall: ContractSource does not exist."; + return tefINTERNAL; + } + + // // Handle the flags for the contract call. + if (ctx_.tx.isFieldPresent(sfParameters)) + { + STArray const& params = ctx_.tx.getFieldArray(sfParameters); + if (auto ter = contract::doApplyFlagParameters( + ctx_.view(), + ctx_.tx, + account_, + contractAccount, + params, + preFeeBalance_, + ctx_.journal); + !isTesSuccess(ter)) + { + JLOG(ctx_.journal.trace()) << "ContractCall: Failed to handle flag parameters."; + return ter; + } + } + + // WASM execution + auto const wasmStr = contractSourceSle->getFieldVL(sfContractCode); + std::vector const wasm(wasmStr.begin(), wasmStr.end()); + auto const functionName = ctx_.tx.getFieldVL(sfFunctionName); + std::string const funcName(functionName.begin(), functionName.end()); + + auto const contractFunctions = contractSle->isFieldPresent(sfFunctions) + ? contractSle->getFieldArray(sfFunctions) + : contractSourceSle->getFieldArray(sfFunctions); + std::optional function; + for (auto const& contractFunction : contractFunctions) + { + if (contractFunction.getFieldVL(sfFunctionName) == functionName) + function = contractFunction; + } + if (!function) + { + JLOG(j_.trace()) << "ContractCall: FunctionName does not exist in contract."; + return tefINTERNAL; + } + + // ContractCall Parameters + std::vector functionParameters; + if (ctx_.tx.isFieldPresent(sfParameters)) + { + STArray const& funcParams = ctx_.tx.getFieldArray(sfParameters); + functionParameters = getParameterValueVec(funcParams); + } + + // ContractSource/Contract Default Parameters + std::vector instanceParameters; + if (contractSle->isFieldPresent(sfInstanceParameterValues)) + { + STArray const& instParams = contractSle->getFieldArray(sfInstanceParameterValues); + instanceParameters = getParameterValueVec(instParams); + } + + // The parameters don't match the function's ABI. + std::vector typeVec; + if (function->isFieldPresent(sfParameters)) + { + STArray const& funcParamsDef = function->getFieldArray(sfParameters); + typeVec = xrpl::getParameterTypeVec(funcParamsDef); + if (functionParameters.size() != typeVec.size()) + return tecINVALID_PARAMETERS; + } + + for (std::size_t i = 0; i < functionParameters.size(); i++) + { + if (functionParameters[i].value.getInnerSType() != typeVec[i].type.getInnerSType()) + return tecINVALID_PARAMETERS; + } + + xrpl::ContractDataMap const dataMap; + xrpl::ContractEventMap const eventMap; + ContractContext contractCtx = { + .applyCtx = ctx_, + .instanceParameters = instanceParameters, + .functionParameters = functionParameters, + .built_txns = {}, + .expected_etxn_count = 1, + .generation = 0, + .burden = 0, + .result = + { + .contractHash = contractHash, + .contractKeylet = k, + .contractSourceKeylet = k, + .contractAccountKeylet = k, + .contractAccount = contractAccount, + .nextSequence = caSle->getFieldU32(sfSequence), + .otxnAccount = account_, + .otxnId = ctx_.tx.getTransactionID(), + .exitReason = "", + .exitCode = -1, + .dataMap = dataMap, + .eventMap = eventMap, + .changedDataCount = 0, + }, + .emitView = std::nullopt, + }; + + ContractHostFunctionsImpl ledgerDataProvider(contractCtx); + + if (!ctx_.tx.isFieldPresent(sfComputationAllowance)) + { + JLOG(j_.trace()) << "ContractCall: Computation allowance is not set."; + return tefINTERNAL; + } + + std::uint32_t const allowance = ctx_.tx[sfComputationAllowance]; + auto re = runEscrowWasm(wasm, ledgerDataProvider, allowance, funcName, {}); + + // Wasm Result + if (re.has_value()) + { + // TODO: better error handling for this conversion + // if (allowance > re.value().cost) + // { + // allowance -= static_cast(re.value().cost); + // // auto const returnAllowance = [&]() { + // // ctx_.view().update( + // // keylet::account(contractAccount), + // // [allowance](SLE& sle) { + // // sle.setFieldU32( + // // sfBalance, + // // sle.getFieldU32(sfBalance) + allowance); + // // }); + // // }; + // // returnAllowance(); + // } + + ctx_.setGasUsed(static_cast(re.value().cost)); + auto ret = re.value().result; + if (ret < 0) + { + JLOG(j_.trace()) << "WASM Execution Failed: " << ret; + ctx_.setWasmReturnCode(ret); + // ctx_.setWasmReturnStr(contractCtx.result.exitReason); + return tecWASM_REJECTED; + } + + if (auto res = contract::finalizeContractData( + ctx_.registry, + ctx_.view(), + contractAccount, + contractCtx.result.dataMap, + contractCtx.result.eventMap, + ctx_.tx.getTransactionID()); + !isTesSuccess(res)) + { + JLOG(j_.trace()) << "Contract data finalization failed: " << transHuman(res); + return res; + } + + ctx_.setWasmReturnCode(ret); + // ctx_.setWasmReturnStr(contractCtx.result.exitReason); + ctx_.setEmittedTxns(contractCtx.result.emittedTxns); + return tesSUCCESS; + } + else + { + JLOG(j_.trace()) << "WASM Failure: " + transHuman(re.error()); + auto const errorCode = TERtoInt(re.error()); + ctx_.setWasmReturnCode(errorCode); + // ctx_.setWasmReturnStr(contractCtx.result.exitReason); + return re.error(); + } + return tesSUCCESS; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/transactors/contract/ContractClawback.cpp b/src/libxrpl/tx/transactors/contract/ContractClawback.cpp new file mode 100644 index 00000000000..1712d07c8cf --- /dev/null +++ b/src/libxrpl/tx/transactors/contract/ContractClawback.cpp @@ -0,0 +1,32 @@ +#include +#include +#include + +namespace xrpl { + +NotTEC +ContractClawback::preflight(PreflightContext const& ctx) +{ + auto const flags = ctx.tx.getFlags(); + if (flags & tfUniversalMask) + { + JLOG(ctx.j.trace()) << "ContractClawback: tfUniversalMask is not allowed."; + return temINVALID_FLAG; + } + + return tesSUCCESS; +} + +TER +ContractClawback::preclaim(PreclaimContext const& ctx) +{ + return tesSUCCESS; +} + +TER +ContractClawback::doApply() +{ + return tesSUCCESS; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/transactors/contract/ContractCreate.cpp b/src/libxrpl/tx/transactors/contract/ContractCreate.cpp new file mode 100644 index 00000000000..2b91baf403f --- /dev/null +++ b/src/libxrpl/tx/transactors/contract/ContractCreate.cpp @@ -0,0 +1,272 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +XRPAmount +ContractCreate::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + XRPAmount const maxAmount{std::numeric_limits::max()}; + XRPAmount createFee{0}; + + if (tx.isFieldPresent(sfCreateCode)) + createFee = XRPAmount{contract::contractCreateFee(tx.getFieldVL(sfCreateCode).size())}; + + if (createFee > maxAmount - view.fees().increment) + { + JLOG(debugLog().trace()) << "ContractCreate: Create fee overflow detected."; + return XRPAmount{INITIAL_XRP}; + } + + createFee += view.fees().increment; + + auto baseFee = Transactor::calculateBaseFee(view, tx); + if (baseFee > maxAmount - createFee) + { + JLOG(debugLog().trace()) << "ContractCreate: Total fee overflow detected."; + return XRPAmount{INITIAL_XRP}; + } + + return createFee + baseFee; +} + +std::uint32_t +ContractCreate::getFlagsMask(PreflightContext const& ctx) +{ + return tfContractMask; +} + +NotTEC +ContractCreate::preflight(PreflightContext const& ctx) +{ + auto const flags = ctx.tx.getFlags(); + + if ((flags & (tfCodeImmutable | tfABIImmutable | tfImmutable)) > tfImmutable) + { + JLOG(ctx.j.trace()) << "ContractCreate: Cannot set more than one immutability flag."; + return temINVALID_FLAG; + } + + if (!ctx.tx.isFieldPresent(sfContractCode) && !ctx.tx.isFieldPresent(sfContractHash)) + { + JLOG(ctx.j.trace()) << "ContractCreate: Neither ContractCode nor ContractHash present"; + return temMALFORMED; + } + + if (ctx.tx.isFieldPresent(sfContractCode) && ctx.tx.isFieldPresent(sfContractHash)) + { + JLOG(ctx.j.trace()) << "ContractCreate: Both ContractCode and ContractHash present"; + return temMALFORMED; + } + + if (auto const res = contract::preflightFunctions(ctx.tx, ctx.j); !isTesSuccess(res)) + { + JLOG(ctx.j.trace()) << "ContractCreate: Functions validation failed: " << transToken(res); + return res; + } + + if (auto const res = contract::preflightInstanceParameters(ctx.tx, ctx.j); !isTesSuccess(res)) + { + JLOG(ctx.j.trace()) << "ContractCreate: InstanceParameters validation failed: " + << transToken(res); + return res; + } + + if (auto const res = contract::preflightInstanceParameterValues(ctx.tx, ctx.j); + !isTesSuccess(res)) + { + JLOG(ctx.j.trace()) << "ContractCreate: InstanceParameterValues validation failed: " + << transToken(res); + return res; + } + + return tesSUCCESS; +} + +TER +ContractCreate::preclaim(PreclaimContext const& ctx) +{ + // ContractHash is provided but there is no existing corresponding + // ContractSource ledger object + bool isInstall = ctx.tx.isFieldPresent(sfContractHash); + auto contractHash = ctx.tx.at(~sfContractHash); + if (isInstall && !ctx.view.exists(keylet::contractSource(*contractHash))) + { + JLOG(ctx.j.trace()) << "ContractCreate: ContractHash provided but no " + "corresponding ContractSource exists"; + return temMALFORMED; + } + + // The ContractCode provided is invalid. + if (ctx.tx.isFieldPresent(sfContractCode)) + { + xrpl::Blob wasmBytes = ctx.tx.getFieldVL(sfContractCode); + if (wasmBytes.empty()) + { + JLOG(ctx.j.trace()) << "ContractCreate: ContractCode provided is empty."; + return temMALFORMED; + } + + contractHash = xrpl::sha512Half_s(xrpl::Slice(wasmBytes.data(), wasmBytes.size())); + if (ctx.view.exists(keylet::contractSource(*contractHash))) + isInstall = true; + + // Iterate through the functions and validate them? + // HostFunctions mock; + // auto const re = preflightEscrowWasm(wasmBytes, "finish", {}, &mock, + // ctx.j); if (!isTesSuccess(re)) + // { + // JLOG(ctx.j.debug()) << "EscrowCreate.FinishFunction bad WASM"; + // return re; + // } + } + + // The ABI provided in Functions doesn't match the code. + + // InstanceParameters don't match what's in the existing ContractSource + // ledger object. + if (isInstall && ctx.tx.isFieldPresent(sfInstanceParameterValues)) + { + auto const sle = ctx.view.read(keylet::contractSource(*contractHash)); + if (!sle) + return tefINTERNAL; // LCOV_EXCL_LINE + + // Already validated in preflight, but we can check here too. + auto const& instanceParams = sle->getFieldArray(sfInstanceParameters); + auto const& instanceParamValues = ctx.tx.getFieldArray(sfInstanceParameterValues); + if (auto const isValid = + contract::validateParameterMapping(instanceParams, instanceParamValues, ctx.j); + !isValid) + { + JLOG(ctx.j.trace()) << "ContractCreate: InstanceParameters do not match what's in " + "the existing ContractSource ledger object."; + return temMALFORMED; + } + } + + return tesSUCCESS; +} + +TER +ContractCreate::doApply() +{ + auto const accountSle = ctx_.view().peek(keylet::account(account_)); + if (!accountSle) + { + JLOG(j_.trace()) << "ContractCreate: Account not found."; + return tefINTERNAL; // LCOV_EXCL_LINE + } + + std::shared_ptr sourceSle; + bool isInstall = ctx_.tx.isFieldPresent(sfContractHash); + auto contractHash = ctx_.tx[~sfContractHash]; + xrpl::Blob wasmBytes; + if (ctx_.tx.isFieldPresent(sfContractCode)) + { + wasmBytes = ctx_.tx.getFieldVL(sfContractCode); + contractHash = xrpl::sha512Half_s(xrpl::Slice(wasmBytes.data(), wasmBytes.size())); + if (ctx_.view().exists(keylet::contractSource(*contractHash))) + isInstall = true; + } + + if (isInstall) + { + sourceSle = ctx_.view().peek(keylet::contractSource(*contractHash)); + if (!sourceSle) + return tefINTERNAL; // LCOV_EXCL_LINE + + sourceSle->at(sfReferenceCount) = sourceSle->getFieldU64(sfReferenceCount) + 1; + ctx_.view().update(sourceSle); + } + else + { + sourceSle = std::make_shared(keylet::contractSource(*contractHash)); + sourceSle->at(sfContractHash) = *contractHash; + sourceSle->at(sfContractCode) = makeSlice(wasmBytes); + sourceSle->setFieldArray(sfFunctions, ctx_.tx.getFieldArray(sfFunctions)); + if (ctx_.tx.isFieldPresent(sfInstanceParameters)) + sourceSle->setFieldArray( + sfInstanceParameters, ctx_.tx.getFieldArray(sfInstanceParameters)); + sourceSle->at(sfReferenceCount) = 1; + + ctx_.view().insert(sourceSle); + } + + std::uint32_t const seq = ctx_.tx.getSeqValue(); + auto const contractKeylet = keylet::contract(*contractHash, account_, seq); + auto contractSle = std::make_shared(contractKeylet); + + auto maybePseudo = createPseudoAccount(view(), contractSle->key(), sfContractID); + if (!maybePseudo) + return maybePseudo.error(); // LCOV_EXCL_LINE + + auto& pseudoSle = *maybePseudo; + auto pseudoAccount = pseudoSle->at(sfAccount); + + contractSle->at(sfContractAccount) = pseudoAccount; + contractSle->at(sfOwner) = account_; + contractSle->at(sfFlags) = ctx_.tx.getFlags(); + contractSle->at(sfSequence) = seq; + contractSle->at(sfContractHash) = *contractHash; + if (ctx_.tx.isFieldPresent(sfInstanceParameterValues)) + contractSle->setFieldArray( + sfInstanceParameterValues, ctx_.tx.getFieldArray(sfInstanceParameterValues)); + + if (ctx_.tx.isFieldPresent(sfURI)) + contractSle->setFieldVL(sfURI, ctx_.tx.getFieldVL(sfURI)); + + ctx_.view().insert(contractSle); + + // Handle the instance parameters for the contract creation. + if (ctx_.tx.isFieldPresent(sfInstanceParameterValues)) + { + STArray const& params = ctx_.tx.getFieldArray(sfInstanceParameterValues); + + // Note: We have to do preclaim and apply here because we will only have + // the pseudo account after the contract is created. + if (auto ter = + contract::preclaimFlagParameters(ctx_.view(), account_, pseudoAccount, params, j_); + !isTesSuccess(ter)) + { + JLOG(j_.trace()) << "ContractCreate: Failed to preclaim flag parameters."; + return ter; + } + + if (auto ter = contract::doApplyFlagParameters( + ctx_.view(), ctx_.tx, account_, pseudoAccount, params, preFeeBalance_, j_); + !isTesSuccess(ter)) + { + JLOG(j_.trace()) << "ContractCreate: Failed to apply flag parameters."; + return ter; + } + } + + // Add Contract to ContractAccount Dir + // TODO: use dirLink + { + auto const page = view().dirInsert( + keylet::ownerDir(pseudoAccount), contractKeylet, describeOwnerDir(pseudoAccount)); + + if (!page) + return tecDIR_FULL; + + contractSle->setFieldU64(sfOwnerNode, *page); + adjustOwnerCount(ctx_.view(), pseudoSle, 1, j_); + } + + return tesSUCCESS; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/transactors/contract/ContractDelete.cpp b/src/libxrpl/tx/transactors/contract/ContractDelete.cpp new file mode 100644 index 00000000000..781b558dc1c --- /dev/null +++ b/src/libxrpl/tx/transactors/contract/ContractDelete.cpp @@ -0,0 +1,149 @@ +#include +#include +#include +#include +#include + +namespace xrpl { + +NotTEC +ContractDelete::preflight(PreflightContext const& ctx) +{ + auto const flags = ctx.tx.getFlags(); + if (flags & tfUniversalMask) + { + JLOG(ctx.j.trace()) << "ContractDelete: only tfUniversalMask is allowed."; + return temINVALID_FLAG; + } + + return tesSUCCESS; +} + +TER +ContractDelete::preclaim(PreclaimContext const& ctx) +{ + AccountID const account = ctx.tx.getAccountID(sfAccount); + AccountID const contractAccount = + ctx.tx.isFieldPresent(sfContractAccount) ? ctx.tx.getAccountID(sfContractAccount) : account; + + auto const caSle = ctx.view.read(keylet::account(contractAccount)); + if (!caSle) + { + JLOG(ctx.j.trace()) << "ContractDelete: Account does not exist."; + return terNO_ACCOUNT; + } + + if (!caSle->isFieldPresent(sfContractID)) + { + JLOG(ctx.j.trace()) << "ContractDelete: Account is not a smart " + "contract pseudo-account."; + return tecNO_PERMISSION; + } + + uint256 const contractID = caSle->getFieldH256(sfContractID); + auto const contractSle = ctx.view.read(keylet::contract(contractID)); + if (!contractSle) + { + JLOG(ctx.j.trace()) << "ContractDelete: Contract does not exist."; + return tecNO_TARGET; + } + + if (contractSle->getAccountID(sfOwner) != account) + { + JLOG(ctx.j.trace()) << "ContractDelete: Cannot delete a contract that " + "does not belong to the account."; + return tecNO_PERMISSION; + } + + std::uint32_t const flags = contractSle->getFlags(); + + // Check if the contract is undeletable. + if (flags & tfUndeletable) + { + JLOG(ctx.j.trace()) << "ContractDelete: Contract is undeletable."; + return tecNO_PERMISSION; + } + + AccountID const owner = contractSle->getAccountID(sfOwner); + if (auto const res = deletePreclaim(ctx, 0, account, owner, true); !isTesSuccess(res)) + return res; + return tesSUCCESS; +} + +TER +ContractDelete::deleteContract( + ApplyView& view, + std::shared_ptr const& sle, + AccountID const& account, + beast::Journal j) +{ + if (!sle) + return tecINTERNAL; // LCOV_EXCL_LINE + + if (!view.dirRemove(keylet::ownerDir(account), (*sle)[sfOwnerNode], sle->key(), false)) + { + // LCOV_EXCL_START + JLOG(j.trace()) << "Unable to delete Delegate from owner."; + return tefBAD_LEDGER; + // LCOV_EXCL_STOP + } + + auto const sleOwner = view.peek(keylet::account(account)); + if (!sleOwner) + return tecINTERNAL; // LCOV_EXCL_LINE + + adjustOwnerCount(view, sleOwner, -1, j); + view.erase(sle); + return tesSUCCESS; +} + +TER +ContractDelete::doApply() +{ + AccountID const account = ctx_.tx.getAccountID(sfAccount); + AccountID const contractAccount = ctx_.tx.isFieldPresent(sfContractAccount) + ? ctx_.tx.getAccountID(sfContractAccount) + : account; + + auto const caSle = ctx_.view().read(keylet::account(contractAccount)); + if (!caSle) + { + JLOG(j_.trace()) << "ContractModify: Account does not exist."; + return tefBAD_LEDGER; + } + + uint256 const contractID = caSle->getFieldH256(sfContractID); + auto const contractSle = ctx_.view().read(keylet::contract(contractID)); + if (!contractSle) + { + JLOG(j_.trace()) << "ContractDelete: Contract does not exist."; + return tecNO_TARGET; + } + + // Lower the reference count of the ContractSource or remove the source from + // the ledger. + uint256 const contractHash = contractSle->getFieldH256(sfContractHash); + auto oldSourceSle = ctx_.view().peek(keylet::contractSource(contractHash)); + if (oldSourceSle->getFieldU64(sfReferenceCount) == 1) + { + // NOTE: We dont adjust the owner count because ContractSource is an + // unowned object. + ctx_.view().erase(oldSourceSle); + } + else + { + oldSourceSle->setFieldU64( + sfReferenceCount, oldSourceSle->getFieldU64(sfReferenceCount) - 1); + ctx_.view().update(oldSourceSle); + } + + AccountID const owner = contractSle->getAccountID(sfOwner); + STAmount const contractBalance = (*caSle)[sfBalance]; + if (auto const res = deleteDoApply(ctx_, contractBalance, contractAccount, owner); + !isTesSuccess(res)) + return res; + + return tesSUCCESS; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/transactors/contract/ContractModify.cpp b/src/libxrpl/tx/transactors/contract/ContractModify.cpp new file mode 100644 index 00000000000..24069e9ffcc --- /dev/null +++ b/src/libxrpl/tx/transactors/contract/ContractModify.cpp @@ -0,0 +1,352 @@ +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +XRPAmount +ContractModify::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + XRPAmount const maxAmount{std::numeric_limits::max()}; + XRPAmount createFee{0}; + + if (tx.isFieldPresent(sfCreateCode)) + createFee = XRPAmount{contract::contractCreateFee(tx.getFieldVL(sfCreateCode).size())}; + + if (createFee > maxAmount - view.fees().increment) + { + JLOG(debugLog().trace()) << "ContractModify: Create fee overflow detected."; + return XRPAmount{INITIAL_XRP}; + } + + auto baseFee = Transactor::calculateBaseFee(view, tx); + if (baseFee > maxAmount - createFee) + { + JLOG(debugLog().trace()) << "ContractModify: Total fee overflow detected."; + return XRPAmount{INITIAL_XRP}; + } + + return createFee + baseFee; +} + +NotTEC +ContractModify::preflight(PreflightContext const& ctx) +{ + auto const flags = ctx.tx.getFlags(); + if (flags & tfUniversalMask) + { + JLOG(ctx.j.trace()) << "ContractModify: only tfUniversalMask is allowed."; + return temINVALID_FLAG; + } + + // Either ContractCode or ContractHash must be present. + if (ctx.tx.isFieldPresent(sfContractCode) && ctx.tx.isFieldPresent(sfContractHash)) + { + JLOG(ctx.j.trace()) << "ContractModify: Both ContractCode and ContractHash present"; + return temMALFORMED; + } + + // Validate Functions & Function Parameters. + if (auto const res = contract::preflightFunctions(ctx.tx, ctx.j); !isTesSuccess(res)) + return res; + + // Validate Instance Parameters. + if (auto const res = contract::preflightInstanceParameters(ctx.tx, ctx.j); !isTesSuccess(res)) + return res; + + // Validate Instance Parameter Values. + if (auto const res = contract::preflightInstanceParameterValues(ctx.tx, ctx.j); + !isTesSuccess(res)) + return res; + + if (ctx.tx.isFieldPresent(sfOwner)) + { + if (ctx.tx.getAccountID(sfOwner) == ctx.tx.getAccountID(sfAccount)) + return temMALFORMED; + + if (ctx.tx.getAccountID(sfOwner) == ctx.tx.getAccountID(sfContractAccount)) + return temMALFORMED; + } + + return tesSUCCESS; +} + +TER +ContractModify::preclaim(PreclaimContext const& ctx) +{ + AccountID const account = ctx.tx.getAccountID(sfAccount); + AccountID const contractAccount = + ctx.tx.isFieldPresent(sfContractAccount) ? ctx.tx.getAccountID(sfContractAccount) : account; + + auto const contractAccountSle = ctx.view.read(keylet::account(contractAccount)); + if (!contractAccountSle) + { + JLOG(ctx.j.trace()) << "ContractModify: Contract Account does not exist."; + return tecNO_TARGET; + } + + uint256 const contractID = contractAccountSle->getFieldH256(sfContractID); + auto const contractSle = ctx.view.read(keylet::contract(contractID)); + if (!contractSle) + { + JLOG(ctx.j.trace()) << "ContractModify: Contract does not exist."; + return tecNO_TARGET; + } + + if (ctx.tx.isFieldPresent(sfContractAccount) && contractSle->getAccountID(sfOwner) != account) + { + JLOG(ctx.j.trace()) << "ContractModify: Cannot modify a contract that " + "does not belong to the account."; + return tecNO_PERMISSION; + } + + std::uint32_t const flags = contractSle->getFlags(); + + // Check if the contract is immutable. + if (flags & tfImmutable) + { + JLOG(ctx.j.trace()) << "ContractModify: Contract is immutable."; + return tecNO_PERMISSION; + } + + // Check if the contract code is immutable. + if (flags & tfCodeImmutable && ctx.tx.isFieldPresent(sfContractCode)) + { + JLOG(ctx.j.trace()) << "ContractModify: ContractCode is immutable."; + return tecNO_PERMISSION; + } + + // Check if the contract ABI is immutable. + if (flags & tfABIImmutable) + { + if (!ctx.tx.isFieldPresent(sfContractCode)) + { + JLOG(ctx.j.trace()) << "ContractModify: ContractCode must be " + "present when modifying ABI."; + return tecNO_PERMISSION; + } + + if (!ctx.tx.isFieldPresent(sfFunctions)) + { + JLOG(ctx.j.trace()) << "ContractModify: Functions must be present " + "when modifying ABI."; + return tecNO_PERMISSION; + } + + JLOG(ctx.j.trace()) << "ContractModify: ABI is immutable."; + return tecNO_PERMISSION; + } + + // Can only include 1 of the 3 flags: tfCodeImmutable, tfABIImmutable, + // tfImmutable. + if ((flags & (tfCodeImmutable | tfABIImmutable | tfImmutable)) > tfImmutable) + { + JLOG(ctx.j.trace()) << "ContractModify: Cannot set more than one immutability flag."; + return temINVALID_FLAG; + } + + bool isInstall = ctx.tx.isFieldPresent(sfContractHash); + auto contractHash = ctx.tx.at(~sfContractHash); + if (ctx.tx.isFieldPresent(sfContractCode)) + { + xrpl::Blob wasmBytes = ctx.tx.getFieldVL(sfContractCode); + if (wasmBytes.empty()) + { + JLOG(ctx.j.trace()) << "ContractModify: ContractCode provided is empty."; + return temMALFORMED; + } + + contractHash = xrpl::sha512Half_s(xrpl::Slice(wasmBytes.data(), wasmBytes.size())); + if (ctx.view.exists(keylet::contractSource(*contractHash))) + isInstall = true; + + // Iterate through the functions and validate them? + // HostFunctions mock; + // auto const re = preflightEscrowWasm(wasmBytes, "finish", {}, &mock, + // ctx.j); if (!isTesSuccess(re)) + // { + // JLOG(ctx.j.debug()) << "EscrowCreate.FinishFunction bad WASM"; + // return re; + // } + } + + // The ABI provided in Functions doesn't match the code. + + if (isInstall) + { + auto const sle = ctx.view.read(keylet::contractSource(*contractHash)); + if (!sle) + { + JLOG(ctx.j.trace()) << "ContractModify: ContractSource ledger object not found for " + "the provided ContractHash."; + return tefINTERNAL; // LCOV_EXCL_LINE + } + + if (sle->isFieldPresent(sfInstanceParameters) && + !ctx.tx.isFieldPresent(sfInstanceParameterValues)) + { + JLOG(ctx.j.trace()) << "ContractModify: ContractHash is present, but " + "InstanceParameterValues is missing."; + return temMALFORMED; + } + + auto const& instanceParams = sle->getFieldArray(sfInstanceParameters); + auto const& instanceParamValues = ctx.tx.getFieldArray(sfInstanceParameterValues); + if (auto const isValid = + contract::validateParameterMapping(instanceParams, instanceParamValues, ctx.j); + !isValid) + { + JLOG(ctx.j.trace()) << "ContractModify: InstanceParameters do not match what's in " + "the existing ContractSource ledger object."; + return temMALFORMED; + } + } + + if (ctx.tx.isFieldPresent(sfOwner)) + { + auto const ownerSle = ctx.view.read(keylet::account(ctx.tx.getAccountID(sfOwner))); + if (!ownerSle) + { + JLOG(ctx.j.trace()) << "ContractModify: New owner account does not exist."; + return tecNO_TARGET; + } + } + + return tesSUCCESS; +} + +TER +ContractModify::doApply() +{ + AccountID const account = ctx_.tx.getAccountID(sfAccount); + AccountID const contractAccount = ctx_.tx.isFieldPresent(sfContractAccount) + ? ctx_.tx.getAccountID(sfContractAccount) + : account; + + auto const contractAccountSle = ctx_.view().read(keylet::account(contractAccount)); + if (!contractAccountSle) + { + JLOG(ctx_.journal.trace()) << "ContractModify: Account does not exist."; + return tefINTERNAL; + } + + uint256 const contractID = contractAccountSle->getFieldH256(sfContractID); + auto const contractSle = ctx_.view().peek(keylet::contract(contractID)); + if (!contractSle) + { + JLOG(ctx_.journal.trace()) << "ContractModify: Contract does not exist."; + return tefINTERNAL; + } + + auto currentSourceSle = + ctx_.view().peek(keylet::contractSource(contractSle->getFieldH256(sfContractHash))); + if (!currentSourceSle) + { + JLOG(ctx_.journal.trace()) << "ContractModify: ContractSource does not exist."; + return tefINTERNAL; + } + + if (ctx_.tx.isFieldPresent(sfContractCode)) + { + JLOG(ctx_.journal.trace()) << "ContractModify: Modifying ContractCode/ContractHash."; + xrpl::Blob wasmBytes = ctx_.tx.getFieldVL(sfContractCode); + auto const contractHash = + xrpl::sha512Half_s(xrpl::Slice(wasmBytes.data(), wasmBytes.size())); + auto const sourceKeylet = keylet::contractSource(contractHash); + auto sourceSle = ctx_.view().peek(sourceKeylet); + if (!sourceSle) + { + JLOG(ctx_.journal.trace()) << "ContractModify: Creating new ContractSource."; + // create the new ContractSource + sourceSle = std::make_shared(sourceKeylet); + sourceSle->at(sfContractHash) = contractHash; + sourceSle->at(sfContractCode) = makeSlice(wasmBytes); + sourceSle->setFieldArray(sfFunctions, ctx_.tx.getFieldArray(sfFunctions)); + if (ctx_.tx.isFieldPresent(sfInstanceParameters)) + sourceSle->setFieldArray( + sfInstanceParameters, ctx_.tx.getFieldArray(sfInstanceParameters)); + sourceSle->at(sfReferenceCount) = 1; + ctx_.view().insert(sourceSle); + } + + // update the Contract + contractSle->setFieldH256(sfContractHash, contractHash); + if (ctx_.tx.isFieldPresent(sfInstanceParameterValues)) + contractSle->setFieldArray( + sfInstanceParameterValues, ctx_.tx.getFieldArray(sfInstanceParameterValues)); + + ctx_.view().update(contractSle); + + // update the existing ContractSource + if (currentSourceSle->getFieldU64(sfReferenceCount) == 1) + { + // remove the old ContractSource if no more references + ctx_.view().erase(currentSourceSle); + } + else + { + // decrement the reference count + currentSourceSle->setFieldU64( + sfReferenceCount, currentSourceSle->getFieldU64(sfReferenceCount) - 1); + ctx_.view().update(currentSourceSle); + } + } + else if (ctx_.tx.isFieldPresent(sfContractHash)) + { + auto sourceSle = + ctx_.view().peek(keylet::contractSource(ctx_.tx.getFieldH256(sfContractHash))); + if (!sourceSle) + { + JLOG(ctx_.journal.trace()) << "ContractModify: ContractSource does not exist."; + return tefINTERNAL; + } + + // set new contract hash + contractSle->setFieldH256(sfContractHash, ctx_.tx.getFieldH256(sfContractHash)); + + // set new instance parameter values if present + if (ctx_.tx.isFieldPresent(sfInstanceParameterValues)) + contractSle->setFieldArray( + sfInstanceParameterValues, ctx_.tx.getFieldArray(sfInstanceParameterValues)); + + ctx_.view().update(contractSle); + + sourceSle->setFieldU64(sfReferenceCount, sourceSle->getFieldU64(sfReferenceCount) + 1); + ctx_.view().update(sourceSle); + + // update the existing ContractSource + if (currentSourceSle->getFieldU64(sfReferenceCount) == 1) + { + // remove the old ContractSource if no more references + ctx_.view().erase(currentSourceSle); + } + else + { + // decrement the reference count + currentSourceSle->setFieldU64( + sfReferenceCount, currentSourceSle->getFieldU64(sfReferenceCount) - 1); + ctx_.view().update(currentSourceSle); + } + } + else if (ctx_.tx.isFieldPresent(sfInstanceParameterValues)) + { + // only updating instance parameter values + contractSle->setFieldArray( + sfInstanceParameterValues, ctx_.tx.getFieldArray(sfInstanceParameterValues)); + + ctx_.view().update(contractSle); + } + + if (ctx_.tx.isFieldPresent(sfOwner)) + { + contractSle->setAccountID(sfOwner, ctx_.tx.getAccountID(sfOwner)); + ctx_.view().update(contractSle); + } + + return tesSUCCESS; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/transactors/contract/ContractUserDelete.cpp b/src/libxrpl/tx/transactors/contract/ContractUserDelete.cpp new file mode 100644 index 00000000000..22181c02d61 --- /dev/null +++ b/src/libxrpl/tx/transactors/contract/ContractUserDelete.cpp @@ -0,0 +1,32 @@ +#include +#include +#include + +namespace xrpl { + +NotTEC +ContractUserDelete::preflight(PreflightContext const& ctx) +{ + auto const flags = ctx.tx.getFlags(); + if (flags & tfUniversalMask) + { + JLOG(ctx.j.trace()) << "ContractUserDelete: tfUniversalMask is not allowed."; + return temINVALID_FLAG; + } + + return tesSUCCESS; +} + +TER +ContractUserDelete::preclaim(PreclaimContext const& ctx) +{ + return tesSUCCESS; +} + +TER +ContractUserDelete::doApply() +{ + return tesSUCCESS; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp index 123f83a1a67..b694de12dd8 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCancel.cpp @@ -210,7 +210,8 @@ EscrowCancel::doApply() } } - adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal); + auto const reserveToSubtract = calculateAdditionalReserve((*slep)[~sfFinishFunction]); + adjustOwnerCount(ctx_.view(), sle, -1 * reserveToSubtract, ctx_.journal); ctx_.view().update(sle); // Remove escrow from ledger diff --git a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp index 0b1db125f66..647255a8e17 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowCreate.cpp @@ -31,6 +31,11 @@ #include #include #include +#include +#include +#include + +#include #include #include @@ -113,6 +118,29 @@ escrowCreatePreflightHelper(PreflightContext const& ctx) return tesSUCCESS; } +XRPAmount +EscrowCreate::calculateBaseFee(ReadView const& view, STTx const& tx) +{ + XRPAmount txnFees{Transactor::calculateBaseFee(view, tx)}; + if (tx.isFieldPresent(sfFinishFunction)) + { + // 10 base fees for the transaction (1 is in + // `Transactor::calculateBaseFee`), plus 5 drops per byte + txnFees += 9 * view.fees().base + 5 * tx[sfFinishFunction].size(); + } + return txnFees; +} + +bool +EscrowCreate::checkExtraFeatures(PreflightContext const& ctx) +{ + if ((ctx.tx.isFieldPresent(sfFinishFunction) || ctx.tx.isFieldPresent(sfData)) && + !ctx.rules.enabled(featureSmartEscrow)) + return false; + + return true; +} + NotTEC EscrowCreate::preflight(PreflightContext const& ctx) { @@ -144,12 +172,19 @@ EscrowCreate::preflight(PreflightContext const& ctx) ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter]) return temBAD_EXPIRATION; + if (ctx.tx.isFieldPresent(sfFinishFunction) && !ctx.tx.isFieldPresent(sfCancelAfter)) + return temBAD_EXPIRATION; + // In the absence of a FinishAfter, the escrow can be finished // immediately, which can be confusing. When creating an escrow, // we want to ensure that either a FinishAfter time is explicitly // specified or a completion condition is attached. - if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition]) + if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition] && !ctx.tx[~sfFinishFunction]) + { + JLOG(ctx.j.debug()) << "Must have at least one of FinishAfter, " + "Condition, or FinishFunction."; return temMALFORMED; + } if (auto const cb = ctx.tx[~sfCondition]) { @@ -165,6 +200,60 @@ EscrowCreate::preflight(PreflightContext const& ctx) } } + if (ctx.tx.isFieldPresent(sfData)) + { + if (!ctx.tx.isFieldPresent(sfFinishFunction)) + { + JLOG(ctx.j.debug()) << "EscrowCreate with Data requires FinishFunction"; + return temMALFORMED; + } + auto const data = ctx.tx.getFieldVL(sfData); + if (data.size() > maxWasmDataLength) + { + JLOG(ctx.j.debug()) << "EscrowCreate.Data bad size " << data.size(); + return temMALFORMED; + } + } + + if (ctx.tx.isFieldPresent(sfFinishFunction)) + { + auto const fees(ctx.registry.get().getFees()); + if (fees.extensionSizeLimit == 0 || fees.extensionComputeLimit == 0) + { + JLOG(ctx.j.debug()) << "WASM runtime deactivated by fee voting"; + return temTEMP_DISABLED; + } + + auto const code = ctx.tx.getFieldVL(sfFinishFunction); + if (code.size() == 0 || code.size() > fees.extensionSizeLimit) + { + JLOG(ctx.j.debug()) << "EscrowCreate.FinishFunction bad size " << code.size(); + return temMALFORMED; + } + // actual validity of WASM code happens in `preflightSigValidated` + // (after the signature is checked) + } + + return tesSUCCESS; +} + +NotTEC +EscrowCreate::preflightSigValidated(PreflightContext const& ctx) +{ + if (ctx.tx.isFieldPresent(sfFinishFunction)) + { + auto const code = ctx.tx.getFieldVL(sfFinishFunction); + // basic checks happen in `preflight` + + HostFunctions mock(ctx.j); + auto const re = preflightEscrowWasm(code, mock, ESCROW_FUNCTION_NAME); + if (!isTesSuccess(re)) + { + JLOG(ctx.j.debug()) << "EscrowCreate.FinishFunction bad WASM"; + return re; + } + } + return tesSUCCESS; } @@ -423,8 +512,9 @@ EscrowCreate::doApply() // Check reserve and funds availability STAmount const amount{ctx_.tx[sfAmount]}; + auto const reserveToAdd = calculateAdditionalReserve(ctx_.tx[~sfFinishFunction]); - auto const reserve = ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1); + auto const reserve = ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + reserveToAdd); auto const balance = sle->getFieldAmount(sfBalance).xrp(); if (balance < reserve) @@ -458,6 +548,8 @@ EscrowCreate::doApply() (*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter]; (*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter]; (*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag]; + (*slep)[~sfFinishFunction] = ctx_.tx[~sfFinishFunction]; + (*slep)[~sfData] = ctx_.tx[~sfData]; if (ctx_.view().rules().enabled(fixIncludeKeyletFields)) { @@ -525,7 +617,7 @@ EscrowCreate::doApply() } // increment owner count - adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal); + adjustOwnerCount(ctx_.view(), sle, reserveToAdd, ctx_.journal); ctx_.view().update(sle); return tesSUCCESS; } diff --git a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp index 13bd4b16826..4da100a1685 100644 --- a/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp +++ b/src/libxrpl/tx/transactors/escrow/EscrowFinish.cpp @@ -29,6 +29,9 @@ #include #include #include +#include +#include +#include #include #include @@ -65,7 +68,14 @@ checkCondition(Slice f, Slice c) bool EscrowFinish::checkExtraFeatures(PreflightContext const& ctx) { - return !ctx.tx.isFieldPresent(sfCredentialIDs) || ctx.rules.enabled(featureCredentials); + if (ctx.tx.isFieldPresent(sfCredentialIDs) && !ctx.rules.enabled(featureCredentials)) + return false; + + if (ctx.tx.isFieldPresent(sfComputationAllowance) && !ctx.rules.enabled(featureSmartEscrow)) + { + return false; + } + return true; } NotTEC @@ -77,7 +87,32 @@ EscrowFinish::preflight(PreflightContext const& ctx) // If you specify a condition, then you must also specify // a fulfillment. if (static_cast(cb) != static_cast(fb)) + { + JLOG(ctx.j.debug()) << "Condition != Fulfillment"; return temMALFORMED; + } + + if (auto const allowance = ctx.tx[~sfComputationAllowance]; allowance) + { + auto const fees(ctx.registry.get().getFees()); + if (fees.extensionComputeLimit == 0) + { + JLOG(ctx.j.debug()) << "WASM runtime deactivated by fee voting"; + return temTEMP_DISABLED; + } + if (*allowance == 0) + { + return temBAD_LIMIT; + } + if (*allowance > fees.extensionComputeLimit) + { + JLOG(ctx.j.debug()) << "ComputationAllowance too large: " << *allowance; + return temBAD_LIMIT; + } + } + + if (auto const err = credentials::checkFields(ctx.tx, ctx.j); !isTesSuccess(err)) + return err; return tesSUCCESS; } @@ -111,9 +146,6 @@ EscrowFinish::preflightSigValidated(PreflightContext const& ctx) } } - if (auto const err = credentials::checkFields(ctx.tx, ctx.j); !isTesSuccess(err)) - return err; - return tesSUCCESS; } @@ -126,7 +158,15 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx) { extraFee += view.fees().base * (32 + (fb->size() / 16)); } - + if (std::optional const allowance = tx[~sfComputationAllowance]; allowance) + { + // The extra fee is the allowance in drops, rounded up to the nearest + // whole drop. + // Integer math rounds down by default, so we add 1 to round up. + uint64_t const allowanceFee = + ((*allowance) * view.fees().gasPrice) / MICRO_DROPS_PER_DROP + 1; + extraFee += allowanceFee; + } return Transactor::calculateBaseFee(view, tx) + extraFee; } @@ -202,25 +242,50 @@ EscrowFinish::preclaim(PreclaimContext const& ctx) return err; } - if (ctx.view.rules().enabled(featureTokenEscrow)) + if (ctx.view.rules().enabled(featureTokenEscrow) || + ctx.view.rules().enabled(featureSmartEscrow)) { + // this check is done in doApply before this amendment is enabled auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]); auto const slep = ctx.view.read(k); if (!slep) return tecNO_TARGET; - AccountID const dest = (*slep)[sfDestination]; - STAmount const amount = (*slep)[sfAmount]; - - if (!isXRP(amount)) + if (ctx.view.rules().enabled(featureSmartEscrow)) { - if (auto const ret = std::visit( - [&](T const&) { - return escrowFinishPreclaimHelper(ctx, dest, amount); - }, - amount.asset().value()); - !isTesSuccess(ret)) - return ret; + if (slep->isFieldPresent(sfFinishFunction)) + { + if (!ctx.tx.isFieldPresent(sfComputationAllowance)) + { + JLOG(ctx.j.debug()) << "FinishFunction requires ComputationAllowance"; + return tefWASM_FIELD_NOT_INCLUDED; + } + } + else + { + if (ctx.tx.isFieldPresent(sfComputationAllowance)) + { + JLOG(ctx.j.debug()) << "FinishFunction not present, " + "ComputationAllowance present"; + return tefNO_WASM; + } + } + } + if (ctx.view.rules().enabled(featureTokenEscrow)) + { + AccountID const dest = (*slep)[sfDestination]; + STAmount const amount = (*slep)[sfAmount]; + + if (!isXRP(amount)) + { + if (auto const ret = std::visit( + [&](T const&) { + return escrowFinishPreclaimHelper(ctx, dest, amount); + }, + amount.asset().value()); + !isTesSuccess(ret)) + return ret; + } } } return tesSUCCESS; @@ -233,7 +298,8 @@ EscrowFinish::doApply() auto const slep = ctx_.view().peek(k); if (!slep) { - if (ctx_.view().rules().enabled(featureTokenEscrow)) + if (ctx_.view().rules().enabled(featureTokenEscrow) || + ctx_.view().rules().enabled(featureSmartEscrow)) return tecINTERNAL; // LCOV_EXCL_LINE return tecNO_TARGET; @@ -251,6 +317,20 @@ EscrowFinish::doApply() if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter])) return tecNO_PERMISSION; + AccountID const destID = (*slep)[sfDestination]; + auto const sled = ctx_.view().peek(keylet::account(destID)); + if (ctx_.view().rules().enabled(featureSmartEscrow)) + { + // NOTE: Escrow payments cannot be used to fund accounts. + if (!sled) + return tecNO_DST; + + if (auto err = + verifyDepositPreauth(ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal); + !isTesSuccess(err)) + return err; + } + // Check cryptocondition fulfillment { auto const id = ctx_.tx.getTransactionID(); @@ -304,16 +384,71 @@ EscrowFinish::doApply() return tecCRYPTOCONDITION_ERROR; } - // NOTE: Escrow payments cannot be used to fund accounts. - AccountID const destID = (*slep)[sfDestination]; - auto const sled = ctx_.view().peek(keylet::account(destID)); - if (!sled) - return tecNO_DST; + if (!ctx_.view().rules().enabled(featureSmartEscrow)) + { + // NOTE: Escrow payments cannot be used to fund accounts. + if (!sled) + return tecNO_DST; - if (auto err = - verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, destID, sled, ctx_.journal); - !isTesSuccess(err)) - return err; + if (auto err = + verifyDepositPreauth(ctx_.tx, ctx_.view(), accountID_, destID, sled, ctx_.journal); + !isTesSuccess(err)) + return err; + } + + // Execute custom release function + if ((*slep)[~sfFinishFunction]) + { + JLOG(j_.trace()) << "The escrow has a finish function, running WASM code..."; + // WASM execution + auto const wasmStr = slep->getFieldVL(sfFinishFunction); + std::vector const wasm(wasmStr.begin(), wasmStr.end()); + + WasmHostFunctionsImpl ledgerDataProvider(ctx_, k); + + if (!ctx_.tx.isFieldPresent(sfComputationAllowance)) + { + // already checked above, this check is just in case + return tecINTERNAL; + } + std::uint32_t const allowance = ctx_.tx[sfComputationAllowance]; + auto re = runEscrowWasm(wasm, ledgerDataProvider, allowance, ESCROW_FUNCTION_NAME); + JLOG(j_.trace()) << "Escrow WASM ran"; + + if (auto const& data = ledgerDataProvider.getData(); data.has_value()) + { + if (data->size() > maxWasmDataLength) + { + // should already be checked in the updateData host function + return tecINTERNAL; // LCOV_EXCL_LINE + } + slep->setFieldVL(sfData, makeSlice(*data)); + ctx_.view().update(slep); + } + + if (re.has_value()) + { + auto const reValue = re.value().result; + auto const reCost = re.value().cost; + JLOG(j_.debug()) << "WASM Success: " + std::to_string(reValue) << ", cost: " << reCost; + + ctx_.setWasmReturnCode(reValue); + + if (reCost < 0 || reCost > std::numeric_limits::max()) + return tecINTERNAL; // LCOV_EXCL_LINE + ctx_.setGasUsed(static_cast(reCost)); + + if (reValue <= 0) + { + return tecWASM_REJECTED; + } + } + else + { + JLOG(j_.debug()) << "WASM Failure: " + transHuman(re.error()); + return re.error(); + } + } AccountID const account = (*slep)[sfAccount]; @@ -390,9 +525,11 @@ EscrowFinish::doApply() ctx_.view().update(sled); + auto const reserveToSubtract = calculateAdditionalReserve((*slep)[~sfFinishFunction]); + // Adjust source owner count auto const sle = ctx_.view().peek(keylet::account(account)); - adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal); + adjustOwnerCount(ctx_.view(), sle, -1 * reserveToSubtract, ctx_.journal); ctx_.view().update(sle); // Remove escrow from ledger diff --git a/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp index b80d282d70b..86b4f9970db 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenAcceptOffer.cpp @@ -434,7 +434,7 @@ NFTokenAcceptOffer::acceptOffer(std::shared_ptr const& offer) } // Now transfer the NFT: - return transferNFToken(buyer, seller, nftokenID); + return nft::transferNFToken(ctx_.view(), buyer, seller, nftokenID); } TER @@ -556,7 +556,7 @@ NFTokenAcceptOffer::doApply() } // Now transfer the NFT: - return transferNFToken(buyer, seller, nftokenID); + return nft::transferNFToken(ctx_.view(), buyer, seller, nftokenID); } if (bo) diff --git a/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp b/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp index 9c7fe7d5ef6..b5732001a28 100644 --- a/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp +++ b/src/libxrpl/tx/transactors/nft/NFTokenCreateOffer.cpp @@ -50,6 +50,10 @@ NFTokenCreateOffer::preflight(PreflightContext const& ctx) TER NFTokenCreateOffer::preclaim(PreclaimContext const& ctx) { + auto const sle = ctx.view.read(keylet::account(ctx.tx[sfAccount])); + auto const balance = sle ? (*sle)[sfBalance] : XRPAmount{0}; + JLOG(ctx.j.error()) << "NFTokenCreateOffer::preclaim.Balance: " << balance; + if (hasExpired(ctx.view, ctx.tx[~sfExpiration])) return tecEXPIRED; diff --git a/src/libxrpl/tx/transactors/system/Change.cpp b/src/libxrpl/tx/transactors/system/Change.cpp index 92a06fd8079..02904682a4b 100644 --- a/src/libxrpl/tx/transactors/system/Change.cpp +++ b/src/libxrpl/tx/transactors/system/Change.cpp @@ -123,6 +123,20 @@ Change::preclaim(PreclaimContext const& ctx) ctx.tx.isFieldPresent(sfReserveIncrementDrops)) return temDISABLED; } + if (ctx.view.rules().enabled(featureSmartEscrow)) + { + if (!ctx.tx.isFieldPresent(sfExtensionComputeLimit) || + !ctx.tx.isFieldPresent(sfExtensionSizeLimit) || + !ctx.tx.isFieldPresent(sfGasPrice)) + return temMALFORMED; + } + else + { + if (ctx.tx.isFieldPresent(sfExtensionComputeLimit) || + ctx.tx.isFieldPresent(sfExtensionSizeLimit) || + ctx.tx.isFieldPresent(sfGasPrice)) + return temDISABLED; + } return tesSUCCESS; case ttAMENDMENT: case ttUNL_MODIFY: @@ -284,6 +298,12 @@ Change::applyFee() set(feeObject, ctx_.tx, sfReserveBase); set(feeObject, ctx_.tx, sfReserveIncrement); } + if (view().rules().enabled(featureSmartEscrow)) + { + set(feeObject, ctx_.tx, sfExtensionComputeLimit); + set(feeObject, ctx_.tx, sfExtensionSizeLimit); + set(feeObject, ctx_.tx, sfGasPrice); + } view().update(feeObject); diff --git a/src/libxrpl/tx/wasm/ContractContext.cpp b/src/libxrpl/tx/wasm/ContractContext.cpp new file mode 100644 index 00000000000..46dc773298a --- /dev/null +++ b/src/libxrpl/tx/wasm/ContractContext.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include + +namespace xrpl { + +std::vector +getParameterValueVec(STArray const& functionParameters) +{ + std::vector param_map; + for (auto const& param : functionParameters) + { + auto const& value = param.getFieldData(sfParameterValue); + param_map.emplace_back(value); + } + return param_map; +} + +std::vector +getParameterTypeVec(STArray const& functionParameters) +{ + std::vector param_map; + for (auto const& param : functionParameters) + { + auto const& type = param.getFieldDataType(sfParameterType); + param_map.emplace_back(type); + } + return param_map; +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/wasm/HostFuncImpl.cpp b/src/libxrpl/tx/wasm/HostFuncImpl.cpp new file mode 100644 index 00000000000..7f6dd4255c4 --- /dev/null +++ b/src/libxrpl/tx/wasm/HostFuncImpl.cpp @@ -0,0 +1,46 @@ +#include +#include +#include + +namespace xrpl { + +// ========================================================= +// SECTION: WRITE FUNCTION +// ========================================================= + +Expected +WasmHostFunctionsImpl::updateData(Slice const& data) +{ + if (data.size() > maxWasmDataLength) + { + return Unexpected(HostFunctionError::DATA_FIELD_TOO_LARGE); + } + data_ = Bytes(data.begin(), data.end()); + return data_->size(); +} + +// ========================================================= +// SECTION: UTILS +// ========================================================= + +Expected +WasmHostFunctionsImpl::checkSignature( + Slice const& message, + Slice const& signature, + Slice const& pubkey) const +{ + if (!publicKeyType(pubkey)) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + PublicKey const pk(pubkey); + return verify(pk, message, signature); +} + +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 00000000000..1faaeab8058 --- /dev/null +++ b/src/libxrpl/tx/wasm/HostFuncImplFloat.cpp @@ -0,0 +1,567 @@ +#include +#include +#include + +#ifdef _DEBUG +// #define DEBUG_OUTPUT 1 +#endif + +namespace xrpl { + +namespace wasm_float { + +namespace detail { + +class Number2 : public Number +{ +protected: + static Bytes const floatNull; + static unsigned constexpr encodedFloatSize = 8; + static int32_t constexpr encodedMantissaBits = 54; + static int32_t constexpr encodedExponentBits = 8; + + static_assert(wasmMinExponent < 0); + + static uint64_t constexpr maxEncodedMantissa = (1ull << (encodedMantissaBits + 1)) - 1; + + bool good_; + +public: + Number2(Slice const& data) : good_(false) + { + if (data.size() != encodedFloatSize) + return; + + if (std::ranges::equal(floatNull, data)) + { + good_ = true; + return; + } + + uint64_t const v = SerialIter(data).get64(); + if ((v & STAmount::cIssuedCurrency) == 0u) + return; + + int32_t const e = static_cast((v >> encodedMantissaBits) & 0xFFull); + int32_t const decodedExponent = e + wasmMinExponent - 1; // e - 97 + if (decodedExponent < wasmMinExponent || decodedExponent > wasmMaxExponent) + return; + + int64_t const neg = ((v & STAmount::cPositive) != 0u) ? 1 : -1; + int64_t const m = neg * static_cast(v & ((1ull << encodedMantissaBits) - 1)); + if (m == 0) + return; + + Number const x(makeNumber(m, decodedExponent)); + if (m != x.mantissa() || decodedExponent != x.exponent()) + return; // not canonical + *static_cast(this) = x; + + good_ = true; + } + + template + Number2(T mantissa = 0, int32_t exponent = 0) : Number(), good_(false) + { + if (!mantissa) + { + good_ = true; + return; + } + + auto const n = makeNumber(mantissa, exponent); + auto const e = n.exponent(); + if (e < wasmMinExponent) + { + good_ = true; // value is zero(as in Numbers behavior) + return; + } + + if (e > wasmMaxExponent) + return; + + *static_cast(this) = n; + good_ = true; + } + + Number2(Number const& n) : Number2(n.mantissa(), n.exponent()) // ensure Number canonized + { + } + + static Number + makeNumber(int64_t mantissa, int32_t exponent) + { + if (mantissa < 0) + return Number(true, -static_cast(mantissa), exponent, Number::normalized()); + return Number(false, mantissa, exponent, Number::normalized()); + } + + static Number + makeNumber(uint64_t mantissa, int32_t exponent) + { + return Number(false, mantissa, exponent, Number::normalized()); + } + + operator bool() const + { + return good_; + } + + Expected + toBytes() const + { + if (!good_) + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + + auto const m = mantissa(); + auto const e = exponent(); + + uint64_t v = m >= 0 ? STAmount::cPositive : 0; + v |= STAmount::cIssuedCurrency; + + uint64_t const absM = std::abs(m); + if (absM == 0u) + { + return floatNull; + } + if (absM > maxEncodedMantissa) + { + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); // LCOV_EXCL_LINE + } + v |= absM; + + if (e > wasmMaxExponent) + { + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + if (e < wasmMinExponent) + { + return floatNull; + } + uint64_t const normExp = e - wasmMinExponent + 1; //+97 + v |= normExp << encodedMantissaBits; + + Serializer msg; + msg.add64(v); + auto data = msg.getData(); + +#ifdef DEBUG_OUTPUT + std::cout << "m: " << std::setw(20) << mantissa() << ", e: " << std::setw(12) << 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 data; + } +}; + +Bytes const Number2::floatNull = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +struct FloatState +{ + Number::rounding_mode oldMode_; + MantissaRange::mantissa_scale oldScale_; + bool good_{false}; + + FloatState(int32_t mode) : oldMode_(Number::getround()), oldScale_(Number::getMantissaScale()) + { + if (mode < Number::rounding_mode::to_nearest || mode > Number::rounding_mode::upward) + return; + + Number::setround(static_cast(mode)); + Number::setMantissaScale(MantissaRange::mantissa_scale::small); + good_ = true; + } + + ~FloatState() + { + Number::setround(oldMode_); + Number::setMantissaScale(oldScale_); + } + + operator bool() const + { + return good_; + } +}; + +} // 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(Number::rounding_mode::to_nearest); + detail::Number2 const num(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; + } + + auto const s = to_string(num); + return s; +} + +Expected +floatFromIntImpl(int64_t x, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + detail::Number2 const num(x); + return num.toBytes(); + } + // LCOV_EXCL_START + catch (...) + { + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +Expected +floatFromUintImpl(uint64_t x, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + detail::Number2 const num(x); + auto r = num.toBytes(); + return r; + } + // LCOV_EXCL_START + catch (...) + { + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +Expected +floatSetImpl(int64_t mantissa, int32_t exponent, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + detail::Number2 const num(mantissa, exponent); + if (!num) + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + return num.toBytes(); + } + catch (...) + { + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); +} + +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(Number::rounding_mode::to_nearest); + detail::Number2 const xx(x); + if (!xx) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + detail::Number2 const yy(y); + if (!yy) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + if (xx < yy) + return 2; + if (xx == yy) + return 0; + return 1; + } + // LCOV_EXCL_START + catch (...) + { + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +Expected +floatAddImpl(Slice const& x, Slice const& y, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + detail::Number2 const xx(x); + if (!xx) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + detail::Number2 const yy(y); + if (!yy) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + detail::Number2 const res = xx + yy; + + return res.toBytes(); + } + // LCOV_EXCL_START + catch (...) + { + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +Expected +floatSubtractImpl(Slice const& x, Slice const& y, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + detail::Number2 const xx(x); + if (!xx) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + detail::Number2 const yy(y); + if (!yy) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + detail::Number2 const res = xx - yy; + + return res.toBytes(); + } + // LCOV_EXCL_START + catch (...) + { + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +Expected +floatMultiplyImpl(Slice const& x, Slice const& y, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + detail::Number2 const xx(x); + if (!xx) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + detail::Number2 const yy(y); + if (!yy) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + detail::Number2 const res = xx * yy; + + return res.toBytes(); + } + // LCOV_EXCL_START + catch (...) + { + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +Expected +floatDivideImpl(Slice const& x, Slice const& y, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + detail::Number2 const xx(x); + if (!xx) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + detail::Number2 const yy(y); + if (!yy) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + detail::Number2 const res = xx / yy; + + return res.toBytes(); + } + catch (...) + { + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); +} + +Expected +floatRootImpl(Slice const& x, int32_t n, int32_t mode) +{ + try + { + if (n < 1) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + detail::FloatState const rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + detail::Number2 const xx(x); + if (!xx) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + detail::Number2 const res(root(xx, n)); + + return res.toBytes(); + } + // LCOV_EXCL_START + catch (...) + { + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +Expected +floatPowerImpl(Slice const& x, int32_t n, int32_t mode) +{ + try + { + if ((n < 0) || (n > wasmMaxExponent)) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + detail::FloatState const rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + detail::Number2 const xx(x); + if (!xx) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + if (xx == Number() && (n == 0)) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + detail::Number2 const res(power(xx, n, 1)); + + return res.toBytes(); + } + // LCOV_EXCL_START + catch (...) + { + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +Expected +floatLogImpl(Slice const& x, int32_t mode) +{ + try + { + detail::FloatState const rm(mode); + if (!rm) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + detail::Number2 const xx(x); + if (!xx) + return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); + + detail::Number2 const res(log10(xx)); + + return res.toBytes(); + } + // LCOV_EXCL_START + catch (...) + { + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + return Unexpected(HostFunctionError::FLOAT_COMPUTATION_ERROR); + // LCOV_EXCL_STOP +} + +} // namespace wasm_float + +// ========================================================= +// ACTUAL HOST FUNCTIONS +// ========================================================= + +Expected +WasmHostFunctionsImpl::floatFromInt(int64_t x, int32_t mode) const +{ + return wasm_float::floatFromIntImpl(x, mode); +} + +Expected +WasmHostFunctionsImpl::floatFromUint(uint64_t x, int32_t mode) const +{ + return wasm_float::floatFromUintImpl(x, mode); +} + +Expected +WasmHostFunctionsImpl::floatSet(int64_t mantissa, int32_t exponent, int32_t mode) const +{ + return wasm_float::floatSetImpl(mantissa, exponent, mode); +} + +Expected +WasmHostFunctionsImpl::floatCompare(Slice const& x, Slice const& y) const +{ + return wasm_float::floatCompareImpl(x, y); +} + +Expected +WasmHostFunctionsImpl::floatAdd(Slice const& x, Slice const& y, int32_t mode) const +{ + return wasm_float::floatAddImpl(x, y, mode); +} + +Expected +WasmHostFunctionsImpl::floatSubtract(Slice const& x, Slice const& y, int32_t mode) const +{ + return wasm_float::floatSubtractImpl(x, y, mode); +} + +Expected +WasmHostFunctionsImpl::floatMultiply(Slice const& x, Slice const& y, int32_t mode) const +{ + return wasm_float::floatMultiplyImpl(x, y, mode); +} + +Expected +WasmHostFunctionsImpl::floatDivide(Slice const& x, Slice const& y, int32_t mode) const +{ + return wasm_float::floatDivideImpl(x, y, mode); +} + +Expected +WasmHostFunctionsImpl::floatRoot(Slice const& x, int32_t n, int32_t mode) const +{ + return wasm_float::floatRootImpl(x, n, mode); +} + +Expected +WasmHostFunctionsImpl::floatPower(Slice const& x, int32_t n, int32_t mode) const +{ + return wasm_float::floatPowerImpl(x, n, mode); +} + +Expected +WasmHostFunctionsImpl::floatLog(Slice const& x, int32_t mode) const +{ + return wasm_float::floatLogImpl(x, mode); +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/wasm/HostFuncImplGetter.cpp b/src/libxrpl/tx/wasm/HostFuncImplGetter.cpp new file mode 100644 index 00000000000..46744c17a76 --- /dev/null +++ b/src/libxrpl/tx/wasm/HostFuncImplGetter.cpp @@ -0,0 +1,406 @@ +#include +#include +#include + +namespace xrpl { + +typedef std::variant FieldValue; + +namespace detail { + +template +Bytes +getIntBytes(STBase const* obj) +{ + static_assert(std::is_integral::value, "Only integral types"); + + auto const& num(static_cast const*>(obj)); // NOLINT + T const data = adjustWasmEndianess(num->value()); + auto const* b = reinterpret_cast(&data); + auto const* e = reinterpret_cast(&data + 1); + return Bytes{b, e}; +} + +static Expected +getAnyFieldData(STBase const* obj) +{ + if (obj == nullptr) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + + auto const stype = obj->getSType(); + switch (stype) + { + // LCOV_EXCL_START + case STI_UNKNOWN: + case STI_NOTPRESENT: + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + // LCOV_EXCL_STOP + + case STI_OBJECT: + case STI_ARRAY: + case STI_VECTOR256: + return Unexpected(HostFunctionError::NOT_LEAF_FIELD); + + 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: + default: + break; // Use serializer + } + + Serializer msg; + obj->add(msg); + return msg.getData(); +} + +static 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()); + } + + return Unexpected(HostFunctionError::INTERNAL); // LCOV_EXCL_LINE +} + +static inline bool +noField(STBase const* field) +{ + return (field == nullptr) || (STI_NOTPRESENT == field->getSType()) || + (STI_UNKNOWN == field->getSType()); +} + +static Expected +locateField(STObject const& obj, Slice const& locator) +{ + if (locator.empty() || ((locator.size() & 3) != 0u)) // must be multiple of 4 + return Unexpected(HostFunctionError::LOCATOR_MALFORMED); + + static_assert(maxWasmParamLength % sizeof(int32_t) == 0); + int32_t locBuf[maxWasmParamLength / sizeof(int32_t)]; + int32_t const* locPtr = &locBuf[0]; + int32_t const locSize = locator.size() / sizeof(int32_t); + + { + uintptr_t const p = reinterpret_cast(locator.data()); + if ((p & (alignof(int32_t) - 1)) != 0u) + { // unaligned + memcpy(&locBuf[0], locator.data(), locator.size()); + } + else + { + locPtr = reinterpret_cast(locator.data()); + } + } + + STBase const* field = nullptr; + auto const& knownSFields = SField::getKnownCodeToField(); + + { + int32_t const sfieldCode = adjustWasmEndianess(locPtr[0]); + auto const it = knownSFields.find(sfieldCode); + if (it == knownSFields.end()) + return Unexpected(HostFunctionError::INVALID_FIELD); + + auto const& fname(*it->second); + field = obj.peekAtPField(fname); + if (noField(field)) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + } + + for (int i = 1; i < locSize; ++i) + { + int32_t const sfieldCode = adjustWasmEndianess(locPtr[i]); + + if (STI_ARRAY == field->getSType()) + { + auto const* arr = static_cast(field); // NOLINT + if (sfieldCode < 0 || std::cmp_greater_equal(sfieldCode, arr->size())) + return Unexpected(HostFunctionError::INDEX_OUT_OF_BOUNDS); + 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 Unexpected(HostFunctionError::INVALID_FIELD); + + 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 Unexpected(HostFunctionError::INDEX_OUT_OF_BOUNDS); + return FieldValue(&(v->operator[](sfieldCode))); + } + else // simple field must be the last one + { + return Unexpected(HostFunctionError::LOCATOR_MALFORMED); + } + + if (noField(field)) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + } + + return FieldValue(field); +} + +static inline 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 Unexpected(HostFunctionError::NO_ARRAY); // LCOV_EXCL_LINE +} + +} // namespace detail + +Expected +WasmHostFunctionsImpl::cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) +{ + auto const& keylet = keylet::unchecked(objId); + if (cacheIdx < 0 || cacheIdx > MAX_CACHE) + return Unexpected(HostFunctionError::SLOT_OUT_RANGE); + + if (cacheIdx == 0) + { + for (cacheIdx = 0; cacheIdx < MAX_CACHE; ++cacheIdx) + { + if (!cache_[cacheIdx]) + break; + } + } + else + { + cacheIdx--; // convert to 0-based index + } + + if (cacheIdx >= MAX_CACHE) + return Unexpected(HostFunctionError::SLOTS_FULL); + + cache_[cacheIdx] = ctx_.view().read(keylet); + if (!cache_[cacheIdx]) + return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND); + return cacheIdx + 1; // return 1-based index +} + +// Subsection: top level getters + +Expected +WasmHostFunctionsImpl::getTxField(SField const& fname) const +{ + return detail::getAnyFieldData(ctx_.tx.peekAtPField(fname)); +} + +Expected +WasmHostFunctionsImpl::getCurrentLedgerObjField(SField const& fname) const +{ + auto const sle = getCurrentLedgerObj(); + if (!sle.has_value()) + return Unexpected(sle.error()); + return detail::getAnyFieldData(sle.value()->peekAtPField(fname)); +} + +Expected +WasmHostFunctionsImpl::getLedgerObjField(int32_t cacheIdx, SField const& fname) const +{ + auto const normalizedIdx = normalizeCacheIndex(cacheIdx); + if (!normalizedIdx.has_value()) + return Unexpected(normalizedIdx.error()); + return detail::getAnyFieldData(cache_[normalizedIdx.value()]->peekAtPField(fname)); +} + +// Subsection: nested getters + +Expected +WasmHostFunctionsImpl::getTxNestedField(Slice const& locator) const +{ + auto const r = detail::locateField(ctx_.tx, locator); + if (!r) + return Unexpected(r.error()); + + return detail::getAnyFieldData(r.value()); +} + +Expected +WasmHostFunctionsImpl::getCurrentLedgerObjNestedField(Slice const& locator) const +{ + auto const sle = getCurrentLedgerObj(); + if (!sle.has_value()) + return Unexpected(sle.error()); + + auto const r = detail::locateField(*sle.value(), locator); + if (!r) + return Unexpected(r.error()); + + return detail::getAnyFieldData(r.value()); +} + +Expected +WasmHostFunctionsImpl::getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator) const +{ + auto const normalizedIdx = normalizeCacheIndex(cacheIdx); + if (!normalizedIdx.has_value()) + return Unexpected(normalizedIdx.error()); + + auto const r = detail::locateField(*cache_[normalizedIdx.value()], locator); + if (!r) + return Unexpected(r.error()); + + return detail::getAnyFieldData(r.value()); +} + +// Subsection: array length getters + +Expected +WasmHostFunctionsImpl::getTxArrayLen(SField const& fname) const +{ + if (fname.fieldType != STI_ARRAY && fname.fieldType != STI_VECTOR256) + return Unexpected(HostFunctionError::NO_ARRAY); + + auto const* field = ctx_.tx.peekAtPField(fname); + if (detail::noField(field)) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + + return detail::getArrayLen(field); +} + +Expected +WasmHostFunctionsImpl::getCurrentLedgerObjArrayLen(SField const& fname) const +{ + if (fname.fieldType != STI_ARRAY && fname.fieldType != STI_VECTOR256) + return Unexpected(HostFunctionError::NO_ARRAY); + + auto const sle = getCurrentLedgerObj(); + if (!sle.has_value()) + return Unexpected(sle.error()); + + auto const* field = sle.value()->peekAtPField(fname); + if (detail::noField(field)) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + + return detail::getArrayLen(field); +} + +Expected +WasmHostFunctionsImpl::getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const +{ + if (fname.fieldType != STI_ARRAY && fname.fieldType != STI_VECTOR256) + return Unexpected(HostFunctionError::NO_ARRAY); + + auto const normalizedIdx = normalizeCacheIndex(cacheIdx); + if (!normalizedIdx.has_value()) + return Unexpected(normalizedIdx.error()); + + auto const* field = cache_[normalizedIdx.value()]->peekAtPField(fname); + if (detail::noField(field)) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + + return detail::getArrayLen(field); +} + +// Subsection: nested array length getters + +Expected +WasmHostFunctionsImpl::getTxNestedArrayLen(Slice const& locator) const +{ + auto const r = detail::locateField(ctx_.tx, locator); + if (!r) + return Unexpected(r.error()); + + auto const& field = r.value(); + return detail::getArrayLen(field); +} + +Expected +WasmHostFunctionsImpl::getCurrentLedgerObjNestedArrayLen(Slice const& locator) const +{ + auto const sle = getCurrentLedgerObj(); + if (!sle.has_value()) + return Unexpected(sle.error()); + auto const r = detail::locateField(*sle.value(), locator); + if (!r) + return Unexpected(r.error()); + + auto const& field = r.value(); + return detail::getArrayLen(field); +} + +Expected +WasmHostFunctionsImpl::getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator) const +{ + auto const normalizedIdx = normalizeCacheIndex(cacheIdx); + if (!normalizedIdx.has_value()) + return Unexpected(normalizedIdx.error()); + + auto const r = detail::locateField(*cache_[normalizedIdx.value()], locator); + if (!r) + return Unexpected(r.error()); + + auto const& field = r.value(); + return detail::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 00000000000..8953790464f --- /dev/null +++ b/src/libxrpl/tx/wasm/HostFuncImplKeylet.cpp @@ -0,0 +1,213 @@ +#include +#include +#include + +namespace xrpl { + +Expected +WasmHostFunctionsImpl::accountKeylet(AccountID const& account) const +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::account(account); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::ammKeylet(Asset const& issue1, Asset const& issue2) const +{ + if (issue1 == issue2) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + // note: this should be removed with the MPT DEX amendment + if (issue1.holds() || issue2.holds()) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + auto const keylet = keylet::amm(issue1, issue2); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::checkKeylet(AccountID const& account, std::uint32_t seq) const +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::check(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::credentialKeylet( + AccountID const& subject, + AccountID const& issuer, + Slice const& credentialType) const +{ + if (!subject || !issuer) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + + if (credentialType.empty() || credentialType.size() > maxCredentialTypeLength) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + auto const keylet = keylet::credential(subject, issuer, credentialType); + + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::didKeylet(AccountID const& account) const +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::did(account); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::delegateKeylet(AccountID const& account, AccountID const& authorize) const +{ + if (!account || !authorize) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + if (account == authorize) + return Unexpected(HostFunctionError::INVALID_PARAMS); + auto const keylet = keylet::delegate(account, authorize); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::depositPreauthKeylet(AccountID const& account, AccountID const& authorize) + const +{ + if (!account || !authorize) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + if (account == authorize) + return Unexpected(HostFunctionError::INVALID_PARAMS); + auto const keylet = keylet::depositPreauth(account, authorize); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::escrowKeylet(AccountID const& account, std::uint32_t seq) const +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::escrow(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::lineKeylet( + AccountID const& account1, + AccountID const& account2, + Currency const& currency) const +{ + if (!account1 || !account2) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + if (account1 == account2) + return Unexpected(HostFunctionError::INVALID_PARAMS); + if (currency.isZero()) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + auto const keylet = keylet::line(account1, account2, currency); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::mptIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) const +{ + if (!issuer) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + + auto const keylet = keylet::mptIssuance(seq, issuer); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::mptokenKeylet(MPTID const& mptid, AccountID const& holder) const +{ + if (!mptid) + return Unexpected(HostFunctionError::INVALID_PARAMS); + if (!holder) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + + auto const keylet = keylet::mptoken(mptid, holder); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::nftOfferKeylet(AccountID const& account, std::uint32_t seq) const +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::nftoffer(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::offerKeylet(AccountID const& account, std::uint32_t seq) const +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::offer(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::oracleKeylet(AccountID const& account, std::uint32_t documentId) const +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::oracle(account, documentId); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::paychanKeylet( + AccountID const& account, + AccountID const& destination, + std::uint32_t seq) const +{ + if (!account || !destination) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + if (account == destination) + return Unexpected(HostFunctionError::INVALID_PARAMS); + auto const keylet = keylet::payChan(account, destination, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) const +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::permissionedDomain(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::signersKeylet(AccountID const& account) const +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::signers(account); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::ticketKeylet(AccountID const& account, std::uint32_t seq) const +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::ticket(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; +} + +Expected +WasmHostFunctionsImpl::vaultKeylet(AccountID const& account, std::uint32_t seq) const +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + 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 00000000000..c8e38f68057 --- /dev/null +++ b/src/libxrpl/tx/wasm/HostFuncImplLedgerHeader.cpp @@ -0,0 +1,49 @@ +#include +#include +#include + +namespace xrpl { + +// ========================================================= +// SECTION: LEDGER HEADER FUNCTIONS +// ========================================================= + +Expected +WasmHostFunctionsImpl::getLedgerSqn() const +{ + return ctx_.view().seq(); +} + +Expected +WasmHostFunctionsImpl::getParentLedgerTime() const +{ + return ctx_.view().parentCloseTime().time_since_epoch().count(); +} + +Expected +WasmHostFunctionsImpl::getParentLedgerHash() const +{ + return ctx_.view().header().parentHash; +} + +Expected +WasmHostFunctionsImpl::getBaseFee() const +{ + return ctx_.view().fees().base.drops(); +} + +Expected +WasmHostFunctionsImpl::isAmendmentEnabled(uint256 const& amendmentId) const +{ + return ctx_.view().rules().enabled(amendmentId); +} + +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 00000000000..5a0dfa01936 --- /dev/null +++ b/src/libxrpl/tx/wasm/HostFuncImplNFT.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include + +namespace xrpl { + +// ========================================================= +// SECTION: NFT UTILS +// ========================================================= + +Expected +WasmHostFunctionsImpl::getNFT(AccountID const& account, uint256 const& nftId) const +{ + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + + if (!nftId) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + auto obj = nft::findToken(ctx_.view(), account, nftId); + if (!obj) + return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND); + + auto objUri = obj->at(~sfURI); + if (!objUri) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + + Slice const s = objUri->value(); + return Bytes(s.begin(), s.end()); +} + +Expected +WasmHostFunctionsImpl::getNFTIssuer(uint256 const& nftId) const +{ + auto const issuer = nft::getIssuer(nftId); + if (!issuer) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + return Bytes{issuer.begin(), issuer.end()}; +} + +Expected +WasmHostFunctionsImpl::getNFTTaxon(uint256 const& nftId) const +{ + return nft::toUInt32(nft::getTaxon(nftId)); +} + +Expected +WasmHostFunctionsImpl::getNFTFlags(uint256 const& nftId) const +{ + return nft::getFlags(nftId); +} + +Expected +WasmHostFunctionsImpl::getNFTTransferFee(uint256 const& nftId) const +{ + return nft::getTransferFee(nftId); +} + +Expected +WasmHostFunctionsImpl::getNFTSerial(uint256 const& nftId) const +{ + return nft::getSerial(nftId); +} + +} // namespace xrpl diff --git a/src/libxrpl/tx/wasm/HostFuncImplTrace.cpp b/src/libxrpl/tx/wasm/HostFuncImplTrace.cpp new file mode 100644 index 00000000000..33f9dc8e2b5 --- /dev/null +++ b/src/libxrpl/tx/wasm/HostFuncImplTrace.cpp @@ -0,0 +1,61 @@ +#include +#include +#include + +#ifdef _DEBUG +// #define DEBUG_OUTPUT 1 +#endif + +namespace xrpl { + +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; +} + +Expected +WasmHostFunctionsImpl::traceNum(std::string_view const& msg, int64_t data) const +{ + log(msg, [data] { return data; }); + return 0; +} + +Expected +WasmHostFunctionsImpl::traceAccount(std::string_view const& msg, AccountID const& account) const +{ + log(msg, [&account] { return toBase58(account); }); + return 0; +} + +Expected +WasmHostFunctionsImpl::traceFloat(std::string_view const& msg, Slice const& data) const +{ + log(msg, [&data] { return wasm_float::floatToString(data); }); + return 0; +} + +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 00000000000..e342d1a62a4 --- /dev/null +++ b/src/libxrpl/tx/wasm/HostFuncWrapper.cpp @@ -0,0 +1,2540 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +using SFieldCRef = std::reference_wrapper; + +static int32_t +setData( + InstanceWrapper const* 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::INVALID_PARAMS); + + if (srcSize > maxWasmDataLength) + return HfErrorToInt(HostFunctionError::DATA_FIELD_TOO_LARGE); + + auto const memory = (runtime != nullptr) ? runtime->getMem() : wmem(); + + // LCOV_EXCL_START + if (memory.s == 0u) + return HfErrorToInt(HostFunctionError::NO_MEM_EXPORTED); + // LCOV_EXCL_STOP + if ((int64_t)dst + dstSize > memory.s) + return HfErrorToInt(HostFunctionError::POINTER_OUT_OF_BOUNDS); + if (srcSize > dstSize) + return HfErrorToInt(HostFunctionError::BUFFER_TOO_SMALL); + + memcpy(memory.p + dst, src, srcSize); + + return srcSize; +} + +template +Expected +getDataInt32(IW const* _runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const result = params->data[i].of.i32; + i++; + return result; +} + +template +Expected +getDataInt64(IW const* _runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const result = params->data[i].of.i64; + i++; + return result; +} + +template +Expected +getDataUnsigned(IW const* 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 Unexpected(r.error()); + if (r->size() != sizeof(T)) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + T x; + uintptr_t 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; +} + +template +Expected +getDataUInt32(IW const* runtime, wasm_val_vec_t const* params, int32_t& i) +{ + return getDataUnsigned(runtime, params, i); +} + +template +Expected +getDataUInt64(IW const* runtime, wasm_val_vec_t const* params, int32_t& i) +{ + return getDataUnsigned(runtime, params, i); +} + +template +Expected +getDataSField(IW const* _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 Unexpected(HostFunctionError::INVALID_FIELD); + } + return *it->second; +} + +template +Expected +getDataSlice(IW const* runtime, wasm_val_vec_t const* params, int32_t& i, bool isUpdate = false) +{ + 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 Unexpected(HostFunctionError::INVALID_PARAMS); + + if (!size) + return Slice(); + + if (size > (isUpdate ? maxWasmDataLength : maxWasmParamLength)) + return Unexpected(HostFunctionError::DATA_FIELD_TOO_LARGE); + + auto const memory = runtime ? runtime->getMem() : wmem(); + // LCOV_EXCL_START + if (!memory.s) + return Unexpected(HostFunctionError::NO_MEM_EXPORTED); + // LCOV_EXCL_STOP + + if (ptr + size > memory.s) + return Unexpected(HostFunctionError::POINTER_OUT_OF_BOUNDS); + + Slice data(memory.p + ptr, size); + return data; +} + +template +Expected +getDataUInt256(IW const* runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const slice = getDataSlice(runtime, params, i); + if (!slice) + { + return Unexpected(slice.error()); + } + + if (slice->size() != uint256::bytes) + { + return Unexpected(HostFunctionError::INVALID_PARAMS); + } + return uint256::fromVoid(slice->data()); +} + +template +Expected +getDataAccountID(IW const* runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const slice = getDataSlice(runtime, params, i); + if (!slice) + { + return Unexpected(slice.error()); + } + + if (slice->size() != AccountID::bytes) + { + return Unexpected(HostFunctionError::INVALID_PARAMS); + } + + return AccountID::fromVoid(slice->data()); +} + +template +static Expected +getDataCurrency(IW const* runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const slice = getDataSlice(runtime, params, i); + if (!slice) + { + return Unexpected(slice.error()); + } + + if (slice->size() != Currency::bytes) + { + return Unexpected(HostFunctionError::INVALID_PARAMS); + } + + return Currency::fromVoid(slice->data()); +} + +template +static Expected +getDataAsset(IW const* runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const slice = getDataSlice(runtime, params, i); + if (!slice) + { + return Unexpected(slice.error()); + } + + if (slice->size() == MPTID::bytes) + { + auto const mptid = MPTID::fromVoid(slice->data()); + return Asset{mptid}; + } + + if (slice->size() == Currency::bytes) + { + auto const currency = Currency::fromVoid(slice->data()); + auto const issue = Issue{currency, xrpAccount()}; + if (!issue.native()) + return Unexpected(HostFunctionError::INVALID_PARAMS); + return Asset{issue}; + } + + if (slice->size() == (AccountID::bytes + Currency::bytes)) + { + auto const issue = Issue( + Currency::fromVoid(slice->data()), + AccountID::fromVoid(slice->data() + Currency::bytes)); + + if (issue.native()) + return Unexpected(HostFunctionError::INVALID_PARAMS); + return Asset{issue}; + } + + return Unexpected(HostFunctionError::INVALID_PARAMS); +} + +template +Expected +getDataString(IW const* runtime, wasm_val_vec_t const* params, int32_t& i) +{ + auto const slice = getDataSlice(runtime, params, i); + if (!slice) + return Unexpected(slice.error()); + return std::string_view(reinterpret_cast(slice->data()), slice->size()); +} + +std::nullptr_t +hfResult(wasm_val_vec_t* results, int32_t value) +{ + results->data[0] = WASM_I32_VAL(value); + // results->size = 1; + return nullptr; +} + +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 +std::nullptr_t +returnResult( + InstanceWrapper const* runtime, + wasm_val_vec_t const* params, + wasm_val_vec_t* results, + Expected const& res, + int32_t index) +{ + if (!res) + { + return hfResult(results, res.error()); + } + + using t = std::decay_t; + if constexpr (std::is_same_v) + { + return hfResult( + results, + setData( + runtime, + params->data[index].of.i32, + params->data[index + 1].of.i32, + res->data(), + res->size())); + } + else if constexpr (std::is_same_v) + { + return hfResult( + results, + setData( + runtime, + params->data[index].of.i32, + params->data[index + 1].of.i32, + res->data(), + res->size())); + } + else if constexpr (std::is_same_v) + { + return hfResult(results, res.value()); + } + else if constexpr (std::is_same_v) + { + auto const resultValue = adjustWasmEndianess(res.value()); + return hfResult( + results, + setData( + runtime, + params->data[index].of.i32, + params->data[index + 1].of.i32, + reinterpret_cast(&resultValue), + static_cast(sizeof(resultValue)))); + } + else + { + static_assert([] { return false; }(), "Unhandled return type in returnResult"); + } +} + +static inline HostFunctions* +getHF(void* env) +{ + auto const* udata = reinterpret_cast(env); + HostFunctions* hf = reinterpret_cast(udata->first); // NOLINT + return hf; +} + +static inline Expected +checkGas(void* env) +{ + auto const* udata = reinterpret_cast(env); + HostFunctions const* hf = reinterpret_cast(udata->first); + + auto const* runtime = reinterpret_cast(hf->getRT()); + if (runtime == nullptr) + { + wasm_trap_t* trap = reinterpret_cast( // NOLINT + WasmEngine::instance().newTrap("hf no runtime")); // LCOV_EXCL_LINE + return Unexpected(trap); // LCOV_EXCL_LINE + } + + int64_t const gas = runtime->getGas(); + WasmImportFunc const& impFunc = udata->second; + int64_t const x = gas >= impFunc.gas ? gas - impFunc.gas : 0; + + if (runtime->setGas(x) < 0) + { + wasm_trap_t* trap = reinterpret_cast( // NOLINT + WasmEngine::instance().newTrap("can't set gas")); // LCOV_EXCL_LINE + return Unexpected(trap); // LCOV_EXCL_LINE + } + + if (gas < impFunc.gas) + { + wasm_trap_t* const trap = // NOLINT + reinterpret_cast(WasmEngine::instance().newTrap("hf out of gas")); + return Unexpected(trap); + } + + return x; +} + +//---------------------------------------------------------------------------------------------------------------------- +wasm_trap_t* +getLedgerSqn_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int const index = 0; + + return returnResult(runtime, params, results, hf->getLedgerSqn(), index); +} + +wasm_trap_t* +getParentLedgerTime_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int const index = 0; + + return returnResult(runtime, params, results, hf->getParentLedgerTime(), index); +} + +wasm_trap_t* +getParentLedgerHash_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int const index = 0; + + return returnResult(runtime, params, results, hf->getParentLedgerHash(), index); +} + +wasm_trap_t* +getBaseFee_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int const index = 0; + + return returnResult(runtime, params, results, hf->getBaseFee(), index); +} + +wasm_trap_t* +isAmendmentEnabled_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const slice = getDataSlice(runtime, params, index); + if (!slice) + { + return hfResult(results, slice.error()); + } + + if (slice->size() == uint256::bytes) + { + if (auto ret = hf->isAmendmentEnabled(uint256::fromVoid(slice->data())); *ret == 1) + { + return returnResult(runtime, params, results, ret, index); + } + } + + if (slice->size() > 64) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const bytes = getDataSlice(runtime, params, index); + if (!bytes) + { + return hfResult(results, bytes.error()); + } + + return returnResult(runtime, params, results, hf->getTxNestedField(*bytes), index); +} + +wasm_trap_t* +getCurrentLedgerObjNestedField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const bytes = getDataSlice(runtime, params, index); + if (!bytes) + { + return hfResult(results, bytes.error()); + } + return returnResult( + runtime, params, results, hf->getCurrentLedgerObjNestedField(*bytes), index); +} + +wasm_trap_t* +getLedgerObjNestedField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const cache = getDataInt32(runtime, params, index); + if (!cache) + { + return hfResult(results, cache.error()); // LCOV_EXCL_LINE + } + + auto const bytes = getDataSlice(runtime, params, index); + if (!bytes) + { + return hfResult(results, bytes.error()); + } + + return returnResult( + runtime, params, results, hf->getLedgerObjNestedField(*cache, *bytes), index); +} + +wasm_trap_t* +getTxArrayLen_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const bytes = getDataSlice(runtime, params, index); + if (!bytes) + { + return hfResult(results, bytes.error()); + } + + return returnResult(runtime, params, results, hf->getTxNestedArrayLen(*bytes), index); +} + +wasm_trap_t* +getCurrentLedgerObjNestedArrayLen_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const bytes = getDataSlice(runtime, params, index); + if (!bytes) + { + return hfResult(results, bytes.error()); + } + + return returnResult( + runtime, params, results, hf->getCurrentLedgerObjNestedArrayLen(*bytes), index); +} +wasm_trap_t* +getLedgerObjNestedArrayLen_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const cache = getDataInt32(runtime, params, index); + if (!cache) + { + return hfResult(results, cache.error()); // LCOV_EXCL_LINE + } + + auto const bytes = getDataSlice(runtime, params, index); + if (!bytes) + { + return hfResult(results, bytes.error()); + } + return returnResult( + runtime, params, results, hf->getLedgerObjNestedArrayLen(*cache, *bytes), index); +} + +wasm_trap_t* +updateData_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const bytes = getDataSlice(runtime, params, index, true); + if (!bytes) + { + return hfResult(results, bytes.error()); + } + + return returnResult(runtime, params, results, hf->updateData(*bytes), index); +} + +wasm_trap_t* +checkSignature_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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* +lineKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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->lineKeylet(acc1.value(), acc2.value(), currency.value()), + index); +} + +wasm_trap_t* +mptIssuanceKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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->mptIssuanceKeylet(acc.value(), seq.value()), index); +} + +wasm_trap_t* +mptokenKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const slice = getDataSlice(runtime, params, index); + if (!slice) + { + return hfResult(results, slice.error()); + } + + if (slice->size() != MPTID::bytes) + { + return hfResult(results, HostFunctionError::INVALID_PARAMS); + } + 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* +nftOfferKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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->nftOfferKeylet(acc.value(), seq.value()), index); +} + +wasm_trap_t* +offerKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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* +paychanKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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->paychanKeylet(acc.value(), dest.value(), seq.value()), index); +} + +wasm_trap_t* +permissionedDomainKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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* +signersKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const acc = getDataAccountID(runtime, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + return returnResult(runtime, params, results, hf->signersKeylet(acc.value()), index); +} + +wasm_trap_t* +ticketKeylet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + 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* +getNFTSerial_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const nftId = getDataUInt256(runtime, params, index); + if (!nftId) + { + return hfResult(results, nftId.error()); + } + + return returnResult(runtime, params, results, hf->getNFTSerial(*nftId), index); +} + +wasm_trap_t* +trace_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + + if (params->data[1].of.i32 + params->data[3].of.i32 > maxWasmParamLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + 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()); + } + + 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::INVALID_PARAMS); + } + + return returnResult(runtime, params, results, hf->trace(*msg, *data, *asHex != 0), index); +} + +wasm_trap_t* +traceNum_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmParamLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + if (params->data[1].of.i32 > maxWasmParamLength) + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + + int i = 0; + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + if (params->data[1].of.i32 > maxWasmParamLength) + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + + int i = 0; + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + if (params->data[1].of.i32 > maxWasmParamLength) + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + + int i = 0; + 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::INVALID_PARAMS); + + return returnResult(runtime, params, results, hf->traceAmount(*msg, *amount), i); +} + +wasm_trap_t* +floatFromInt_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + 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* +floatSet_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + auto const exp = getDataInt32(runtime, params, i); + if (!exp) + return hfResult(results, exp.error()); // LCOV_EXCL_LINE + + auto const mant = getDataInt64(runtime, params, i); + if (!mant) + return hfResult(results, mant.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->floatSet(*mant, *exp, *rounding), i); +} + +wasm_trap_t* +floatCompare_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + 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(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + 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); +} + +wasm_trap_t* +floatLog_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + if (auto g = checkGas(env); !g) + return g.error(); // LCOV_EXCL_LINE + auto* hf = getHF(env); + auto const* runtime = reinterpret_cast(hf->getRT()); + + int i = 0; + 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->floatLog(*x, *rounding), i); +} + +// Contract-specific host function wrappers + +wasm_trap_t* +instanceParam_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const iindex = getDataInt32(rt, params, index); + if (!iindex) + { + return hfResult(results, iindex.error()); + } + + auto const stTypeId = getDataInt32(rt, params, index); + if (!stTypeId) + { + return hfResult(results, stTypeId.error()); + } + + return returnResult(rt, params, results, hf->instanceParam(*iindex, *stTypeId), index); +} + +wasm_trap_t* +functionParam_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const iindex = getDataInt32(rt, params, index); + if (!iindex) + { + return hfResult(results, iindex.error()); + } + + auto const stTypeId = getDataInt32(rt, params, index); + if (!stTypeId) + { + return hfResult(results, stTypeId.error()); + } + + return returnResult(rt, params, results, hf->functionParam(*iindex, *stTypeId), index); +} + +wasm_trap_t* +getDataObjectField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const acc = getDataAccountID(rt, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const key = getDataString(rt, params, index); + if (!key) + { + return hfResult(results, key.error()); + } + + if (params->data[5].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + return returnResult(rt, params, results, hf->getDataObjectField(*acc, *key), index); +} + +wasm_trap_t* +getDataNestedObjectField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const acc = getDataAccountID(rt, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const key = getDataString(rt, params, index); + if (!key) + { + return hfResult(results, key.error()); + } + + if (params->data[5].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const nested = getDataString(rt, params, index); + if (!nested) + { + return hfResult(results, nested.error()); + } + + if (params->data[7].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + return returnResult( + rt, params, results, hf->getDataNestedObjectField(*acc, *key, *nested), index); +} + +wasm_trap_t* +getDataArrayElementField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const acc = getDataAccountID(rt, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const key = getDataString(rt, params, index); + if (!key) + { + return hfResult(results, key.error()); + } + + auto const elemIndex = getDataInt32(rt, params, index); + if (!elemIndex) + { + return hfResult(results, elemIndex.error()); + } + + if (params->data[6].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + return returnResult( + rt, params, results, hf->getDataArrayElementField(*acc, *elemIndex, *key), index); +} + +wasm_trap_t* +getDataNestedArrayElementField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const acc = getDataAccountID(rt, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const key = getDataString(rt, params, index); + if (!key) + { + return hfResult(results, key.error()); + } + + auto const elemIndex = getDataInt32(rt, params, index); + if (!elemIndex) + { + return hfResult(results, elemIndex.error()); + } + + if (params->data[6].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const nested = getDataString(rt, params, index); + if (!nested) + { + return hfResult(results, nested.error()); + } + + if (params->data[8].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + return returnResult( + rt, + params, + results, + hf->getDataNestedArrayElementField(*acc, *key, *elemIndex, *nested), + index); +} + +wasm_trap_t* +setDataObjectField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const acc = getDataAccountID(rt, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const key = getDataString(rt, params, index); + if (!key) + { + return hfResult(results, key.error()); + } + + if (params->data[5].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const data = getDataSlice(rt, params, index); + if (!data) + { + return hfResult(results, data.error()); + } + + SerialIter valueSit(data->data(), data->size()); + STJson::Value const value = STJson::makeValueFromVLWithType(valueSit); + return returnResult(rt, params, results, hf->setDataObjectField(*acc, *key, value), index); +} + +wasm_trap_t* +setDataNestedObjectField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const acc = getDataAccountID(rt, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const nested = getDataString(rt, params, index); + if (!nested) + { + return hfResult(results, nested.error()); + } + + if (params->data[5].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const key = getDataString(rt, params, index); + if (!key) + { + return hfResult(results, key.error()); + } + + if (params->data[7].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const data = getDataSlice(rt, params, index); + if (!data) + { + return hfResult(results, data.error()); + } + + SerialIter valueSit(data->data(), data->size()); + STJson::Value const value = STJson::makeValueFromVLWithType(valueSit); + return returnResult( + rt, params, results, hf->setDataNestedObjectField(*acc, *nested, *key, value), index); +} + +wasm_trap_t* +setDataArrayElementField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const acc = getDataAccountID(rt, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const key = getDataString(rt, params, index); + if (!key) + { + return hfResult(results, key.error()); + } + + auto const elemIndex = getDataInt32(rt, params, index); + if (!elemIndex) + { + return hfResult(results, elemIndex.error()); + } + + if (params->data[6].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const data = getDataSlice(rt, params, index); + if (!data) + { + return hfResult(results, data.error()); + } + + SerialIter valueSit(data->data(), data->size()); + STJson::Value const value = STJson::makeValueFromVLWithType(valueSit); + return returnResult( + rt, params, results, hf->setDataArrayElementField(*acc, *elemIndex, *key, value), index); +} + +wasm_trap_t* +setDataNestedArrayElementField_wrap( + void* env, + wasm_val_vec_t const* params, + wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const acc = getDataAccountID(rt, params, index); + if (!acc) + { + return hfResult(results, acc.error()); + } + + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const key = getDataString(rt, params, index); + if (!key) + { + return hfResult(results, key.error()); + } + + auto const elemIndex = getDataInt32(rt, params, index); + if (!elemIndex) + { + return hfResult(results, elemIndex.error()); + } + + if (params->data[6].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const nested = getDataString(rt, params, index); + if (!nested) + { + return hfResult(results, nested.error()); + } + + if (params->data[8].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const data = getDataSlice(rt, params, index); + if (!data) + { + return hfResult(results, data.error()); + } + + SerialIter valueSit(data->data(), data->size()); + STJson::Value const value = STJson::makeValueFromVLWithType(valueSit); + return returnResult( + rt, + params, + results, + hf->setDataNestedArrayElementField(*acc, *key, *elemIndex, *nested, value), + index); +} + +wasm_trap_t* +buildTxn_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const txnType = getDataInt32(rt, params, index); + if (!txnType) + { + return hfResult(results, txnType.error()); + } + + return returnResult(rt, params, results, hf->buildTxn(*txnType), index); +} + +wasm_trap_t* +addTxnField_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[3].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const txnIndex = getDataInt32(rt, params, index); + if (!txnIndex) + { + return hfResult(results, txnIndex.error()); + } + + auto const fname = getDataSField(rt, params, index); + if (!fname) + { + return hfResult(results, fname.error()); + } + + auto const data = getDataSlice(rt, params, index); + if (!data) + { + return hfResult(results, data.error()); + } + + return returnResult(rt, params, results, hf->addTxnField(*txnIndex, *fname, *data), index); +} + +wasm_trap_t* +emitBuiltTxn_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + + auto const txnIndex = getDataInt32(rt, params, index); + if (!txnIndex) + { + return hfResult(results, txnIndex.error()); + } + + return returnResult(rt, params, results, hf->emitBuiltTxn(*txnIndex), index); +} + +wasm_trap_t* +emitTxn_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const slice = getDataSlice(rt, params, index); + if (!slice) + { + return hfResult(results, slice.error()); + } + + std::shared_ptr stpTrans; + try + { + stpTrans = std::make_shared(SerialIter{*slice}); + } + catch (std::exception& e) + { + std::cout << "Error creating STTx: " << e.what() << std::endl; + return hfResult(results, HostFunctionError::INTERNAL); + } + + return returnResult(rt, params, results, hf->emitTxn(stpTrans), index); +} + +wasm_trap_t* +emitEvent_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) +{ + auto* hf = getHF(env); + auto const* rt = reinterpret_cast(hf->getRT()); + int index = 0; + if (params->data[1].of.i32 > maxWasmDataLength) + { + return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + auto const name = getDataString(rt, params, index); + if (!name) + { + return hfResult(results, name.error()); + } + + auto const data = getDataSlice(rt, params, index); + if (!data) + { + return hfResult(results, data.error()); + } + + std::shared_ptr parsed; + try + { + parsed = STJson::fromBlob(data->data(), data->size()); + } + catch (std::exception const&) + { + return hfResult(results, HostFunctionError::INVALID_PARAMS); + } + + if (!parsed) + return hfResult(results, HostFunctionError::INVALID_PARAMS); + + return returnResult(rt, params, results, hf->emitEvent(*name, *parsed), index); +} + +// LCOV_EXCL_START +namespace test { + +class MockInstanceWrapper +{ + wmem mem_; + +public: + MockInstanceWrapper(wmem memory) : mem_(memory) + { + } + + // Mock methods to simulate the behavior of InstanceWrapper + wmem + getMem() const + { + return mem_; + } +}; + +bool +testGetDataIncrement() +{ + wasm_val_t values[4]; + + std::array buffer = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'}; + MockInstanceWrapper const runtime(wmem{buffer.data(), buffer.size()}); + + { + // test int32_t + wasm_val_vec_t const params = {1, &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 = {1, &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 = {1, &values[0]}; + + values[0] = WASM_I32_VAL(sfAccount.fieldCode); + + 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 = {2, &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 = {2, &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 = {2, &values[0]}; + + values[0] = WASM_I32_VAL(0); + values[1] = WASM_I32_VAL(id.bytes); + memcpy(&buffer[0], id.data(), id.bytes); + + 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 = {2, &values[0]}; + + values[0] = WASM_I32_VAL(0); + values[1] = WASM_I32_VAL(h1.bytes); + memcpy(&buffer[0], h1.data(), h1.bytes); + + 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 = {2, &values[0]}; + + values[0] = WASM_I32_VAL(0); + values[1] = WASM_I32_VAL(c.bytes); + memcpy(&buffer[0], c.data(), c.bytes); + + 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 00000000000..e0f5aeb063d --- /dev/null +++ b/src/libxrpl/tx/wasm/WasmVM.cpp @@ -0,0 +1,225 @@ +#ifdef _DEBUG +// #define DEBUG_OUTPUT 1 +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +static void +setCommonHostFunctions(HostFunctions* hfs, ImportVec& i) +{ + // clang-format off + WASM_IMPORT_FUNC2(i, getLedgerSqn, "get_ledger_sqn", hfs, 60); + WASM_IMPORT_FUNC2(i, getParentLedgerTime, "get_parent_ledger_time", hfs, 60); + WASM_IMPORT_FUNC2(i, getParentLedgerHash, "get_parent_ledger_hash", hfs, 60); + WASM_IMPORT_FUNC2(i, getBaseFee, "get_base_fee", hfs, 60); + WASM_IMPORT_FUNC2(i, isAmendmentEnabled, "amendment_enabled", hfs, 100); + + WASM_IMPORT_FUNC2(i, cacheLedgerObj, "cache_ledger_obj", hfs, 5'000); + WASM_IMPORT_FUNC2(i, getTxField, "get_tx_field", hfs, 70); + WASM_IMPORT_FUNC2(i, getCurrentLedgerObjField, "get_current_ledger_obj_field", hfs, 70); + WASM_IMPORT_FUNC2(i, getLedgerObjField, "get_ledger_obj_field", hfs, 70); + WASM_IMPORT_FUNC2(i, getTxNestedField, "get_tx_nested_field", hfs, 110); + WASM_IMPORT_FUNC2(i, getCurrentLedgerObjNestedField, "get_current_ledger_obj_nested_field", hfs, 110); + WASM_IMPORT_FUNC2(i, getLedgerObjNestedField, "get_ledger_obj_nested_field", hfs, 110); + WASM_IMPORT_FUNC2(i, getTxArrayLen, "get_tx_array_len", hfs, 40); + WASM_IMPORT_FUNC2(i, getCurrentLedgerObjArrayLen, "get_current_ledger_obj_array_len", hfs, 40); + WASM_IMPORT_FUNC2(i, getLedgerObjArrayLen, "get_ledger_obj_array_len", hfs, 40); + WASM_IMPORT_FUNC2(i, getTxNestedArrayLen, "get_tx_nested_array_len", hfs, 70); + WASM_IMPORT_FUNC2(i, getCurrentLedgerObjNestedArrayLen, "get_current_ledger_obj_nested_array_len", hfs, 70); + WASM_IMPORT_FUNC2(i, getLedgerObjNestedArrayLen, "get_ledger_obj_nested_array_len", hfs, 70); + + WASM_IMPORT_FUNC2(i, checkSignature, "check_sig", hfs, 35'000); + WASM_IMPORT_FUNC2(i, computeSha512HalfHash, "compute_sha512_half", hfs, 1'500); + + WASM_IMPORT_FUNC2(i, accountKeylet, "account_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, ammKeylet, "amm_keylet", hfs, 450); + WASM_IMPORT_FUNC2(i, checkKeylet, "check_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, credentialKeylet, "credential_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, delegateKeylet, "delegate_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, depositPreauthKeylet, "deposit_preauth_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, didKeylet, "did_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, escrowKeylet, "escrow_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, lineKeylet, "line_keylet", hfs, 400); + WASM_IMPORT_FUNC2(i, mptIssuanceKeylet, "mpt_issuance_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, mptokenKeylet, "mptoken_keylet", hfs, 500); + WASM_IMPORT_FUNC2(i, nftOfferKeylet, "nft_offer_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, offerKeylet, "offer_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, oracleKeylet, "oracle_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, paychanKeylet, "paychan_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, permissionedDomainKeylet, "permissioned_domain_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, signersKeylet, "signers_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, ticketKeylet, "ticket_keylet", hfs, 350); + WASM_IMPORT_FUNC2(i, vaultKeylet, "vault_keylet", hfs, 350); + + WASM_IMPORT_FUNC2(i, getNFT, "get_nft", hfs, 1000); + WASM_IMPORT_FUNC2(i, getNFTIssuer, "get_nft_issuer", hfs, 70); + WASM_IMPORT_FUNC2(i, getNFTTaxon, "get_nft_taxon", hfs, 60); + WASM_IMPORT_FUNC2(i, getNFTFlags, "get_nft_flags", hfs, 60); + WASM_IMPORT_FUNC2(i, getNFTTransferFee, "get_nft_transfer_fee", hfs, 60); + WASM_IMPORT_FUNC2(i, getNFTSerial, "get_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_account", hfs, 500); + WASM_IMPORT_FUNC2(i, traceFloat, "trace_opaque_float", hfs, 500); + WASM_IMPORT_FUNC2(i, traceAmount, "trace_amount", 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, floatSet, "float_set", hfs, 100); + WASM_IMPORT_FUNC2(i, floatCompare, "float_compare", hfs, 80); + WASM_IMPORT_FUNC2(i, floatAdd, "float_add", hfs, 160); + WASM_IMPORT_FUNC2(i, floatSubtract, "float_subtract", hfs, 160); + WASM_IMPORT_FUNC2(i, floatMultiply, "float_multiply", hfs, 300); + WASM_IMPORT_FUNC2(i, floatDivide, "float_divide", hfs, 300); + WASM_IMPORT_FUNC2(i, floatRoot, "float_root", hfs, 5'500); + WASM_IMPORT_FUNC2(i, floatPower, "float_pow", hfs, 5'500); + WASM_IMPORT_FUNC2(i, floatLog, "float_log", hfs, 12'000); + // clang-format on +} + +ImportVec +createWasmImport(HostFunctions& hfs) +{ + ImportVec i; + + setCommonHostFunctions(&hfs, i); + WASM_IMPORT_FUNC2(i, updateData, "update_data", &hfs, 1000); + + // clang-format off + // Contract-specific host functions + WASM_IMPORT_FUNC2(i, instanceParam, "instance_param", &hfs, 100); + WASM_IMPORT_FUNC2(i, functionParam, "function_param", &hfs, 100); + + WASM_IMPORT_FUNC2(i, getDataObjectField, "get_data_object_field", &hfs, 500); + WASM_IMPORT_FUNC2(i, getDataNestedObjectField, "get_data_nested_object_field", &hfs, 500); + WASM_IMPORT_FUNC2(i, getDataArrayElementField, "get_data_array_element_field", &hfs, 500); + WASM_IMPORT_FUNC2(i, getDataNestedArrayElementField, "get_data_nested_array_element_field", &hfs, 500); + + WASM_IMPORT_FUNC2(i, setDataObjectField, "set_data_object_field", &hfs, 500); + WASM_IMPORT_FUNC2(i, setDataNestedObjectField, "set_data_nested_object_field", &hfs, 500); + WASM_IMPORT_FUNC2(i, setDataArrayElementField, "set_data_array_element_field", &hfs, 500); + WASM_IMPORT_FUNC2(i, setDataNestedArrayElementField, "set_data_nested_array_element_field", &hfs, 500); + + WASM_IMPORT_FUNC2(i, buildTxn, "build_txn", &hfs, 200); + WASM_IMPORT_FUNC2(i, addTxnField, "add_txn_field", &hfs, 200); + WASM_IMPORT_FUNC2(i, emitBuiltTxn, "emit_built_txn", &hfs, 500); + WASM_IMPORT_FUNC2(i, emitTxn, "emit_txn", &hfs, 500); + WASM_IMPORT_FUNC2(i, emitEvent, "emit_event", &hfs, 500); + // clang-format on + + return i; +} + +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()); + + // std::cout << "runEscrowWasm, mod size: " << wasmCode.size() + // << ", gasLimit: " << gasLimit << ", funcName: " << funcName; + + if (!ret) + { +#ifdef DEBUG_OUTPUT + std::cout << ", error: " << ret.error() << std::endl; +#endif + return Unexpected(ret.error()); + } + +#ifdef DEBUG_OUTPUT + std::cout << ", ret: " << ret->result << ", gas spent: " << ret->cost << std::endl; +#endif + return EscrowResult{ret->result, 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; +} + +Expected, TER> +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 00000000000..e624d1540d2 --- /dev/null +++ b/src/libxrpl/tx/wasm/WasmiVM.cpp @@ -0,0 +1,937 @@ +#include +#include + +#include + +#ifdef _DEBUG +// #define DEBUG_OUTPUT 1 +#endif +// #define SHOW_CALL_TIME 1 + +namespace xrpl { + +namespace { + +void +print_wasm_error(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::severities::kWarning)) +#endif + { + wasm_byte_vec_t error_message WASM_EMPTY_VEC; + + if (trap != nullptr) + wasm_trap_message(trap, &error_message); + + if (error_message.size != 0u) + { + j << "WASMI Error: " << msg << ", " + << std::string_view(error_message.data, error_message.size - 1); + } + else + { + j << "WASMI Error: " << msg; + } + + if (error_message.size != 0u) + wasm_byte_vec_delete(&error_message); + } + + if (trap != nullptr) + wasm_trap_delete(trap); + +#ifdef DEBUG_OUTPUT + j << std::endl; +#endif +} +// LCOV_EXCL_STOP + +} // namespace + +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.vec_, &trap), &wasm_instance_delete); + + if (!mi || (trap != nullptr)) + { + print_wasm_error("can't create instance", trap, j); + throw std::runtime_error("can't create instance"); + } + wasm_instance_exports(mi.get(), &expt.vec_); + return mi; +} + +InstanceWrapper::InstanceWrapper() : instance_(nullptr, &wasm_instance_delete) +{ +} + +// LCOV_EXCL_START +InstanceWrapper::InstanceWrapper(InstanceWrapper&& o) : instance_(nullptr, &wasm_instance_delete) +{ + *this = std::move(o); +} +// LCOV_EXCL_STOP + +InstanceWrapper::InstanceWrapper( + StorePtr& s, + ModulePtr& m, + WasmExternVec const& imports, + beast::Journal j) + : store_(s.get()), instance_(init(s, m, exports_, imports, j)), j_(j) +{ +} + +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; +} + +InstanceWrapper:: +operator bool() const +{ + return static_cast(instance_); +} + +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 std::runtime_error("no instance"); // LCOV_EXCL_LINE + + if (exportTypes.vec_.size == 0u) + throw std::runtime_error("no export"); // LCOV_EXCL_LINE + if (exportTypes.vec_.size != exports_.vec_.size) + throw std::runtime_error("invalid export"); // LCOV_EXCL_LINE + + for (unsigned i = 0; i < exportTypes.vec_.size; ++i) + { + auto const* expType(exportTypes.vec_.data[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_.vec_.data[i]); + if (wasm_extern_kind(exn) != WASM_EXTERN_FUNC) + throw std::runtime_error("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 std::runtime_error("can't find function <" + std::string(funcName) + ">"); + + return {f, ft}; +} + +wmem +InstanceWrapper::getMem() const +{ + if (memIdx_ >= 0) + { + auto* e(exports_.vec_.data[memIdx_]); + wasm_memory_t* mem = wasm_extern_as_memory(e); + return {reinterpret_cast(wasm_memory_data(mem)), wasm_memory_data_size(mem)}; + } + + wasm_memory_t* mem = nullptr; + for (int i = 0; i < exports_.vec_.size; ++i) + { + auto* e(exports_.vec_.data[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 {reinterpret_cast(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 + print_wasm_error("Can't set instance gas", nullptr, j_); + wasmi_error_delete(err); + return -1; + // LCOV_EXCL_STOP + } + + return gas; +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +ModulePtr +ModuleWrapper::init(StorePtr& s, Bytes const& wasmBin, beast::Journal j) +{ + wasm_byte_vec_t const code{wasmBin.size(), (char*)(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; +} + +// LCOV_EXCL_START +ModuleWrapper::ModuleWrapper() : module_(nullptr, &wasm_module_delete) +{ +} + +ModuleWrapper::ModuleWrapper(ModuleWrapper&& o) : module_(nullptr, &wasm_module_delete) +{ + *this = std::move(o); +} +// LCOV_EXCL_STOP + +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_.vec_); + 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; +} + +ModuleWrapper:: +operator bool() const +{ + return instanceWrap_; +} + +// 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 WT_I32: + v.vec_.data[i] = wasm_valtype_new_i32(); + break; + case WT_I64: + v.vec_.data[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 WT_I32: + v.vec_.data[0] = wasm_valtype_new_i32(); + break; + // LCOV_EXCL_START + case WT_I64: + v.vec_.data[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.vec_); + + if (importTypes.vec_.size == 0u) + return {}; + if (imports.empty()) + throw std::runtime_error("Missing imports"); + + WasmExternVec wimports(importTypes.vec_.size); + + unsigned impCnt = 0; + for (unsigned i = 0; i < importTypes.vec_.size; ++i) + { + wasm_importtype_t const* importType = importTypes.vec_.data[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 std::runtime_error( + "Invalid import type " + std::to_string(itype)); // LCOV_EXCL_LINE + } + + // for multi-module support + // if ((W_ENV != modName) && (W_HOST_LIB != modName)) + // continue; + + bool impSet = false; + for (auto const& obj : imports) + { + auto const& imp = obj.second; + if (imp.name != fieldName) + continue; + + WasmValtypeVec params(makeImpParams(imp)); + WasmValtypeVec results(makeImpReturn(imp)); + + std::unique_ptr const ftype( + wasm_functype_new(¶ms.vec_, &results.vec_), &wasm_functype_delete); + + params.release(); + results.release(); + + wasm_func_t* func = wasm_func_new_with_env( + s.get(), + ftype.get(), + reinterpret_cast(imp.wrap), + (void*)&obj, + nullptr); + if (func == nullptr) + { + // LCOV_EXCL_START + throw std::runtime_error("can't create import function " + imp.name); + // LCOV_EXCL_STOP + } + + wimports.vec_.data[i] = wasm_func_as_extern(func); + ++impCnt; + impSet = true; + + break; + } + + if (!impSet) + { + print_wasm_error("Import not found: " + std::string(fieldName), nullptr, j_); + } + } + + if (impCnt != importTypes.vec_.size) + { + print_wasm_error( + std::string("Imports not finished: ") + std::to_string(impCnt) + "/" + + std::to_string(importTypes.vec_.size), + nullptr, + j_); + throw std::runtime_error("Missing imports"); + } + + return wimports; +} + +FuncInfo +ModuleWrapper::getFunc(std::string_view funcName) const +{ + return instanceWrap_.getFunc(funcName, exportTypes_); +} + +wasm_functype_t* +ModuleWrapper::getFuncType(std::string_view funcName) const +{ + for (size_t i = 0; i < exportTypes_.vec_.size; i++) + { + auto const* exp_type(exportTypes_.vec_.data[i]); + wasm_name_t const* name = wasm_exporttype_name(exp_type); + wasm_externtype_t const* exn_type = wasm_exporttype_type(exp_type); + if (wasm_externtype_kind(exn_type) == WASM_EXTERN_FUNC && + funcName == std::string_view(name->data, name->size)) + { + return wasm_externtype_as_functype(const_cast(exn_type)); + } + } + + throw std::runtime_error("can't find function <" + std::string(funcName) + ">"); +} + +wmem +ModuleWrapper::getMem() const +{ + return instanceWrap_.getMem(); +} + +InstanceWrapper const& +ModuleWrapper::getInstance(int) const +{ + return instanceWrap_; +} + +int +ModuleWrapper::addInstance(StorePtr& s, WasmExternVec const& imports) +{ + instanceWrap_ = {s, module_, imports, j_}; + return 0; +} + +// 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; +// } + +std::int64_t +ModuleWrapper::getGas() const +{ + return instanceWrap_ ? instanceWrap_.getGas() : -1; +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// 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); +} + +WasmiEngine::WasmiEngine() : engine_(init()), store_(nullptr, &wasm_store_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(), MAX_PAGES), &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 + print_wasm_error("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()); +// } + +FuncInfo +WasmiEngine::getFunc(std::string_view funcName) const +{ + return moduleWrap_->getFunc(funcName); +} + +std::vector +WasmiEngine::convertParams(std::vector const& params) +{ + std::vector v; + v.reserve(params.size()); + for (auto const& p : params) + { + switch (p.type) + { + case WT_I32: + v.push_back(WASM_I32_VAL(p.of.i32)); + break; + // LCOV_EXCL_START + case WT_I64: + v.push_back(WASM_I64_VAL(p.of.i64)); + break; + default: + throw std::runtime_error("unknown parameter type: " + std::to_string(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::add_param(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::add_param(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) +{ + // wasm_val_t rs[1] = {WASM_I32_VAL(0)}; + WasmiResult ret(NR); + // if (NR) { wasm_val_vec_new_uninitialized(&ret, NR); // + // wasm_val_vec_new(&ret, NR, &rs[0]); // ret = WASM_ARRAY_VEC(rs); } + + wasm_val_vec_t const inv = + in.empty() ? wasm_val_vec_t WASM_EMPTY_VEC : wasm_val_vec_t{in.size(), in.data()}; + +#ifdef SHOW_CALL_TIME + auto const start = usecs(); +#endif + + wasm_trap_t* trap = wasm_func_call(f.first, &inv, &ret.r.vec_); + +#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) + { + ret.f = true; + print_wasm_error("failure to call func", trap, j_); + } + + // assert(results[0].kind == WASM_I32); + // if (NR) printf("Result P5: %d\n", ret[0].of.i32); + + return ret; +} + +template +WasmiResult +WasmiEngine::call(FuncInfo const& f, std::vector& in, std::int32_t p, Types&&... args) +{ + add_param(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) +{ + add_param(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.first) + Throw("Imports hf unsync"); + } +} + +Expected, TER> +WasmiEngine::run( + Bytes const& wasmCode, + HostFunctions& hfs, + int64_t gas, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports, + beast::Journal j) +{ + j_ = j; + + if (gas <= 0) + return Unexpected(temBAD_AMOUNT); + + try + { + checkImports(imports, &hfs); + return runHlp(wasmCode, hfs, gas, funcName, params, imports); + } + catch (std::exception const& e) + { + print_wasm_error(std::string("exception: ") + e.what(), nullptr, j_); + } + // LCOV_EXCL_START + catch (...) + { + print_wasm_error(std::string("exception: unknown"), nullptr, j_); + } + // LCOV_EXCL_STOP + return Unexpected(tecFAILED_PROCESSING); +} + +Expected, TER> +WasmiEngine::runHlp( + Bytes const& wasmCode, + HostFunctions& hfs, + int64_t gas, + std::string_view funcName, + std::vector const& params, + ImportVec const& imports) +{ + // currently only 1 module support, possible parallel UT run + std::lock_guard const lg(m_); + + 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_->instanceWrap_) + throw std::runtime_error("no instance"); // LCOV_EXCL_LINE + + hfs.setRT(&getRT()); + + // 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 (res.f) + { + throw std::runtime_error("<" + std::string(funcName) + "> failure"); + } + if (res.r.vec_.size == 0u) + { + throw std::runtime_error( + "<" + std::string(funcName) + "> return nothing"); // LCOV_EXCL_LINE + } + if (res.r.vec_.data[0].kind != WASM_I32) + { + throw std::runtime_error( + "<" + std::string(funcName) + "> return type mismatch, ret: " + + std::to_string(static_cast(res.r.vec_.data[0].kind))); + } + + if (gas == -1) + gas = std::numeric_limits::max(); + WasmResult const ret{res.r.vec_.data[0].of.i32, 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) +{ + j_ = j; + + try + { + checkImports(imports, &hfs); + return checkHlp(wasmCode, hfs, funcName, params, imports); + } + catch (std::exception const& e) + { + print_wasm_error(std::string("exception: ") + e.what(), nullptr, j_); + } + // LCOV_EXCL_START + catch (...) + { + print_wasm_error(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) +{ + // currently only 1 module support, possible parallel UT run + std::lock_guard const lg(m_); + + // Create and instantiate the module. + if (wasmCode.empty()) + throw std::runtime_error("empty nodule"); + + 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; +} + +// LCOV_EXCL_START +std::int64_t +WasmiEngine::getGas() const +{ + return moduleWrap_ ? moduleWrap_->getGas() : -1; +} +// LCOV_EXCL_STOP + +wmem +WasmiEngine::getMem() const +{ + return moduleWrap_ ? moduleWrap_->getMem() : wmem(); +} + +InstanceWrapper const& +WasmiEngine::getRT(int m, int i) const +{ + if (!moduleWrap_) + throw std::runtime_error("no module"); + return moduleWrap_->getInstance(i); +} + +wasm_trap_t* +WasmiEngine::newTrap(std::string const& txt) +{ + static char empty[1] = {0}; + wasm_message_t msg = {1, 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; +} + +// LCOV_EXCL_START +beast::Journal +WasmiEngine::getJournal() const +{ + return j_; +} +// LCOV_EXCL_STOP + +} // namespace xrpl diff --git a/src/test/app/AMM_test.cpp b/src/test/app/AMM_test.cpp index 17f959ae3cc..4dd3b8b7812 100644 --- a/src/test/app/AMM_test.cpp +++ b/src/test/app/AMM_test.cpp @@ -6710,7 +6710,7 @@ struct AMM_test : public jtx::AMMTest using namespace test::jtx; auto const testCase = [&](std::string suffix, FeatureBitset features) { - testcase("Fail pseudo-account allocation " + suffix); + testcase("Pseudo-account allocation failure " + suffix); std::string logs; Env env{*this, features, std::make_unique(&logs)}; env.fund(XRP(30'000), gw_, alice_); diff --git a/src/test/app/ContractHostFuncImpl_test.cpp b/src/test/app/ContractHostFuncImpl_test.cpp new file mode 100644 index 00000000000..0c5975fd759 --- /dev/null +++ b/src/test/app/ContractHostFuncImpl_test.cpp @@ -0,0 +1,1374 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { +namespace test { + +static ApplyContext +createApplyContext( + test::jtx::Env& env, + OpenView& ov, + STTx const& tx = STTx(ttCONTRACT_CALL, [](STObject&) {})) +{ + ApplyContext ac{ + env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, tapNONE, env.journal}; + return ac; +} + +struct ContractHostFuncImpl_test : public beast::unit_test::suite +{ + ContractContext + createContractContext( + ApplyContext& ac, + jtx::Account const& contract, + jtx::Account const& otxn, + uint256 const& contractHash = uint256{1}) + { + using namespace jtx; + xrpl::ContractDataMap const dataMap; + xrpl::ContractEventMap const eventMap; + std::vector const instanceParameters; + std::vector const functionParameters; + + uint256 const& txId = uint256{2}; + + auto const nextSequence = + ac.view().read(keylet::account(contract.id()))->getFieldU32(sfSequence); + + auto const k = keylet::contract(contractHash, otxn, 0); + return ContractContext{ + .applyCtx = ac, + .instanceParameters = instanceParameters, + .functionParameters = functionParameters, + .built_txns = {}, + .expected_etxn_count = 0, + .generation = 0, + .burden = 0, + .result = + { + .contractHash = contractHash, + .contractKeylet = k, + .contractSourceKeylet = k, + .contractAccountKeylet = k, + .contractAccount = contract.id(), + .nextSequence = nextSequence, + .otxnAccount = otxn.id(), + .otxnId = txId, + .exitCode = -1, + .dataMap = dataMap, + .eventMap = eventMap, + .changedDataCount = 0, + }, + .emitView = std::nullopt, + }; + } + + // Helper function to create STJson::Value from different types + STJson::Value + createJsonValue(SerializedTypeID type, std::function addData) + { + Serializer s; + s.add8(static_cast(type)); + addData(s); + + SerialIter sit(s.peekData().data(), s.peekData().size()); + return STJson::makeValueFromVLWithType(sit); + } + + void + testInstanceParam() + { + testcase("instanceParam"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const contract("contract"); + Account const otxn("otxn"); + env.fund(XRP(10000), alice, contract, otxn); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto contractCtx = createContractContext(ac, contract, otxn); + + // Add test instance parameters for all supported types + + // UINT8 + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, uint8_t{0xFF})}); + + // UINT16 + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, uint16_t{0xFFFF})}); + + // UINT32 + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, uint32_t{0xFFFFFFFF})}); + + // UINT64 + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, uint64_t{0x8000000000000000})}); + + // UINT128 + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, uint128{1})}); + + // UINT160 + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, uint160{1})}); + + // UINT192 + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, uint192{1})}); + + // UINT256 + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, uint256{1})}); + + // VL (Variable Length) + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, Blob{0x01, 0x02, 0x03, 0x04, 0x05})}); + + // ACCOUNT + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, alice.id())}); + + // AMOUNT (XRP) + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, STAmount{100000})}); + + // AMOUNT (IOU) + contractCtx.instanceParameters.push_back( + ParameterValueVec{ + STData(sfParameterValue, STAmount{Issue{Currency{1}, AccountID{2}}, 1000})}); + + // AMOUNT (MPT) + MPTIssue const mpt{MPTIssue{makeMptID(1, AccountID(0x4985601))}}; + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, STAmount{mpt, 1000})}); + + // NUMBER + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, numberFromJson(sfNumber, "42.5"))}); + + // ISSUE + // STIssue{sfAsset2, MPTIssue{mptId} + auto const iouAsset = env.master["USD"]; + contractCtx.instanceParameters.push_back( + ParameterValueVec{STData(sfParameterValue, STIssue{sfAsset, iouAsset.issue()})}); + + // CURRENCY + contractCtx.instanceParameters.push_back( + ParameterValueVec{ + STData(sfParameterValue, STCurrency{sfBaseAsset, iouAsset.issue().currency})}); + + ContractHostFunctionsImpl cfs(contractCtx); + + // Test UINT8 + { + auto result = cfs.instanceParam(0, STI_UINT8); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 1); + BEAST_EXPECT(bytes[0] == 0xFF); + } + } + + // Test UINT16 + { + auto result = cfs.instanceParam(1, STI_UINT16); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 2); + BEAST_EXPECT(bytes[0] == 0xFF); + BEAST_EXPECT(bytes[1] == 0xFF); + } + } + + // Test UINT32 + { + auto result = cfs.instanceParam(2, STI_UINT32); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 4); + BEAST_EXPECT(bytes[0] == 0xFF); + BEAST_EXPECT(bytes[1] == 0xFF); + BEAST_EXPECT(bytes[2] == 0xFF); + BEAST_EXPECT(bytes[3] == 0xFF); + } + } + + // Test UINT64 + { + auto result = cfs.instanceParam(3, STI_UINT64); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 8); + BEAST_EXPECT(bytes[0] == 0x00); + BEAST_EXPECT(bytes[7] == 0x80); + } + } + + // Test UINT128 + { + auto result = cfs.instanceParam(4, STI_UINT128); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == sizeof(uint128)); + BEAST_EXPECT(bytes[15] == 0x01); + } + } + + // Test UINT160 + { + auto result = cfs.instanceParam(5, STI_UINT160); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == sizeof(uint160)); + BEAST_EXPECT(bytes[19] == 0x01); + } + } + + // Test UINT192 + { + auto result = cfs.instanceParam(6, STI_UINT192); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == sizeof(uint192)); + BEAST_EXPECT(bytes[23] == 0x01); + } + } + + // Test UINT256 + { + auto result = cfs.instanceParam(7, STI_UINT256); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == sizeof(uint256)); + BEAST_EXPECT(bytes[31] == 0x01); + } + } + + // Test VL + { + auto result = cfs.instanceParam(8, STI_VL); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 5); + BEAST_EXPECT(bytes[0] == 0x01); + BEAST_EXPECT(bytes[4] == 0x05); + } + } + + // Test ACCOUNT + { + auto result = cfs.instanceParam(9, STI_ACCOUNT); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 20); + } + } + + // Test AMOUNT (XRP) + { + auto result = cfs.instanceParam(10, STI_AMOUNT); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 8); // Native amount + } + } + + // Test AMOUNT (IOU) + { + auto result = cfs.instanceParam(11, STI_AMOUNT); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 48); // IOU amount + } + } + + // Test AMOUNT (MPT) + { + auto result = cfs.instanceParam(12, STI_AMOUNT); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 33); // MPT amount + } + } + + // Test NUMBER + { + auto result = cfs.instanceParam(13, STI_NUMBER); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 12); + } + } + + // Test ISSUE + { + auto result = cfs.instanceParam(14, STI_ISSUE); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 40); + } + } + + // Test CURRENCY + { + auto result = cfs.instanceParam(15, STI_CURRENCY); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto& bytes = result.value(); + BEAST_EXPECT(bytes.size() == 20); + } + } + + // Test index out of bounds + { + auto result = cfs.instanceParam(16, STI_UINT32); + BEAST_EXPECT(!result.has_value()); + if (!result.has_value()) + BEAST_EXPECT(result.error() == HostFunctionError::INDEX_OUT_OF_BOUNDS); + } + + // Test type mismatch + { + auto result = cfs.instanceParam(0, STI_UINT64); // Index 0 is UINT8 + BEAST_EXPECT(!result.has_value()); + if (!result.has_value()) + BEAST_EXPECT(result.error() == HostFunctionError::INVALID_PARAMS); + } + + // Test unsupported types + { + auto result = cfs.instanceParam(2, STI_PATHSET); + BEAST_EXPECT(!result.has_value()); + if (!result.has_value()) + BEAST_EXPECT(result.error() == HostFunctionError::INVALID_PARAMS); + } + } + + void + testFunctionParam() + { + testcase("functionParam"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const contract("contract"); + Account const otxn("otxn"); + env.fund(XRP(10000), alice, contract, otxn); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto contractCtx = createContractContext(ac, contract, otxn); + + // Add test function parameters (same as instance parameters for + // testing) [Similar parameter setup as instanceParam test...] + + ContractHostFunctionsImpl const cfs(contractCtx); + + // [Similar tests as instanceParam but using functionParam method...] + } + + void + testContractDataFromKey() + { + testcase("contractDataFromKey"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const bob("bob"); + Account const contract("contract"); + Account const otxn("otxn"); + env.fund(XRP(10000), alice, bob, contract, otxn); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto contractCtx = createContractContext(ac, contract, otxn); + + ContractHostFunctionsImpl cfs(contractCtx); + + // Test setDataObjectField - string value + { + // Create a properly formatted STJson::Value for a VL (string) + auto value = createJsonValue(STI_VL, [](Serializer& s) { + s.addVL(Blob{0x61, 0x62, 0x63}); // "abc" + }); + + auto setResult = cfs.setDataObjectField(alice.id(), "name", value); + BEAST_EXPECT(!setResult.has_value()); + if (!setResult.has_value()) + BEAST_EXPECT(setResult.error() == HostFunctionError::INTERNAL); + + // Verify data was cached and can be retrieved + auto getResult = cfs.getDataObjectField(alice.id(), "name"); + BEAST_EXPECT(getResult.has_value()); + if (getResult.has_value()) + { + auto& bytes = getResult.value(); + BEAST_EXPECT(bytes.size() > 0); + } + } + + // Test setDataObjectField - numeric value (UINT32) + { + auto value = createJsonValue(STI_UINT32, [](Serializer& s) { s.add32(30); }); + + auto setResult = cfs.setDataObjectField(alice.id(), "age", value); + BEAST_EXPECT(!setResult.has_value()); + if (!setResult.has_value()) + BEAST_EXPECT(setResult.error() == HostFunctionError::INTERNAL); + + auto getResult = cfs.getDataObjectField(alice.id(), "age"); + BEAST_EXPECT(getResult.has_value()); + } + + // Test setDataObjectField - UINT8 value (for boolean-like) + { + auto value = createJsonValue(STI_UINT8, [](Serializer& s) { + s.add8(1); // true + }); + + auto setResult = cfs.setDataObjectField(alice.id(), "verified", value); + BEAST_EXPECT(!setResult.has_value()); + if (!setResult.has_value()) + BEAST_EXPECT(setResult.error() == HostFunctionError::INTERNAL); + + auto getResult = cfs.getDataObjectField(alice.id(), "verified"); + BEAST_EXPECT(getResult.has_value()); + } + + // Test getting non-existent key + { + auto getResult = cfs.getDataObjectField(alice.id(), "nonexistent"); + BEAST_EXPECT(!getResult.has_value()); + if (!getResult.has_value()) + BEAST_EXPECT(getResult.error() == HostFunctionError::INVALID_FIELD); + } + + // Test updating existing key + { + auto value1 = createJsonValue(STI_UINT32, [](Serializer& s) { s.add32(100); }); + auto setResult1 = cfs.setDataObjectField(bob.id(), "balance", value1); + BEAST_EXPECT(!setResult1.has_value()); + + auto value2 = createJsonValue(STI_UINT32, [](Serializer& s) { s.add32(200); }); + auto setResult2 = cfs.setDataObjectField(bob.id(), "balance", value2); + BEAST_EXPECT(!setResult2.has_value()); + + auto getResult = cfs.getDataObjectField(bob.id(), "balance"); + BEAST_EXPECT(getResult.has_value()); + } + + // Test multiple keys for same account + { + auto value1 = createJsonValue(STI_VL, [](Serializer& s) { + Blob const data = {'v', 'a', 'l', 'u', 'e', '1'}; + s.addVL(data); + }); + auto setResult1 = cfs.setDataObjectField(alice.id(), "field1", value1); + BEAST_EXPECT(!setResult1.has_value()); + + auto value2 = createJsonValue(STI_VL, [](Serializer& s) { + Blob const data = {'v', 'a', 'l', 'u', 'e', '2'}; + s.addVL(data); + }); + auto setResult2 = cfs.setDataObjectField(alice.id(), "field2", value2); + BEAST_EXPECT(!setResult2.has_value()); + + auto value3 = createJsonValue(STI_UINT32, [](Serializer& s) { s.add32(123); }); + auto setResult3 = cfs.setDataObjectField(alice.id(), "field3", value3); + BEAST_EXPECT(!setResult3.has_value()); + + // Verify all keys exist + auto getResult1 = cfs.getDataObjectField(alice.id(), "field1"); + BEAST_EXPECT(getResult1.has_value()); + + auto getResult2 = cfs.getDataObjectField(alice.id(), "field2"); + BEAST_EXPECT(getResult2.has_value()); + + auto getResult3 = cfs.getDataObjectField(alice.id(), "field3"); + BEAST_EXPECT(getResult3.has_value()); + } + } + + void + testNestedContractDataFromKey() + { + testcase("nestedContractDataFromKey"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const bob("bob"); + Account const contract("contract"); + Account const otxn("otxn"); + env.fund(XRP(10000), alice, bob, contract, otxn); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto contractCtx = createContractContext(ac, contract, otxn); + + ContractHostFunctionsImpl cfs(contractCtx); + + // Test setDataNestedObjectField + { + auto value = createJsonValue(STI_VL, [](Serializer& s) { + Blob const data = {'A', 'l', 'i', 'c', 'e'}; + s.addVL(data); + }); + + auto setResult = + cfs.setDataNestedObjectField(alice.id(), "profile", "firstName", value); + BEAST_EXPECT(setResult.has_value()); + + // Add more nested fields + auto value2 = createJsonValue(STI_VL, [](Serializer& s) { + Blob const data = {'S', 'm', 'i', 't', 'h'}; + s.addVL(data); + }); + auto setResult2 = + cfs.setDataNestedObjectField(alice.id(), "profile", "lastName", value2); + BEAST_EXPECT(setResult2.has_value()); + + auto value3 = createJsonValue(STI_UINT32, [](Serializer& s) { s.add32(25); }); + auto setResult3 = cfs.setDataNestedObjectField(alice.id(), "profile", "age", value3); + BEAST_EXPECT(setResult3.has_value()); + + // Retrieve nested fields + auto getResult1 = cfs.getDataNestedObjectField(alice.id(), "profile", "firstName"); + BEAST_EXPECT(getResult1.has_value()); + + auto getResult2 = cfs.getDataNestedObjectField(alice.id(), "profile", "lastName"); + BEAST_EXPECT(getResult2.has_value()); + + auto getResult3 = cfs.getDataNestedObjectField(alice.id(), "profile", "age"); + BEAST_EXPECT(getResult3.has_value()); + } + + // Test nested objects with different parent keys + { + auto value1 = createJsonValue(STI_VL, [](Serializer& s) { + Blob const data = {'d', 'a', 'r', 'k'}; + s.addVL(data); + }); + auto setResult1 = cfs.setDataNestedObjectField(alice.id(), "settings", "theme", value1); + BEAST_EXPECT(setResult1.has_value()); + + auto value2 = createJsonValue(STI_UINT8, [](Serializer& s) { + s.add8(1); // true + }); + auto setResult2 = + cfs.setDataNestedObjectField(alice.id(), "settings", "notifications", value2); + BEAST_EXPECT(setResult2.has_value()); + + auto value3 = createJsonValue(STI_VL, [](Serializer& s) { + Blob const data = {'e', 'n'}; + s.addVL(data); + }); + auto setResult3 = + cfs.setDataNestedObjectField(alice.id(), "preferences", "language", value3); + BEAST_EXPECT(setResult3.has_value()); + + // Verify nested data retrieval + auto getResult1 = cfs.getDataNestedObjectField(alice.id(), "settings", "theme"); + BEAST_EXPECT(getResult1.has_value()); + + auto getResult2 = cfs.getDataNestedObjectField(alice.id(), "settings", "notifications"); + BEAST_EXPECT(getResult2.has_value()); + + auto getResult3 = cfs.getDataNestedObjectField(alice.id(), "preferences", "language"); + BEAST_EXPECT(getResult3.has_value()); + } + + // Test getting non-existent nested key + { + auto getResult = cfs.getDataNestedObjectField(alice.id(), "nonexistent", "key"); + BEAST_EXPECT(!getResult.has_value()); + if (!getResult.has_value()) + BEAST_EXPECT(getResult.error() == HostFunctionError::INVALID_FIELD); + + auto getResult2 = cfs.getDataNestedObjectField(alice.id(), "profile", "nonexistent"); + BEAST_EXPECT(!getResult2.has_value()); + if (!getResult2.has_value()) + BEAST_EXPECT(getResult2.error() == HostFunctionError::INVALID_FIELD); + } + + // Test updating existing nested key + { + auto value1 = createJsonValue(STI_UINT32, [](Serializer& s) { s.add32(100); }); + auto setResult1 = cfs.setDataNestedObjectField(bob.id(), "stats", "score", value1); + BEAST_EXPECT(setResult1.has_value()); + + auto value2 = createJsonValue(STI_UINT32, [](Serializer& s) { s.add32(150); }); + auto setResult2 = cfs.setDataNestedObjectField(bob.id(), "stats", "score", value2); + BEAST_EXPECT(setResult2.has_value()); + + auto getResult = cfs.getDataNestedObjectField(bob.id(), "stats", "score"); + BEAST_EXPECT(getResult.has_value()); + } + } + + void + testNestedContractDataFromArrayKey() + { + testcase("nestedContractDataFromArrayKey"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const bob("bob"); + Account const contract("contract"); + Account const otxn("otxn"); + env.fund(XRP(10000), alice, bob, contract, otxn); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto contractCtx = createContractContext(ac, contract, otxn); + + ContractHostFunctionsImpl cfs(contractCtx); + + // Test setDataNestedObjectField + { + auto value = createJsonValue(STI_VL, [](Serializer& s) { + Blob const data = {'A', 'l', 'i', 'c', 'e'}; + s.addVL(data); + }); + + auto setResult = + cfs.setDataNestedObjectField(alice.id(), "profile", "firstName", value); + BEAST_EXPECT(setResult.has_value()); + + // Add more nested fields + auto value2 = createJsonValue(STI_VL, [](Serializer& s) { + Blob const data = {'S', 'm', 'i', 't', 'h'}; + s.addVL(data); + }); + auto setResult2 = + cfs.setDataNestedObjectField(alice.id(), "profile", "lastName", value2); + BEAST_EXPECT(setResult2.has_value()); + + auto value3 = createJsonValue(STI_UINT32, [](Serializer& s) { s.add32(25); }); + auto setResult3 = cfs.setDataNestedObjectField(alice.id(), "profile", "age", value3); + BEAST_EXPECT(setResult3.has_value()); + + // Retrieve nested fields + auto getResult1 = cfs.getDataNestedObjectField(alice.id(), "profile", "firstName"); + BEAST_EXPECT(getResult1.has_value()); + + auto getResult2 = cfs.getDataNestedObjectField(alice.id(), "profile", "lastName"); + BEAST_EXPECT(getResult2.has_value()); + + auto getResult3 = cfs.getDataNestedObjectField(alice.id(), "profile", "age"); + BEAST_EXPECT(getResult3.has_value()); + } + + // Test nested objects with different parent keys + { + auto value1 = createJsonValue(STI_VL, [](Serializer& s) { + Blob const data = {'d', 'a', 'r', 'k'}; + s.addVL(data); + }); + auto setResult1 = cfs.setDataNestedObjectField(alice.id(), "settings", "theme", value1); + BEAST_EXPECT(setResult1.has_value()); + + auto value2 = createJsonValue(STI_UINT8, [](Serializer& s) { + s.add8(1); // true + }); + auto setResult2 = + cfs.setDataNestedObjectField(alice.id(), "settings", "notifications", value2); + BEAST_EXPECT(setResult2.has_value()); + + auto value3 = createJsonValue(STI_VL, [](Serializer& s) { + Blob const data = {'e', 'n'}; + s.addVL(data); + }); + auto setResult3 = + cfs.setDataNestedObjectField(alice.id(), "preferences", "language", value3); + BEAST_EXPECT(setResult3.has_value()); + + // Verify nested data retrieval + auto getResult1 = cfs.getDataNestedObjectField(alice.id(), "settings", "theme"); + BEAST_EXPECT(getResult1.has_value()); + + auto getResult2 = cfs.getDataNestedObjectField(alice.id(), "settings", "notifications"); + BEAST_EXPECT(getResult2.has_value()); + + auto getResult3 = cfs.getDataNestedObjectField(alice.id(), "preferences", "language"); + BEAST_EXPECT(getResult3.has_value()); + } + + // Test getting non-existent nested key + { + auto getResult = cfs.getDataNestedObjectField(alice.id(), "nonexistent", "key"); + BEAST_EXPECT(!getResult.has_value()); + if (!getResult.has_value()) + BEAST_EXPECT(getResult.error() == HostFunctionError::INVALID_FIELD); + + auto getResult2 = cfs.getDataNestedObjectField(alice.id(), "profile", "nonexistent"); + BEAST_EXPECT(!getResult2.has_value()); + if (!getResult2.has_value()) + BEAST_EXPECT(getResult2.error() == HostFunctionError::INVALID_FIELD); + } + + // Test updating existing nested key + { + auto value1 = createJsonValue(STI_UINT32, [](Serializer& s) { s.add32(100); }); + auto setResult1 = cfs.setDataNestedObjectField(bob.id(), "stats", "score", value1); + BEAST_EXPECT(setResult1.has_value()); + + auto value2 = createJsonValue(STI_UINT32, [](Serializer& s) { s.add32(150); }); + auto setResult2 = cfs.setDataNestedObjectField(bob.id(), "stats", "score", value2); + BEAST_EXPECT(setResult2.has_value()); + + auto getResult = cfs.getDataNestedObjectField(bob.id(), "stats", "score"); + BEAST_EXPECT(getResult.has_value()); + } + } + + void + testBuildTxn() + { + testcase("buildTxn"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const contract("contract"); + Account const otxn("otxn"); + env.fund(XRP(10000), alice, contract, otxn); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto contractCtx = createContractContext(ac, contract, otxn); + + ContractHostFunctionsImpl cfs(contractCtx); + + // Test building a Payment transaction + { + auto result = cfs.buildTxn(ttPAYMENT); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto txIndex = result.value(); + BEAST_EXPECT(txIndex == 0); + BEAST_EXPECT(contractCtx.built_txns.size() == 1); + + // Verify the transaction has required fields + auto& txn = contractCtx.built_txns[0]; + BEAST_EXPECT(txn.isFieldPresent(sfTransactionType)); + BEAST_EXPECT(txn.getFieldU16(sfTransactionType) == ttPAYMENT); + BEAST_EXPECT(txn.isFieldPresent(sfAccount)); + BEAST_EXPECT(txn.getAccountID(sfAccount) == contract.id()); + BEAST_EXPECT(txn.isFieldPresent(sfSequence)); + BEAST_EXPECT(txn.getFieldU32(sfSequence) == 1); + BEAST_EXPECT(txn.isFieldPresent(sfFee)); + BEAST_EXPECT(txn.getFieldAmount(sfFee) == XRP(0)); + BEAST_EXPECT(txn.isFieldPresent(sfFlags)); + BEAST_EXPECT(txn.getFieldU32(sfFlags) == 536870912); + + // Verify sequence incremented + BEAST_EXPECT(contractCtx.result.nextSequence == 2); + } + } + + // Test building an AccountSet transaction + { + auto result = cfs.buildTxn(ttACCOUNT_SET); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto txIndex = result.value(); + BEAST_EXPECT(txIndex == 1); + BEAST_EXPECT(contractCtx.built_txns.size() == 2); + + auto& txn = contractCtx.built_txns[1]; + BEAST_EXPECT(txn.getFieldU16(sfTransactionType) == ttACCOUNT_SET); + BEAST_EXPECT(txn.getFieldU32(sfSequence) == 2); + BEAST_EXPECT(contractCtx.result.nextSequence == 3); + } + } + + // Test building a TrustSet transaction + { + auto result = cfs.buildTxn(ttTRUST_SET); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + auto txIndex = result.value(); + BEAST_EXPECT(txIndex == 2); + BEAST_EXPECT(contractCtx.built_txns.size() == 3); + + auto& txn = contractCtx.built_txns[2]; + BEAST_EXPECT(txn.getFieldU16(sfTransactionType) == ttTRUST_SET); + } + } + + // Test building multiple transactions in sequence + { + auto initialSize = contractCtx.built_txns.size(); + auto initialSeq = contractCtx.result.nextSequence; + + for (int i = 0; i < 5; ++i) + { + auto result = cfs.buildTxn(ttPAYMENT); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == initialSize + i); + } + } + + BEAST_EXPECT(contractCtx.built_txns.size() == initialSize + 5); + BEAST_EXPECT(contractCtx.result.nextSequence == initialSeq + 5); + } + } + + void + testAddTxnField() + { + testcase("addTxnField"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const bob("bob"); + Account const contract("contract"); + Account const otxn("otxn"); + env.fund(XRP(10000), alice, bob, contract, otxn); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto contractCtx = createContractContext(ac, contract, otxn); + + ContractHostFunctionsImpl cfs(contractCtx); + + // Build a Payment transaction to add fields to + auto buildResult = cfs.buildTxn(ttPAYMENT); + BEAST_EXPECT(buildResult.has_value()); + if (!buildResult.has_value()) + return; + + uint32_t const txIndex = buildResult.value(); + + // Test adding Destination (AccountID) with 0x14 prefix + { + AccountID data = bob.id(); + // Prepend the required type byte 0x14 before the 20-byte AccountID + Blob buf; + buf.reserve(1 + data.size()); + buf.push_back(0x14); + buf.insert(buf.end(), data.begin(), data.end()); + auto const s = Slice{buf.data(), buf.size()}; + auto result = cfs.addTxnField(txIndex, sfDestination, s); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[txIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfDestination)); + BEAST_EXPECT(txn.getAccountID(sfDestination) == bob.id()); + } + } + + // Test adding Amount (STAmount - XRP) + { + STAmount const amount{XRP(1000)}; + Serializer s; + amount.add(s); + auto result = cfs.addTxnField(txIndex, sfAmount, s.slice()); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[txIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfAmount)); + BEAST_EXPECT(txn.getFieldAmount(sfAmount) == amount); + } + } + + // Test adding SendMax (STAmount - IOU) + { + auto const USD = alice["USD"]; + STAmount const sendMax{USD.issue(), 500}; + Serializer s; + sendMax.add(s); + auto result = cfs.addTxnField(txIndex, sfSendMax, s.slice()); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[txIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfSendMax)); + BEAST_EXPECT(txn.getFieldAmount(sfSendMax) == sendMax); + } + } + + // Test adding DestinationTag (UInt32) + { + uint32_t const tag = 12345; + Serializer s; + s.add32(tag); + auto result = cfs.addTxnField(txIndex, sfDestinationTag, s.slice()); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[txIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfDestinationTag)); + BEAST_EXPECT(txn.getFieldU32(sfDestinationTag) == tag); + } + } + + // Build a TrustSet transaction for testing additional fields + auto trustBuildResult = cfs.buildTxn(ttTRUST_SET); + BEAST_EXPECT(trustBuildResult.has_value()); + if (trustBuildResult.has_value()) + { + uint32_t const trustIndex = trustBuildResult.value(); + + // Test adding LimitAmount (STAmount for TrustSet) + { + auto const EUR = alice["EUR"]; + STAmount const limit{EUR.issue(), 10000}; + Serializer s; + limit.add(s); + auto result = cfs.addTxnField(trustIndex, sfLimitAmount, s.slice()); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[trustIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfLimitAmount)); + BEAST_EXPECT(txn.getFieldAmount(sfLimitAmount) == limit); + } + } + + // Test adding QualityIn (UInt32) + { + uint32_t const quality = 1000000; + Serializer s; + s.add32(quality); + auto result = cfs.addTxnField(trustIndex, sfQualityIn, s.slice()); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[trustIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfQualityIn)); + BEAST_EXPECT(txn.getFieldU32(sfQualityIn) == quality); + } + } + } + + // Build an AccountSet transaction for testing more fields + auto accSetResult = cfs.buildTxn(ttACCOUNT_SET); + BEAST_EXPECT(accSetResult.has_value()); + if (accSetResult.has_value()) + { + uint32_t const accSetIndex = accSetResult.value(); + + // Test adding Domain (Blob/VL) + { + Blob const domain = {'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm'}; + Serializer s; + s.addVL(domain); + auto result = cfs.addTxnField(accSetIndex, sfDomain, s.slice()); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[accSetIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfDomain)); + BEAST_EXPECT(txn.getFieldVL(sfDomain) == domain); + } + } + + // Test adding TransferRate (UInt32) + { + uint32_t const fee = 500; + Serializer s; + s.add32(fee); + auto result = cfs.addTxnField(accSetIndex, sfTransferRate, s.slice()); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[accSetIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfTransferRate)); + BEAST_EXPECT(txn.getFieldU32(sfTransferRate) == fee); + } + } + + // Test adding SetFlag (UInt32) + { + uint32_t const flag = 8; // asfRequireAuth + Serializer s; + s.add32(flag); + auto result = cfs.addTxnField(accSetIndex, sfSetFlag, s.slice()); + BEAST_EXPECT(result.has_value()); + if (result.has_value()) + { + BEAST_EXPECT(result.value() == 0); + auto& txn = contractCtx.built_txns[accSetIndex]; + BEAST_EXPECT(txn.isFieldPresent(sfSetFlag)); + BEAST_EXPECT(txn.getFieldU32(sfSetFlag) == flag); + } + } + } + + // Test error cases + + // Test adding field to non-existent transaction + { + Serializer s; + s.add32(123); + auto result = cfs.addTxnField(9999, sfDestinationTag, s.slice()); + BEAST_EXPECT(!result.has_value()); + // The implementation will likely throw or return an error + // when accessing out-of-bounds index + } + + // // Test adding invalid field for transaction type + // // Note: The current implementation checks against ttCONTRACT_CALL + // // format which might need adjustment for proper field validation + // { + // auto paymentResult = cfs.buildTxn(ttPAYMENT); + // if (paymentResult.has_value()) + // { + // uint32_t payIndex = paymentResult.value(); + + // // Try to add a field that doesn't belong to Payment + // // This test might need adjustment based on actual field + // // validation + // Serializer s; + // s.add32(100); + // // Using an obscure field that likely isn't in Payment format + // auto result = cfs.addTxnField(payIndex, sfTickSize, + // s.slice()); + // // The result depends on implementation's field validation + // BEAST_EXPECT(result.has_value()); + // } + // } + } + + void + testEmitBuiltTxn() + { + testcase("emitBuiltTxn"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const bob("bob"); + Account const carol("carol"); + Account const contract("contract"); + env.fund(XRP(10000), alice, bob, carol, contract); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto contractCtx = createContractContext(ac, contract, alice); + + ContractHostFunctionsImpl cfs(contractCtx); + + // Test emitting a valid Payment transaction + { + // Build a Payment transaction + auto buildResult = cfs.buildTxn(ttPAYMENT); + BEAST_EXPECT(buildResult.has_value()); + if (!buildResult.has_value()) + return; + + uint32_t const txIndex = buildResult.value(); + + // Add required fields for Payment + AccountID destAccount = bob.id(); + Blob destBuf; + destBuf.reserve(1 + destAccount.size()); + destBuf.push_back(0x14); // Type prefix for AccountID + destBuf.insert(destBuf.end(), destAccount.begin(), destAccount.end()); + auto destResult = + cfs.addTxnField(txIndex, sfDestination, Slice{destBuf.data(), destBuf.size()}); + BEAST_EXPECT(destResult.has_value()); + + // Add Amount field + STAmount const amount{XRP(100)}; + Serializer amtSerializer; + amount.add(amtSerializer); + auto amtResult = cfs.addTxnField(txIndex, sfAmount, amtSerializer.slice()); + BEAST_EXPECT(amtResult.has_value()); + + // Emit the transaction + auto emitResult = cfs.emitBuiltTxn(txIndex); + BEAST_EXPECT(emitResult.has_value()); + if (emitResult.has_value()) + { + // Check that the transaction was added to emitted transactions + BEAST_EXPECT(contractCtx.result.emittedTxns.size() == 1); + + // The result should be a TER code converted to int + int32_t const terCode = emitResult.value(); + // We expect a success code + BEAST_EXPECT(terCode == 0); + } + } + + // Test emitting multiple transactions + { + // Build first transaction - Payment + auto build1 = cfs.buildTxn(ttPAYMENT); + BEAST_EXPECT(build1.has_value()); + if (build1.has_value()) + { + uint32_t const tx1 = build1.value(); + + // Add fields for first payment + AccountID dest1 = alice.id(); + Blob dest1Buf; + dest1Buf.reserve(1 + dest1.size()); + dest1Buf.push_back(0x14); + dest1Buf.insert(dest1Buf.end(), dest1.begin(), dest1.end()); + auto const result = + cfs.addTxnField(tx1, sfDestination, Slice{dest1Buf.data(), dest1Buf.size()}); + BEAST_EXPECT(result.has_value()); + + STAmount const amt1{XRP(50)}; + Serializer amt1Ser; + amt1.add(amt1Ser); + [[maybe_unused]] auto const amt1Result = + cfs.addTxnField(tx1, sfAmount, amt1Ser.slice()); + } + + // Build second transaction - Another Payment + auto build2 = cfs.buildTxn(ttPAYMENT); + BEAST_EXPECT(build2.has_value()); + if (build2.has_value()) + { + uint32_t const tx2 = build2.value(); + + // Add fields for second payment + AccountID dest2 = carol.id(); + Blob dest2Buf; + dest2Buf.reserve(1 + dest2.size()); + dest2Buf.push_back(0x14); + dest2Buf.insert(dest2Buf.end(), dest2.begin(), dest2.end()); + auto const result = + cfs.addTxnField(tx2, sfDestination, Slice{dest2Buf.data(), dest2Buf.size()}); + BEAST_EXPECT(result.has_value()); + + STAmount const amt2{XRP(75)}; + Serializer amt2Ser; + amt2.add(amt2Ser); + [[maybe_unused]] auto const amt2Result = + cfs.addTxnField(tx2, sfAmount, amt2Ser.slice()); + } + + // Emit both transactions + if (build1.has_value()) + { + auto emit1 = cfs.emitBuiltTxn(build1.value()); + BEAST_EXPECT(emit1.has_value()); + } + + if (build2.has_value()) + { + auto emit2 = cfs.emitBuiltTxn(build2.value()); + BEAST_EXPECT(emit2.has_value()); + } + + // Check that both were added to emitted transactions + // (Note: actual count depends on previous test state) + BEAST_EXPECT(contractCtx.result.emittedTxns.size() >= 2); + } + + // // Test emitting transaction with invalid index + // { + // auto emitResult = cfs.emitBuiltTxn(9999); + // BEAST_EXPECT(!emitResult.has_value()); + // if (!emitResult.has_value()) + // { + // BEAST_EXPECT( + // emitResult.error() == + // HostFunctionError::INDEX_OUT_OF_BOUNDS); + // } + // } + + // // Test emitting AccountSet transaction + // { + // auto buildResult = cfs.buildTxn(ttACCOUNT_SET); + // BEAST_EXPECT(buildResult.has_value()); + // if (buildResult.has_value()) + // { + // uint32_t txIndex = buildResult.value(); + + // // Add optional fields for AccountSet + // uint32_t setFlag = 8; // asfRequireAuth + // Serializer flagSer; + // flagSer.add32(setFlag); + // auto flagResult = + // cfs.addTxnField(txIndex, sfSetFlag, flagSer.slice()); + // BEAST_EXPECT(flagResult.has_value()); + + // // Emit the transaction + // auto emitResult = cfs.emitBuiltTxn(txIndex); + // BEAST_EXPECT(emitResult.has_value()); + // } + // } + + // // Test emitting TrustSet transaction + // { + // auto buildResult = cfs.buildTxn(ttTRUST_SET); + // BEAST_EXPECT(buildResult.has_value()); + // if (buildResult.has_value()) + // { + // uint32_t txIndex = buildResult.value(); + + // // Add LimitAmount field (required for TrustSet) + // auto const USD = alice["USD"]; + // STAmount limit{USD.issue(), 1000}; + // Serializer limitSer; + // limit.add(limitSer); + // auto limitResult = + // cfs.addTxnField(txIndex, sfLimitAmount, + // limitSer.slice()); + // BEAST_EXPECT(limitResult.has_value()); + + // // Emit the transaction + // auto emitResult = cfs.emitBuiltTxn(txIndex); + // BEAST_EXPECT(emitResult.has_value()); + // } + // } + + // // Test emitting transaction without required fields + // { + // // Build a Payment but don't add required fields + // auto buildResult = cfs.buildTxn(ttPAYMENT); + // BEAST_EXPECT(buildResult.has_value()); + // if (buildResult.has_value()) + // { + // uint32_t txIndex = buildResult.value(); + + // // Try to emit without Destination and Amount + // auto emitResult = cfs.emitBuiltTxn(txIndex); + // BEAST_EXPECT(emitResult.has_value()); + // if (emitResult.has_value()) + // { + // // Should get an error code indicating missing fields + // int32_t terCode = emitResult.value(); + // // The transaction should fail validation + // BEAST_EXPECT(terCode != + // static_cast(tesSUCCESS)); + // } + // } + // } + + // // Test sequence of build and emit operations + // { + // auto initialEmittedCount = contractCtx.result.emittedTxns.size(); + + // // Build several transactions + // std::vector indices; + // for (int i = 0; i < 3; ++i) + // { + // auto buildResult = cfs.buildTxn(ttACCOUNT_SET); + // BEAST_EXPECT(buildResult.has_value()); + // if (buildResult.has_value()) + // { + // indices.push_back(buildResult.value()); + // } + // } + + // // Emit them in reverse order + // for (auto it = indices.rbegin(); it != indices.rend(); ++it) + // { + // auto emitResult = cfs.emitBuiltTxn(*it); + // BEAST_EXPECT(emitResult.has_value()); + // } + + // // Check that all were emitted + // BEAST_EXPECT( + // contractCtx.result.emittedTxns.size() == + // initialEmittedCount + indices.size()); + // } + } + + void + run() override + { + using namespace test::jtx; + // testInstanceParam(); + // testFunctionParam(); + // testContractDataFromKey(); + // testNestedContractDataFromKey(); + testNestedContractDataFromArrayKey(); + // testBuildTxn(); + // testAddTxnField(); + // testEmitBuiltTxn(); + } +}; + +BEAST_DEFINE_TESTSUITE(ContractHostFuncImpl, app, xrpl); + +} // namespace test +} // namespace xrpl diff --git a/src/test/app/Contract_test.cpp b/src/test/app/Contract_test.cpp new file mode 100644 index 00000000000..589e72d1f1a --- /dev/null +++ b/src/test/app/Contract_test.cpp @@ -0,0 +1,2029 @@ +#include +#include +#include + +#include +#include +#include + +namespace xrpl { +namespace test { + +class Contract_test : public beast::unit_test::suite +{ + struct TestLedgerData + { + int index; + std::string txType; + std::string result; + }; + + Json::Value + getLastLedger(jtx::Env& env) + { + Json::Value params; + params[jss::ledger_index] = env.closed()->seq(); + params[jss::transactions] = true; + params[jss::expand] = true; + return env.rpc("json", "ledger", to_string(params)); + } + + Json::Value + getTxByIndex(Json::Value const& jrr, int const index) + { + for (auto const& txn : jrr[jss::result][jss::ledger][jss::transactions]) + { + if (txn[jss::metaData][sfTransactionIndex.jsonName] == index) + return txn; + } + return {}; + } + + void + validateClosedLedger(jtx::Env& env, std::vector const& ledgerResults) + { + auto const jrr = getLastLedger(env); + auto const transactions = jrr[jss::result][jss::ledger][jss::transactions]; + BEAST_EXPECT(transactions.size() == ledgerResults.size()); + for (TestLedgerData const& ledgerResult : ledgerResults) + { + auto const txn = getTxByIndex(jrr, ledgerResult.index); + BEAST_EXPECT(txn.isMember(jss::metaData)); + Json::Value const meta = txn[jss::metaData]; + BEAST_EXPECT(txn[sfTransactionType.jsonName] == ledgerResult.txType); + BEAST_EXPECT(meta[sfTransactionResult.jsonName] == ledgerResult.result); + } + } + + static std::pair> + contractSourceKeyAndSle(ReadView const& view, uint256 const& contractHash) + { + auto const k = keylet::contractSource(contractHash); + return {k.key, view.read(k)}; + } + + static std::pair> + contractKeyAndSle( + ReadView const& view, + uint256 const& contractHash, + AccountID const& owner, + std::uint32_t const& seq) + { + auto const k = keylet::contract(contractHash, owner, seq); + return {k.key, view.read(k)}; + } + + Json::Value + getContractCreateTx(Json::Value const& jrr) + { + for (auto const& txn : jrr[jss::result][jss::ledger][jss::transactions]) + { + if (txn[jss::TransactionType] == jss::ContractCreate) + return txn; + } + return {}; + } + + uint256 + getContractHash(Blob const& wasmBytes) + { + return xrpl::sha512Half_s(xrpl::Slice(wasmBytes.data(), wasmBytes.size())); + } + + void + validateFunctions(std::shared_ptr const& sle, Json::Value const& functions) + { + auto const stored = sle->getFieldArray(sfFunctions); + BEAST_EXPECT(stored.size() == functions.size()); + for (std::size_t i = 0; i < stored.size(); ++i) + { + auto const sIPV = stored[i].getJson(JsonOptions::none); + auto const& eIPV = functions[i]["Function"]; + + // Compare function name. + BEAST_EXPECT(sIPV.isMember("FunctionName")); + BEAST_EXPECT(eIPV.isMember("FunctionName")); + BEAST_EXPECT(sIPV["FunctionName"].asString() == eIPV["FunctionName"].asString()); + + // Compare parameters if present. + if (eIPV.isMember("Parameters")) + { + BEAST_EXPECT(sIPV.isMember("Parameters")); + BEAST_EXPECT(sIPV["Parameters"].isArray()); + BEAST_EXPECT(eIPV["Parameters"].isArray()); + BEAST_EXPECT(sIPV["Parameters"].size() == eIPV["Parameters"].size()); + + for (std::size_t j = 0; j < sIPV["Parameters"].size(); ++j) + { + auto const& sParam = sIPV["Parameters"][j]; + auto const& eParam = eIPV["Parameters"][j]["Parameter"]; + + // Compare ParameterFlag if present. + if (sParam.isMember("ParameterFlag")) + { + BEAST_EXPECT(eParam.isMember("ParameterFlag")); + BEAST_EXPECT( + sParam["ParameterFlag"].asUInt() == eParam["ParameterFlag"].asUInt()); + } + + // Compare ParameterName if present. + if (sParam.isMember("ParameterName")) + { + BEAST_EXPECT(eParam.isMember("ParameterName")); + BEAST_EXPECT( + sParam["ParameterName"].asString() == + eParam["ParameterName"].asString()); + } + + // Compare ParameterType if present. + if (sParam.isMember("ParameterType")) + { + BEAST_EXPECT(eParam.isMember("ParameterType")); + BEAST_EXPECT( + sParam["ParameterType"]["type"].asString() == + eParam["ParameterType"]["type"].asString()); + } + } + } + } + } + + void + validateInstanceParams( + std::shared_ptr const& sle, + Json::Value const& instanceParamValues) + { + // Convert stored SLE array to JSON and compare against expected JSON. + auto const stored = sle->getFieldArray(sfInstanceParameterValues); + BEAST_EXPECT(stored.size() == instanceParamValues.size()); + + for (std::size_t i = 0; i < stored.size(); ++i) + { + // Convert the STObject entry to JSON for easy comparison. + auto const sIPV = stored[i].getJson(JsonOptions::none); + auto const& eIPV = instanceParamValues[i]["InstanceParameterValue"]; + + // Compare flag if present. + BEAST_EXPECT(sIPV.isMember("ParameterFlag")); + BEAST_EXPECT(eIPV.isMember("ParameterFlag")); + BEAST_EXPECT(sIPV["ParameterFlag"].asUInt() == eIPV["ParameterFlag"].asUInt()); + + // Compare ParameterValue contents (name/type/value) when present. + BEAST_EXPECT(sIPV.isMember("ParameterValue")); + BEAST_EXPECT(eIPV.isMember("ParameterValue")); + auto const& sPV = sIPV["ParameterValue"]; + auto const& ePV = eIPV["ParameterValue"]; + + if (ePV.isMember("name")) + BEAST_EXPECT( + sPV.isMember("name") && sPV["name"].asString() == ePV["name"].asString()); + + if (ePV.isMember("type")) + BEAST_EXPECT( + sPV.isMember("type") && sPV["type"].asString() == ePV["type"].asString()); + + if (ePV.isMember("value")) + { + // value can be number, string, or object; compare generically + BEAST_EXPECT(sPV.isMember("value")); + BEAST_EXPECT(sPV["value"] == ePV["value"]); + } + } + } + + void + validateInstanceParamValues( + std::shared_ptr const& sle, + Json::Value const& instanceParamValues) + { + // Convert stored SLE array to JSON and compare against expected JSON. + auto const stored = sle->getFieldArray(sfInstanceParameterValues); + BEAST_EXPECT(stored.size() == instanceParamValues.size()); + + for (std::size_t i = 0; i < stored.size(); ++i) + { + // Convert the STObject entry to JSON for easy comparison. + auto const sIPV = stored[i].getJson(JsonOptions::none); + auto const& eIPV = instanceParamValues[i]["InstanceParameterValue"]; + + // Compare flag if present. + BEAST_EXPECT(sIPV.isMember("ParameterFlag")); + BEAST_EXPECT(eIPV.isMember("ParameterFlag")); + BEAST_EXPECT(sIPV["ParameterFlag"].asUInt() == eIPV["ParameterFlag"].asUInt()); + + // Compare ParameterValue contents (name/type/value) when present. + BEAST_EXPECT(sIPV.isMember("ParameterValue")); + BEAST_EXPECT(eIPV.isMember("ParameterValue")); + auto const& sPV = sIPV["ParameterValue"]; + auto const& ePV = eIPV["ParameterValue"]; + + if (ePV.isMember("type")) + BEAST_EXPECT( + sPV.isMember("type") && sPV["type"].asString() == ePV["type"].asString()); + + if (ePV.isMember("value")) + { + // value can be number, string, or object; compare generically + BEAST_EXPECT(sPV.isMember("value")); + BEAST_EXPECT(sPV["value"] == ePV["value"]); + } + } + } + + void + validateContract( + jtx::Env& env, + Keylet const& k, + AccountID const& contractAccount, + AccountID const& owner, + std::uint32_t const& flags, + std::uint32_t const& seq, + uint256 const& contractHash, + std::optional const& instanceParamValues = std::nullopt, + std::optional const& uri = std::nullopt) + { + auto const sle = env.current()->read(k); + if (!sle) + { + fail(); + return; + } + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->getAccountID(sfContractAccount) == contractAccount); + BEAST_EXPECT(sle->getAccountID(sfOwner) == owner); + BEAST_EXPECT(sle->getFieldU32(sfFlags) == flags); + BEAST_EXPECT(sle->getFieldU32(sfSequence) == seq); + BEAST_EXPECT(sle->getFieldH256(sfContractHash) == contractHash); + // if (instanceParamValues) + // validateInstanceParamValues(sle, *instanceParamValues); + // if (uri) + // { + // std::cout << "URI: " << *uri << std::endl; + // BEAST_EXPECT(sle->getFieldVL(sfURI) == strUnHex(*uri)); + // } + } + + void + validateContractSource( + jtx::Env& env, + Blob const& wasmBytes, + uint256 const& contractHash, + std::uint64_t const& referenceCount, + Json::Value const& functions, + std::optional const& instanceParams = std::nullopt) + { + auto const [id, sle] = contractSourceKeyAndSle(*env.current(), contractHash); + BEAST_EXPECT(sle); + BEAST_EXPECT(sle->getFieldVL(sfContractCode) == wasmBytes); + BEAST_EXPECT(sle->getFieldH256(sfContractHash) == contractHash); + BEAST_EXPECT(sle->getFieldU64(sfReferenceCount) == referenceCount); + validateFunctions(sle, functions); + } + + template + std::tuple + setContract(jtx::Env& env, TER const& result, Args&&... args) + { + auto jt = env.jt(std::forward(args)...); + env(jt, jtx::ter(result)); + env.close(); + + // { + // Json::Value params; + // params[jss::ledger_index] = env.current()->seq() - 1; + // params[jss::transactions] = true; + // params[jss::expand] = true; + // auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + // } + + // if (jt.jv.isMember(sfContractHash.jsonName)) + // { + // auto const accountID = + // parseBase58(jt.jv[sfContractAccount].asString()); + // jtx::Account const contractAccount{ + // "Contract pseudo-account", + // *accountID}; + // return std::make_pair(contractAccount, + // uint256(jt.jv[sfContractHash])); + // } + + auto const wasmBytes = strUnHex(jt.jv[sfContractCode.jsonName].asString()); + // std::cout << "WASM Size: " << wasmBytes->size() << std::endl; + if (!wasmBytes || wasmBytes->empty()) + return std::make_tuple(jtx::Account{"invalid"}, uint256{}, jt.jv); + uint256 const contractHash = + xrpl::sha512Half_s(xrpl::Slice(wasmBytes->data(), wasmBytes->size())); + auto const accountID = parseBase58(jt.jv[sfAccount].asString()); + auto const [contractKey, sle] = contractKeyAndSle( + *env.current(), contractHash, *accountID, jt.jv[sfSequence.jsonName].asUInt()); + if (!sle) + return std::make_tuple(jtx::Account{"invalid"}, contractHash, jt.jv); + jtx::Account const contractAccount{ + "Contract pseudo-account", sle->getAccountID(sfContractAccount)}; + return std::make_tuple(contractAccount, contractHash, jt.jv); + } + + std::string const BaseContractWasm = + "0061736D01000000010E0260057F7F7F7F7F017F6000017F02120108686F73745F" + "6C696205747261636500000302010105030100110619037F01418080C0000B7F00" + "419E80C0000B7F0041A080C0000B072C04066D656D6F7279020004626173650001" + "0A5F5F646174615F656E6403010B5F5F686561705F6261736503020A6C016A0101" + "7F23808080800041206B2200248080808000200041186A410028009080C0800036" + "0200200041106A410029008880C080003703002000410029008080C08000370308" + "419480C08000410A200041086A411441011080808080001A200041206A24808080" + "800041000B0B270100418080C0000B1EAE123A8556F3CF91154711376AFB0F894F" + "832B3D20204163636F756E743A"; + + std::string const Base2ContractWasm = + "0061736D01000000010E0260057F7F7F7F7F017F6000017F02120108686F73745F6C69" + "6205747261636500000302010105030100110619037F01418080C0000B7F0041A380C0" + "000B7F0041B080C0000B072C04066D656D6F72790200046261736500010A5F5F646174" + "615F656E6403010B5F5F686561705F6261736503020A1B011900418080C08000412341" + "00410041001080808080001A41000B0B2C0100418080C0000B23242424242420535441" + "5254494E47204241534520455845435554494F4E202424242424"; + + void + testCreatePreflight(FeatureBitset features) + { + testcase("create preflight"); + + using namespace jtx; + + // temDISABLED: Feature not enabled + { + test::jtx::Env env{*this, features - featureSmartContract}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, BaseContractWasm), ter(temDISABLED)); + } + + // temINVALID_FLAG: tfContractMask is not allowed. + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, BaseContractWasm), + txflags(tfBurnable), + ter(temINVALID_FLAG)); + } + + // temMALFORMED: Neither ContractCode nor ContractHash present + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = alice.human(); + jv[jss::Fee] = to_string(XRP(10).value()); + // Missing both ContractCode and ContractHash + + env(jv, ter(temMALFORMED)); + } + + // temMALFORMED: Both ContractCode and ContractHash present + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = alice.human(); + jv[jss::Fee] = to_string(XRP(10).value()); + jv[sfContractCode.jsonName] = BaseContractWasm; + jv[sfContractHash.jsonName] = + "D955DAC2E77519F05AD151A5D3C99FC8125FB39D58FF9F106F1ACA4491902C" + "25"; + + env(jv, ter(temMALFORMED)); + } + + // temARRAY_EMPTY: ContractCode present but Functions missing + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = alice.human(); + jv[jss::Fee] = to_string(XRP(10).value()); + jv[sfContractCode.jsonName] = BaseContractWasm; + // Missing Functions array + + env(jv, ter(temARRAY_EMPTY)); + } + + // temARRAY_EMPTY: ContractCode present but Functions missing + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = alice.human(); + jv[jss::Fee] = to_string(XRP(10).value()); + jv[sfContractCode.jsonName] = BaseContractWasm; + jv[sfFunctions.jsonName] = Json::arrayValue; // Empty array + + env(jv, ter(temARRAY_EMPTY)); + } + + // temARRAY_TOO_LARGE: Functions array too large + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, BaseContractWasm), + contract::add_function("func1", {}), + contract::add_function("func2", {}), + contract::add_function("func3", {}), + contract::add_function("func4", {}), + contract::add_function("func5", {}), + contract::add_function("func6", {}), + contract::add_function("func7", {}), + contract::add_function("func8", {}), + contract::add_function("func9", {}), + contract::add_function("func10", {}), + contract::add_function("func11", {}), + contract::add_function("func12", {}), + contract::add_function("func13", {}), + ter(temARRAY_TOO_LARGE)); + } + + // temREDUNDANT: Duplicate function name + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, BaseContractWasm), + contract::add_function("test", {}), + contract::add_function("test", {}), // Duplicate + ter(temREDUNDANT)); + } + + // temARRAY_TOO_LARGE: Function Parameters array too large + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, BaseContractWasm), + contract::add_function( + "test", + { + {0, "param1", "UINT8"}, {0, "param2", "UINT8"}, + {0, "param3", "UINT8"}, {0, "param4", "UINT8"}, + {0, "param5", "UINT8"}, {0, "param6", "UINT8"}, + {0, "param7", "UINT8"}, {0, "param8", "UINT8"}, + {0, "param9", "UINT8"}, {0, "param10", "UINT8"}, + {0, "param11", "UINT8"}, {0, "param12", "UINT8"}, + {0, "param13", "UINT8"}, {0, "param14", "UINT8"}, + {0, "param15", "UINT8"}, {0, "param16", "UINT8"}, + {0, "param17", "UINT8"}, {0, "param18", "UINT8"}, + {0, "param19", "UINT8"}, {0, "param20", "UINT8"}, + {0, "param21", "UINT8"}, {0, "param22", "UINT8"}, + {0, "param23", "UINT8"}, {0, "param24", "UINT8"}, + {0, "param25", "UINT8"}, {0, "param26", "UINT8"}, + {0, "param27", "UINT8"}, {0, "param28", "UINT8"}, + {0, "param29", "UINT8"}, {0, "param30", "UINT8"}, + {0, "param31", "UINT8"}, {0, "param32", "UINT8"}, + {0, "param33", "UINT8"}, // 33rd parameter + }), + ter(temARRAY_TOO_LARGE)); + } + + // temMALFORMED: Function Parameter is missing flag + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = alice.human(); + jv[jss::Fee] = to_string(XRP(10).value()); + jv[sfContractCode.jsonName] = BaseContractWasm; + jv[sfFunctions.jsonName] = Json::arrayValue; + + Json::Value func; + func[sfFunction.jsonName][sfFunctionName.jsonName] = strHex(std::string("test")); + func[sfFunction.jsonName][sfParameters.jsonName] = Json::arrayValue; + + Json::Value param; + param[sfParameter.jsonName][sfParameterType.jsonName]["type"] = "UINT8"; + func[sfFunction.jsonName][sfParameters.jsonName].append(param); + + jv[sfFunctions.jsonName].append(func); + env(jv, ter(temMALFORMED)); + } + + // temMALFORMED: Function Parameter is missing type + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = alice.human(); + jv[jss::Fee] = to_string(XRP(10).value()); + jv[sfContractCode.jsonName] = BaseContractWasm; + jv[sfFunctions.jsonName] = Json::arrayValue; + + Json::Value func; + func[sfFunction.jsonName][sfFunctionName.jsonName] = strHex(std::string("test")); + func[sfFunction.jsonName][sfParameters.jsonName] = Json::arrayValue; + + Json::Value param; + param[sfParameter.jsonName][sfParameterFlag.jsonName] = 0; + // Missing sfParameterType + func[sfFunction.jsonName][sfParameters.jsonName].append(param); + + jv[sfFunctions.jsonName].append(func); + env(jv, ter(temMALFORMED)); + } + + // temINVALID_FLAG: Invalid parameter flag in Function. + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, BaseContractWasm), + contract::add_function("test", {{0xFF000000, "param", "UINT8"}}), // Invalid flag + ter(temINVALID_FLAG)); + } + + // temARRAY_EMPTY: InstanceParameters empty array + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = alice.human(); + jv[jss::Fee] = to_string(XRP(10).value()); + jv[sfContractCode.jsonName] = BaseContractWasm; + jv[sfFunctions.jsonName] = Json::arrayValue; + Json::Value func; + func[sfFunction.jsonName][sfFunctionName.jsonName] = strHex(std::string("test")); + jv[sfFunctions.jsonName].append(func); + jv[sfInstanceParameters.jsonName] = Json::arrayValue; // Empty array + + env(jv, ter(temARRAY_EMPTY)); + } + + // temARRAY_TOO_LARGE: InstanceParameters array is too large + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, BaseContractWasm), + contract::add_function("test", {}), + contract::add_instance_param(0, "param1", "UINT8", 1), + contract::add_instance_param(0, "param2", "UINT8", 2), + contract::add_instance_param(0, "param3", "UINT8", 3), + contract::add_instance_param(0, "param4", "UINT8", 4), + contract::add_instance_param(0, "param5", "UINT8", 5), + contract::add_instance_param(0, "param6", "UINT8", 6), + contract::add_instance_param(0, "param7", "UINT8", 7), + contract::add_instance_param(0, "param8", "UINT8", 8), + contract::add_instance_param(0, "param9", "UINT8", 9), + contract::add_instance_param(0, "param10", "UINT8", 10), + contract::add_instance_param(0, "param11", "UINT8", 11), + contract::add_instance_param(0, "param12", "UINT8", 12), + contract::add_instance_param(0, "param13", "UINT8", 13), + contract::add_instance_param(0, "param14", "UINT8", 14), + contract::add_instance_param(0, "param15", "UINT8", 15), + contract::add_instance_param(0, "param16", "UINT8", 16), + contract::add_instance_param(0, "param17", "UINT8", 17), + contract::add_instance_param(0, "param18", "UINT8", 18), + contract::add_instance_param(0, "param19", "UINT8", 19), + contract::add_instance_param(0, "param20", "UINT8", 20), + contract::add_instance_param(0, "param21", "UINT8", 21), + contract::add_instance_param(0, "param22", "UINT8", 22), + contract::add_instance_param(0, "param23", "UINT8", 23), + contract::add_instance_param(0, "param24", "UINT8", 24), + contract::add_instance_param(0, "param25", "UINT8", 25), + contract::add_instance_param(0, "param26", "UINT8", 26), + contract::add_instance_param(0, "param27", "UINT8", 27), + contract::add_instance_param(0, "param28", "UINT8", 28), + contract::add_instance_param(0, "param29", "UINT8", 29), + contract::add_instance_param(0, "param30", "UINT8", 30), + contract::add_instance_param(0, "param31", "UINT8", 31), + contract::add_instance_param(0, "param32", "UINT8", 32), + contract::add_instance_param(0, "param33", "UINT8", 33), + ter(temARRAY_TOO_LARGE)); + } + + // temARRAY_EMPTY: InstanceParameterValues is missing + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = alice.human(); + jv[jss::Fee] = to_string(XRP(10).value()); + jv[sfContractCode.jsonName] = BaseContractWasm; + jv[sfFunctions.jsonName] = Json::arrayValue; + Json::Value func; + func[sfFunction.jsonName][sfFunctionName.jsonName] = strHex(std::string("test")); + jv[sfFunctions.jsonName].append(func); + jv[sfInstanceParameterValues.jsonName] = Json::arrayValue; // Empty array + + env(jv, ter(temARRAY_EMPTY)); + } + + // // Test 18: InstanceParameterValues array is too large. + // { + // test::jtx::Env env{*this, features}; + + // auto const alice = Account{"alice"}; + // env.fund(XRP(10'000), alice); + // env.close(); + + // Json::Value jv; + // jv[jss::TransactionType] = jss::ContractCreate; + // jv[jss::Account] = alice.human(); + // jv[jss::Fee] = to_string(XRP(10).value()); + // jv[sfContractCode.jsonName] = BaseContractWasm; + // jv[sfFunctions.jsonName] = Json::arrayValue; + // Json::Value func; + // func[sfFunction.jsonName][sfFunctionName.jsonName] = "test"; + // func[sfFunction.jsonName][sfParameters.jsonName] = + // Json::arrayValue; jv[sfFunctions.jsonName].append(func); + // jv[sfInstanceParameterValues.jsonName] = Json::arrayValue; + + // // Add more than maxContractParams + // for (int i = 0; i < 257; ++i) + // { + // Json::Value param; + // param[sfInstanceParameterValue.jsonName] + // [sfParameterFlag.jsonName] = 0; + // param[sfInstanceParameterValue.jsonName] + // [sfParameterValue.jsonName]["type"] = "UINT8"; + // param[sfInstanceParameterValue.jsonName] + // [sfParameterValue.jsonName]["value"] = i; + // jv[sfInstanceParameterValues.jsonName].append(param); + // } + + // env(jv, ter(temARRAY_TOO_LARGE)); + // } + + // // Test 19: InstanceParameterValue missing flag + // { + // test::jtx::Env env{*this, features}; + + // auto const alice = Account{"alice"}; + // env.fund(XRP(10'000), alice); + // env.close(); + + // Json::Value jv; + // jv[jss::TransactionType] = jss::ContractCreate; + // jv[jss::Account] = alice.human(); + // jv[jss::Fee] = to_string(XRP(10).value()); + // jv[sfContractCode.jsonName] = BaseContractWasm; + // jv[sfFunctions.jsonName] = Json::arrayValue; + // Json::Value func; + // func[sfFunction.jsonName][sfFunctionName.jsonName] = "test"; + // func[sfFunction.jsonName][sfParameters.jsonName] = + // Json::arrayValue; jv[sfFunctions.jsonName].append(func); + // jv[sfInstanceParameterValues.jsonName] = Json::arrayValue; + + // Json::Value param; + // // Missing sfParameterFlag + // param[sfInstanceParameterValue.jsonName][sfParameterValue.jsonName] + // ["type"] = "UINT8"; + // param[sfInstanceParameterValue.jsonName][sfParameterValue.jsonName] + // ["value"] = 1; + // jv[sfInstanceParameterValues.jsonName].append(param); + + // env(jv, ter(temMALFORMED)); + // } + + // // Test 20: InstanceParameterValue missing value + // { + // test::jtx::Env env{*this, features}; + + // auto const alice = Account{"alice"}; + // env.fund(XRP(10'000), alice); + // env.close(); + + // Json::Value jv; + // jv[jss::TransactionType] = jss::ContractCreate; + // jv[jss::Account] = alice.human(); + // jv[jss::Fee] = to_string(XRP(10).value()); + // jv[sfContractCode.jsonName] = BaseContractWasm; + // jv[sfFunctions.jsonName] = Json::arrayValue; + // Json::Value func; + // func[sfFunction.jsonName][sfFunctionName.jsonName] = "test"; + // func[sfFunction.jsonName][sfParameters.jsonName] = + // Json::arrayValue; jv[sfFunctions.jsonName].append(func); + // jv[sfInstanceParameterValues.jsonName] = Json::arrayValue; + + // Json::Value param; + // param[sfInstanceParameterValue.jsonName][sfParameterFlag.jsonName] + // = + // 0; + // // Missing sfParameterValue + // jv[sfInstanceParameterValues.jsonName].append(param); + + // env(jv, ter(temMALFORMED)); + // } + + // // Test 21: InstanceParameterValue invalid flag + // { + // test::jtx::Env env{*this, features}; + + // auto const alice = Account{"alice"}; + // env.fund(XRP(10'000), alice); + // env.close(); + + // env(contract::create(alice, BaseContractWasm), + // contract::add_function("test", {}), + // contract::add_instance_param( + // 0xFF000000, "param", "UINT8", 1), // Invalid flag + // ter(temINVALID_FLAG)); + // } + + // // Test 22: Success - ContractCode with Functions + // { + // test::jtx::Env env{*this, features}; + + // auto const alice = Account{"alice"}; + // env.fund(XRP(10'000), alice); + // env.close(); + + // env(contract::create(alice, BaseContractWasm), + // contract::add_function("base", {}), + // fee(XRP(200)), + // ter(tesSUCCESS)); + // } + + // // Test 23: Success - ContractCode with Functions and parameters + // { + // test::jtx::Env env{*this, features}; + + // auto const alice = Account{"alice"}; + // env.fund(XRP(10'000), alice); + // env.close(); + + // env(contract::create(alice, BaseContractWasm), + // contract::add_function( + // "base", {{0, "param1", "UINT8"}, {0, "param2", + // "UINT32"}}), + // fee(XRP(200)), + // ter(tesSUCCESS)); + // } + + // // Test 24: Success - with InstanceParameters and + // // InstanceParameterValues + // { + // test::jtx::Env env{*this, features}; + + // auto const alice = Account{"alice"}; + // env.fund(XRP(10'000), alice); + // env.close(); + + // env(contract::create(alice, BaseContractWasm), + // contract::add_instance_param(0, "uint8", "UINT8", 1), + // contract::add_instance_param(0, "uint32", "UINT32", 100), + // contract::add_function("base", {}), + // fee(XRP(200)), + // ter(tesSUCCESS)); + // } + } + + void + testCreatePreclaim(FeatureBitset features) + { + testcase("create preclaim"); + + using namespace jtx; + + // temMALFORMED: ContractHash provided but no corresponding + // ContractSource exists + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create( + alice, + uint256{"D955DAC2E77519F05AD151A5D3C99FC8125FB39D58FF9F106F" + "1ACA4491902C25"}), + fee(XRP(200)), + ter(temMALFORMED)); + } + + // temMALFORMED: ContractCode provided is empty + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, ""), // Empty code + contract::add_function("test", {}), + fee(XRP(200)), + ter(temMALFORMED)); + } + + // tesSUCCESS: ContractCode provided, ContractSource doesn't exist yet + // (first create) + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, BaseContractWasm), + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + + // TODO: Validate + } + + // tesSUCCESS: ContractCode provided, ContractSource already exists + // (install) + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + // First create + env(contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + env.close(); + + // Second create with same code (install) + env(contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 2), // Different value + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + + // TODO: Validate + } + + // tesSUCCESS: ContractHash provided with valid ContractSource + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + // First create to establish ContractSource + env(contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + env.close(); + + // Get the hash of the contract + auto const wasmBytes = strUnHex(BaseContractWasm); + uint256 const contractHash = getContractHash(*wasmBytes); + + // Install using ContractHash + env(contract::create(alice, contractHash), + contract::add_instance_param(0, "uint8", "UINT8", 2), + fee(XRP(200)), + ter(tesSUCCESS)); + + // TODO: Validate + } + + // temMALFORMED: Install with InstanceParameterValues that don't + // match + // ContractSource + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + // First create with specific InstanceParameters + env(contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_instance_param(0, "uint32", "UINT32", 100), + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + env.close(); + + // Get the hash + auto const wasmBytes = strUnHex(BaseContractWasm); + uint256 const contractHash = getContractHash(*wasmBytes); + + // Try to install with mismatched InstanceParameterValues + // Only providing one parameter when ContractSource expects two + env(contract::create(alice, contractHash), + contract::add_instance_param(0, "uint8", "UINT8", 2), + fee(XRP(200)), + ter(temMALFORMED)); + } + + // temMALFORMED: ContractHash provided but ContractSource doesn't + // exist + // (should fail in preclaim) + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + // Use Base2ContractWasm hash which hasn't been created yet + auto const wasmBytes = strUnHex(Base2ContractWasm); + uint256 const contractHash = getContractHash(*wasmBytes); + + env(contract::create(alice, contractHash), fee(XRP(200)), ter(temMALFORMED)); + } + + // tesSUCCESS: ContractCode with InstanceParameters for first + // creation + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::create(alice, Base2ContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 255), + contract::add_instance_param(tfSendAmount, "amount", "AMOUNT", XRP(100)), + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + } + + // tesSUCCESS: Multiple installs of same contract + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10'000), alice, bob); + env.close(); + + // Alice creates first instance + env(contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + env.close(); + + // Bob installs same contract + env(contract::create(bob, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 2), + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + env.close(); + + // Alice installs another instance + env(contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 3), + contract::add_function("base", {}), + fee(XRP(200)), + ter(tesSUCCESS)); + } + } + + void + testCreateDoApply(FeatureBitset features) + { + testcase("create doApply"); + + using namespace jtx; + + //---------------------------------------------------------------------- + // doApply.ContractCode.tesSUCCESS + + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + // auto const seq = env.current()->seq(); + auto const [contractAccount, contractHash, jv] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + token::uri("https://example.com/contract"), + fee(XRP(200))); + + // validate contract + // validateContract( + // env, + // contractAccount.id(), + // alice.id(), + // 0, + // seq, + // contractHash, + // jv[sfInstanceParameterValues], + // to_string(jv[sfURI])); + + // validate contract source + // validateContractSource( + // env, *wasmBytes, contractHash, 1, jv[sfFunctions]); + } + + //---------------------------------------------------------------------- + // doApply.ContractHash.tesSUCCESS + + { + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + // auto const wasmBytes = strUnHex(BaseContractWasm); + // uint256 const contractHash = getContractHash(*wasmBytes); + + // Create Contract. + { + // auto const seq = env.current()->seq(); + auto const [contractAccount, contractHash, jv] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + // validate contract + // validateContract( + // env, + // contractAccount.id(), + // alice.id(), + // 0, + // seq, + // contractHash, + // jv[sfInstanceParameterValues]); + + // validate contract source + // validateContractSource( + // env, *wasmBytes, contractHash, 1, jv[sfFunctions]); + } + + // Install Contract. + { + // auto const seq = env.current()->seq(); + auto const [contractAccount, contractHash, jv] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + // validate contract + // validateContract( + // env, + // contractAccount.id(), + // alice.id(), + // 0, + // seq, + // contractHash, + // jv[sfInstanceParameterValues]); + + // validate contract source + // validateContractSource( + // env, *wasmBytes, contractHash, 2, jv[sfFunctions]); + } + } + } + + void + testModifyDoApply(FeatureBitset features) + { + testcase("modify doApply"); + + using namespace jtx; + + //---------------------------------------------------------------------- + // doApply.ContractCode.tesSUCCESS + + { + test::jtx::Env env{*this, features}; + + jtx::Account const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + // Create initial contract + auto const seq = env.seq(alice); + auto const [contractAccount, contractHash, jv] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + // Modify contract + auto jt = env.jt( + contract::modify(alice, contractAccount, Base2ContractWasm), + contract::add_instance_param(0, "uint16", "UINT16", 1), + contract::add_function("base", {{0, "uint16", "UINT16"}}), + fee(XRP(200))); + env(jt, ter(tesSUCCESS)); + env.close(); + + // old contract source is deleted + auto const [sourceKey, sourceSle] = + contractSourceKeyAndSle(*env.current(), contractHash); + BEAST_EXPECT(!sourceSle); + + // new contract source exists + auto const wasmBytes = strUnHex(Base2ContractWasm); + uint256 const newContractHash = + xrpl::sha512Half_s(xrpl::Slice(wasmBytes->data(), wasmBytes->size())); + auto const [contractKey, contractSle] = + contractSourceKeyAndSle(*env.current(), newContractHash); + BEAST_EXPECT(contractSle); + + // validate modified contract + auto const k = keylet::contract(contractHash, alice, seq); + validateContract( + env, + k, + contractAccount.id(), + alice.id(), + 0, + seq, + newContractHash, + jt.jv[sfInstanceParameterValues]); + } + + //---------------------------------------------------------------------- + // doApply.ContractHash.tesSUCCESS + + { + test::jtx::Env env{*this, features}; + + jtx::Account const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + // Create initial contract + // auto const seq = env.seq(alice); + auto const [contractAccount, contractHash, jv] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + auto const [contractAccount2, contractHash2, jv2] = setContract( + env, + tesSUCCESS, + contract::create(alice, Base2ContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + // Modify contract + auto jt = env.jt( + contract::modify(alice, contractAccount, contractHash2), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + env(jt, ter(tesSUCCESS)); + env.close(); + + // old contract source is deleted + auto const [oldSourceKey, oldSourceSle] = + contractSourceKeyAndSle(*env.current(), contractHash); + BEAST_EXPECT(!oldSourceSle); + + // new contract source exists + auto const [sourceKey, sourceSle] = + contractSourceKeyAndSle(*env.current(), contractHash2); + BEAST_EXPECT(sourceSle); + BEAST_EXPECT(sourceSle->getFieldU64(sfReferenceCount) == 2); + + // // validate modified contract + // auto const k = keylet::contract(contractHash, seq); + // validateContract( + // env, + // k, + // contractAccount.id(), + // alice.id(), + // 0, + // seq, + // newContractHash, + // jt.jv[sfInstanceParameterValues]); + } + + //---------------------------------------------------------------------- + // doApply.ContractOwner.tesSUCCESS + + { + test::jtx::Env env{*this, features}; + + jtx::Account const alice = Account{"alice"}; + jtx::Account const bob = Account{"bob"}; + env.fund(XRP(10'000), alice, bob); + env.close(); + + // Create initial contract + auto const seq = env.seq(alice); + auto const [contractAccount, contractHash, jv] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + // Modify contract + auto jt = env.jt(contract::modify(alice, contractAccount, bob), fee(XRP(200))); + env(jt, ter(tesSUCCESS)); + env.close(); + + // validate modified contract + auto const k = keylet::contract(contractHash, alice, seq); + validateContract( + env, + k, + contractAccount.id(), + bob.id(), + 0, + seq, + contractHash, + jv[sfInstanceParameterValues]); + } + } + + void + testDeleteDoApply(FeatureBitset features) + { + testcase("delete doApply"); + + using namespace jtx; + + //------------------------------------------------------------------------- + // doApply.tesSUCCESS - Single Reference + { + test::jtx::Env env{*this, features}; + + jtx::Account const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + auto const seq = env.current()->seq(); + auto const [contractAccount, contractHash, jv] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + env(contract::del(alice, contractAccount), ter(tesSUCCESS)); + env.close(); + + // Pseudo Account is deleted + auto const pseudoAccountKey = keylet::account(contractAccount); + BEAST_EXPECT(!env.le(pseudoAccountKey)); + + // Contract instance is deleted + auto const wasmBytes = strUnHex(BaseContractWasm); + auto const contractKey = keylet::contract(contractHash, alice, seq); + BEAST_EXPECT(!env.le(contractKey)); + + // ContractSource is deleted - because it had a single reference + auto const contractSourceKey = keylet::contractSource(contractHash); + BEAST_EXPECT(!env.le(contractSourceKey)); + } + + // doApply.tesSUCCESS - Multiple Reference + { + test::jtx::Env env{*this, features}; + + jtx::Account const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + auto const seq = env.current()->seq(); + auto const [contractAccount, contractHash, jv] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + auto const seq2 = env.current()->seq(); + auto const [contractAccount2, contractHash2, jv2] = setContract( + env, + tesSUCCESS, + contract::create(alice, BaseContractWasm), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function("base", {{0, "uint8", "UINT8"}}), + fee(XRP(200))); + + env(contract::del(alice, contractAccount), ter(tesSUCCESS)); + env.close(); + + // Pseudo Account is deleted + auto const pseudoAccountKey = keylet::account(contractAccount); + BEAST_EXPECT(!env.le(pseudoAccountKey)); + + // Contract instance is deleted + auto const wasmBytes = strUnHex(BaseContractWasm); + auto const contractKey = keylet::contract(contractHash, alice, seq); + BEAST_EXPECT(!env.le(contractKey)); + + // Ensure ContractSource still exists + auto const contractSourceKey = keylet::contractSource(contractHash); + BEAST_EXPECT(env.le(contractSourceKey)); + BEAST_EXPECT(env.le(contractSourceKey)->getFieldU64(sfReferenceCount) == 1); + + // Pseudo Account of second instance still exists + auto const pseudoAccountKey2 = keylet::account(contractAccount2); + BEAST_EXPECT(env.le(pseudoAccountKey2)); + + // Ensure second contract instance still exists + auto const contractKey2 = keylet::contract(contractHash2, alice, seq2); + BEAST_EXPECT(env.le(contractKey2)); + } + } + + void + testUserDeletePreflight(FeatureBitset features) + { + testcase("user delete preflight"); + + using namespace jtx; + + // temDISABLED: Feature not enabled + { + test::jtx::Env env{*this, features - featureSmartContract}; + + auto const alice = Account{"alice"}; + env.fund(XRP(10'000), alice); + env.close(); + + env(contract::userDelete(alice, BaseContractWasm), ter(temDISABLED)); + } + } + + std::string + loadContractWasmStr(std::string const& contract_name = "") + { + std::string const& dir = "e2e-tests"; + std::string const name = "/Users/darkmatter/projects/ledger-works/xrpl-wasm-std/" + dir + + "/" + contract_name + "/target/wasm32v1-none/release/" + contract_name + ".wasm"; + if (!boost::filesystem::exists(name)) + { + std::cout << "File does not exist: " << name << "\n"; + return ""; + } + + std::ifstream file(name, std::ios::binary); + + if (!file) + { + std::cout << "Failed to open file: " << name << "\n"; + return ""; + } + + // Read the file into a vector + std::vector const buffer( + (std::istreambuf_iterator(file)), std::istreambuf_iterator()); + + // Check if the buffer is empty + if (buffer.empty()) + { + std::cout << "File is empty or could not be read properly.\n"; + return ""; + } + + return strHex(buffer); + } + + void + testContractDataSimple(FeatureBitset features) + { + testcase("contract data simple"); + + using namespace jtx; + + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10'000), alice, bob); + env.close(); + + std::string const contractDataWasmHex = loadContractWasmStr("contract_data"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, contractDataWasmHex), + contract::add_instance_param(tfSendAmount, "value", "AMOUNT", XRP(2000)), + contract::add_function("object_simple_create", {}), + contract::add_function("object_simple_update", {}), + fee(XRP(2000))); + + env(contract::call(alice, contractAccount, "object_simple_create"), + escrow::comp_allowance(1'000'000), + ter(tesSUCCESS)); + env.close(); + + env(contract::call(alice, contractAccount, "object_simple_update"), + escrow::comp_allowance(1'000'000), + ter(tesSUCCESS)); + env.close(); + } + + void + testContractDataNested(FeatureBitset features) + { + testcase("contract data nested"); + + using namespace jtx; + + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10'000), alice, bob); + env.close(); + + std::string const contractDataWasmHex = loadContractWasmStr("contract_data"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, contractDataWasmHex), + contract::add_instance_param(tfSendAmount, "value", "AMOUNT", XRP(2000)), + contract::add_function("object_nested_create", {}), + contract::add_function("object_nested_update", {}), + fee(XRP(2000))); + + env(contract::call(alice, contractAccount, "object_nested_create"), + escrow::comp_allowance(1'000'000), + ter(tesSUCCESS)); + env.close(); + + env(contract::call(alice, contractAccount, "object_nested_update"), + escrow::comp_allowance(1'000'000), + ter(tesSUCCESS)); + env.close(); + } + + void + testContractDataArray(FeatureBitset features) + { + testcase("contract data array"); + + using namespace jtx; + + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10'000), alice, bob); + env.close(); + + std::string const contractDataWasmHex = loadContractWasmStr("contract_data"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, contractDataWasmHex), + contract::add_instance_param(tfSendAmount, "value", "AMOUNT", XRP(2000)), + contract::add_function("object_with_arrays_create", {}), + contract::add_function("object_with_arrays_update", {}), + fee(XRP(2000))); + + env(contract::call(alice, contractAccount, "object_with_arrays_create"), + escrow::comp_allowance(1'000'000), + ter(tesSUCCESS)); + env.close(); + + env(contract::call(alice, contractAccount, "object_with_arrays_update"), + escrow::comp_allowance(1'000'000), + ter(tesSUCCESS)); + env.close(); + } + + void + testContractDataNestedArray(FeatureBitset features) + { + testcase("contract data nested array"); + + using namespace jtx; + + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10'000), alice, bob); + env.close(); + + std::string const contractDataWasmHex = loadContractWasmStr("contract_data"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, contractDataWasmHex), + contract::add_instance_param(tfSendAmount, "value", "AMOUNT", XRP(2000)), + contract::add_function("object_with_nested_arrays_create", {}), + contract::add_function("object_with_nested_arrays_update", {}), + fee(XRP(2000))); + + env(contract::call(alice, contractAccount, "object_with_nested_arrays_create"), + escrow::comp_allowance(1'000'000), + ter(tesSUCCESS)); + env.close(); + + env(contract::call(alice, contractAccount, "object_with_nested_arrays_update"), + escrow::comp_allowance(1'000'000), + ter(tesSUCCESS)); + env.close(); + } + + void + testInstanceParameters(FeatureBitset features) + { + testcase("instance parameters"); + + using namespace jtx; + + Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const gw = Account{"gw"}; + auto const USD = gw["USD"]; + env.fund(XRP(10'000), alice, bob); + env.close(); + + // Test Instance Parameter (1 of 2) + // uint8, uint16, uint32, uint64, uint128, uint160, uint192, uint256 + { + std::string const wasmHex = loadContractWasmStr("instance_params_uint"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, wasmHex), + contract::add_instance_param(0, "uint8", "UINT8", 255), + contract::add_instance_param(0, "uint16", "UINT16", 65535), + contract::add_instance_param( + 0, "uint32", "UINT32", static_cast(4294967295)), + contract::add_instance_param(0, "uint64", "UINT64", "FFFFFFFFFFFFFFFF"), + contract::add_instance_param( + 0, "uint128", "UINT128", "00000000000000000000000000000001"), + contract::add_instance_param( + 0, "uint160", "UINT160", "0000000000000000000000000000000000000001"), + contract::add_instance_param( + 0, "uint192", "UINT192", "000000000000000000000000000000000000000000000001"), + contract::add_instance_param( + 0, + "uint256", + "UINT256", + "D955DAC2E77519F05AD151A5D3C99FC8125FB39D58FF9F106F1ACA4491" + "902C" + "25"), + contract::add_function("instance_params_uint", {}), + fee(XRP(200))); + + // { + // Json::Value params; + // params[jss::ledger_index] = env.current()->seq() - 1; + // params[jss::transactions] = true; + // params[jss::expand] = true; + // auto const jrr = env.rpc("json", "ledger", + // to_string(params)); std::cout << jrr << std::endl; + // } + + env(contract::call(alice, contractAccount, "instance_params_uint"), + escrow::comp_allowance(1000000), + ter(tesSUCCESS)); + env.close(); + } + + { + // Test Instance Parameter (2 of 2) + // vl, account, amount (XRP), amount (IOU), number, currency, issue + std::string const wasmHex = loadContractWasmStr("instance_params_other"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, wasmHex), + contract::add_instance_param(0, "vl", "VL", "DEADBEEF"), + contract::add_instance_param(0, "account", "ACCOUNT", alice.human()), + contract::add_instance_param( + 0, "amountXRP", "AMOUNT", XRP(1).value().getJson(JsonOptions::none)), + contract::add_instance_param( + 0, "amountIOU", "AMOUNT", USD(1.2).value().getJson(JsonOptions::none)), + contract::add_instance_param(0, "number", "NUMBER", "1.2"), + contract::add_function("instance_params_other", {}), + fee(XRP(200))); + + // { + // Json::Value params; + // params[jss::ledger_index] = env.current()->seq() - 1; + // params[jss::transactions] = true; + // params[jss::expand] = true; + // auto const jrr = env.rpc("json", "ledger", + // to_string(params)); std::cout << jrr << std::endl; + // } + + env(contract::call(alice, contractAccount, "instance_params_other"), + escrow::comp_allowance(1000000), + ter(tesSUCCESS)); + env.close(); + } + } + + void + testFunctionParameters(FeatureBitset features) + { + testcase("function parameters"); + + using namespace jtx; + + Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const gw = Account{"gw"}; + auto const USD = gw["USD"]; + env.fund(XRP(10'000), alice, bob); + env.close(); + + std::string const wasmHex = loadContractWasmStr("function_params"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, wasmHex), + contract::add_instance_param(tfSendAmount, "amount", "AMOUNT", XRP(2000)), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function( + "function_params_uint", + { + {0, "uint8", "UINT8"}, + {0, "uint16", "UINT16"}, + {0, "uint32", "UINT32"}, + {0, "uint64", "UINT64"}, + {0, "uint128", "UINT128"}, + {0, "uint160", "UINT160"}, + {0, "uint192", "UINT192"}, + {0, "uint256", "UINT256"}, + }), + contract::add_function( + "function_params_other", + { + {0, "vl", "VL"}, + {0, "account", "ACCOUNT"}, + {0, "amountXRP", "AMOUNT"}, + {0, "amountIOU", "AMOUNT"}, + {0, "number", "NUMBER"}, + // {0, "issue", "ISSUE"}, + // {0, "currency", "CURRENCY"} + }), + fee(XRP(200))); + + // { + // Json::Value params; + // params[jss::ledger_index] = env.current()->seq() - 1; + // params[jss::transactions] = true; + // params[jss::expand] = true; + // auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + // } + + env(contract::call(alice, contractAccount, "function_params_uint"), + escrow::comp_allowance(1000000), + contract::add_param(0, "uint8", "UINT8", 255), + contract::add_param(0, "uint16", "UINT16", 65535), + contract::add_param(0, "uint32", "UINT32", static_cast(4294967295)), + contract::add_param(0, "uint64", "UINT64", "FFFFFFFFFFFFFFFF"), + contract::add_param(0, "uint128", "UINT128", "00000000000000000000000000000001"), + contract::add_param( + 0, "uint160", "UINT160", "0000000000000000000000000000000000000001"), + contract::add_param( + 0, "uint192", "UINT192", "000000000000000000000000000000000000000000000001"), + contract::add_param( + 0, + "uint256", + "UINT256", + "D955DAC2E77519F05AD151A5D3C99FC8125FB39D58FF9F106F1ACA4491902C" + "25"), + ter(tesSUCCESS)); + + env(contract::call(alice, contractAccount, "function_params_other"), + escrow::comp_allowance(1000000), + contract::add_param(0, "vl", "VL", "DEADBEEF"), + contract::add_param(0, "account", "ACCOUNT", alice.human()), + contract::add_param( + 0, "amountXRP", "AMOUNT", XRP(1).value().getJson(JsonOptions::none)), + contract::add_param( + 0, "amountIOU", "AMOUNT", USD(1.2).value().getJson(JsonOptions::none)), + contract::add_param(0, "number", "NUMBER", "1.2"), + // contract::add_param(0, "issue", "ISSUE", + // to_json(USD(1).value().issue())), contract::add_param(0, + // "currency", "CURRENCY", "USD"), + ter(tesSUCCESS)); + env.close(); + } + + void + testEmit(FeatureBitset features) + { + testcase("emit"); + + using namespace jtx; + + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10'000), alice, bob); + env.close(); + + std::string const emitTxWasmHex = loadContractWasmStr("emit_txn"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, emitTxWasmHex), + contract::add_instance_param(tfSendAmount, "value", "AMOUNT", XRP(2000)), + contract::add_function("emit", {}), + fee(XRP(200))); + + // { + // Json::Value params; + // params[jss::ledger_index] = env.current()->seq() - 1; + // params[jss::transactions] = true; + // params[jss::expand] = true; + // auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + // } + + env(contract::call(alice, contractAccount, "emit"), + escrow::comp_allowance(1000000), + ter(tesSUCCESS)); + env.close(); + } + + void + testEvents(FeatureBitset features) + { + testcase("events"); + + using namespace std::chrono_literals; + using namespace jtx; + + test::jtx::Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const gw = Account{"gw"}; + auto const USD = gw["USD"]; + env.fund(XRP(10'000), alice, bob); + env.close(); + + auto wsc = makeWSClient(env.app().config()); + Json::Value stream; + + { + // RPC subscribe to contract events stream + stream[jss::streams] = Json::arrayValue; + stream[jss::streams].append("contract_events"); + auto jv = wsc->invoke("subscribe", stream); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } + BEAST_EXPECT(jv[jss::result][jss::status] == "success"); + } + + std::string const eventsWasmHex = loadContractWasmStr("events"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, eventsWasmHex), + contract::add_instance_param(tfSendAmount, "amount", "AMOUNT", XRP(2000)), + contract::add_function("events", {}), + fee(XRP(200))); + + // { + // Json::Value params; + // params[jss::ledger_index] = env.current()->seq() - 1; + // params[jss::transactions] = true; + // params[jss::expand] = true; + // auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + // } + + env(contract::call(alice, contractAccount, "events"), + escrow::comp_allowance(1000000), + ter(tesSUCCESS)); + env.close(); + + { + // Get contract info + Json::Value params; + params[jss::contract_account] = contractAccount.human(); + params[jss::account] = alice.human(); + auto const jrr = env.rpc("json", "contract_info", to_string(params)); + std::cout << jrr << std::endl; + } + + // Check stream update + BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) { + auto const data = jv[jss::data]; + // std::cout << "Event: " << data << std::endl; + BEAST_EXPECT(data["amount"] == "192"); + BEAST_EXPECT(data["currency"] == "USD"); + BEAST_EXPECT(data["destination"] == "r99mpXDsCPybsGs9XzGJmuxa8gWLTn8aCz"); + BEAST_EXPECT(data["uint128"] == "00000000000000000000000000000000"); + BEAST_EXPECT(data["uint16"] == 16); + BEAST_EXPECT(data["uint160"] == "0000000000000000000000000000000000000000"); + BEAST_EXPECT(data["uint192"] == "000000000000000000000000000000000000000000000000"); + BEAST_EXPECT( + data["uint256"] == + "00000000000000000000000000000000000000000000000000000000000000" + "00"); + BEAST_EXPECT(data["uint32"] == 32); + BEAST_EXPECT(data["uint64"] == "40"); + BEAST_EXPECT(data["uint8"] == 8); + BEAST_EXPECT(data["vl"] == "48656C6C6F2C20576F726C6421"); + return jv[jss::type] == "contractEvent" && jv[jss::name] == "event1"; + })); + + // RPC unsubscribe + auto jv = wsc->invoke("unsubscribe", stream); + if (wsc->version() == 2) + { + BEAST_EXPECT(jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0"); + BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5); + } + BEAST_EXPECT(jv[jss::status] == "success"); + } + + void + testEasyMode(FeatureBitset features) + { + testcase("easy mode"); + + using namespace jtx; + + Env env{*this, features}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const gw = Account{"gw"}; + auto const USD = gw["USD"]; + env.fund(XRP(10'000), alice, bob); + env.close(); + + std::string const wasmHex = loadContractWasmStr("easymode"); + auto const [contractAccount, contractHash, _] = setContract( + env, + tesSUCCESS, + contract::create(alice, wasmHex), + contract::add_instance_param(tfSendAmount, "amount", "AMOUNT", XRP(2000)), + contract::add_instance_param(0, "uint8", "UINT8", 1), + contract::add_function( + "easymode", + { + {0, "account", "ACCOUNT"}, + {0, "amount", "AMOUNT"}, + }), + fee(XRP(200))); + + // { + // Json::Value params; + // params[jss::ledger_index] = env.current()->seq() - 1; + // params[jss::transactions] = true; + // params[jss::expand] = true; + // auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + // } + + env(contract::call(alice, contractAccount, "easymode"), + escrow::comp_allowance(1000000), + contract::add_param(0, "account", "ACCOUNT", bob.human()), + contract::add_param(0, "amount", "AMOUNT", XRP(1)), + ter(tesSUCCESS)); + env.close(); + + // { + // Json::Value params; + // params[jss::ledger_index] = env.current()->seq() - 1; + // params[jss::transactions] = true; + // params[jss::expand] = true; + // auto const jrr = env.rpc("json", "ledger", to_string(params)); + // std::cout << jrr << std::endl; + // } + } + + void + testWithFeats(FeatureBitset features) + { + testCreatePreflight(features); + testCreatePreclaim(features); + testCreateDoApply(features); + // testModifyPreflight(features); + // testModifyPreclaim(features); + testModifyDoApply(features); + // testDeletePreflight(features); + // testDeletePreclaim(features); + testDeleteDoApply(features); + // testUserDeletePreflight(features); + // testUserDeletePreclaim(features); + // testUserDeleteDoApply(features); + testContractDataSimple(features); + testContractDataNested(features); + testContractDataArray(features); + testContractDataNestedArray(features); + testInstanceParameters(features); + testFunctionParameters(features); + testEmit(features); + // testEvents(features); + } + +public: + void + run() override + { + using namespace test::jtx; + auto const sa = testable_amendments(); + testWithFeats(sa); + } +}; + +BEAST_DEFINE_TESTSUITE(Contract, app, xrpl); + +} // namespace test +} // namespace xrpl diff --git a/src/test/app/Delegate_test.cpp b/src/test/app/Delegate_test.cpp index 588aeee634d..3ccb5dc33f3 100644 --- a/src/test/app/Delegate_test.cpp +++ b/src/test/app/Delegate_test.cpp @@ -2182,7 +2182,7 @@ class Delegate_test : public beast::unit_test::Suite // DO NOT modify expectedDelegableCount unless all scenarios, including // edge cases, have been fully tested and verified. // ==================================================================== - std::size_t const expectedDelegableCount = 51; + std::size_t const expectedDelegableCount = 57; BEAST_EXPECTS( delegableCount == expectedDelegableCount, diff --git a/src/test/app/EscrowSmart_test.cpp b/src/test/app/EscrowSmart_test.cpp new file mode 100644 index 00000000000..c12313c0a8d --- /dev/null +++ b/src/test/app/EscrowSmart_test.cpp @@ -0,0 +1,1193 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace xrpl { +namespace test { + +struct EscrowSmart_test : public beast::unit_test::suite +{ + void + testCreateFinishFunctionPreflight(FeatureBitset features) + { + testcase("Test preflight checks involving FinishFunction"); + + using namespace jtx; + using namespace std::chrono; + + Account const alice{"alice"}; + Account const carol{"carol"}; + + // Tests whether the ledger index is >= 5 + // getLedgerSqn() >= 5} + + { + // featureSmartEscrow disabled + Env env(*this, features - featureSmartEscrow); + env.fund(XRP(5000), alice, carol); + XRPAmount const txnFees = env.current()->fees().base + 1000; + auto const escrowCreate = escrow::create(alice, carol, XRP(1000)); + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + escrow::cancel_time(env.now() + 100s), + fee(txnFees), + ter(temDISABLED)); + env.close(); + + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + escrow::cancel_time(env.now() + 100s), + escrow::data("00112233"), + fee(txnFees), + ter(temDISABLED)); + env.close(); + } + + { + // FinishFunction > max length + Env env( + *this, + envconfig([](std::unique_ptr cfg) { + cfg->FEES.extension_size_limit = 10; // 10 bytes + return cfg; + }), + features); + XRPAmount const txnFees = env.current()->fees().base + 1000; + // create escrow + env.fund(XRP(5000), alice, carol); + + auto const escrowCreate = escrow::create(alice, carol, XRP(500)); + + // 11-byte string + std::string const longWasmHex = "00112233445566778899AA"; + env(escrowCreate, + escrow::finish_function(longWasmHex), + escrow::cancel_time(env.now() + 100s), + fee(txnFees), + ter(temMALFORMED)); + env.close(); + } + + { + // compute limit set to 0 + Env env( + *this, + envconfig([](std::unique_ptr cfg) { + // WASM runtime disabled + cfg->FEES.extension_compute_limit = 0; + return cfg; + }), + features); + XRPAmount const txnFees = env.current()->fees().base + 1000; + // create escrow + env.fund(XRP(5000), alice, carol); + + auto const escrowCreate = escrow::create(alice, carol, XRP(500)); + + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + escrow::cancel_time(env.now() + 100s), + escrow::comp_allowance(100), + fee(txnFees), + ter(temMALFORMED)); + env.close(); + } + + { + // size limit set to 0 + Env env( + *this, + envconfig([](std::unique_ptr cfg) { + cfg->FEES.extension_size_limit = 0; // WASM upload disabled + return cfg; + }), + features); + XRPAmount const txnFees = env.current()->fees().base + 1000; + // create escrow + env.fund(XRP(5000), alice, carol); + + auto const escrowCreate = escrow::create(alice, carol, XRP(500)); + + // 2-byte string + env(escrowCreate, + escrow::finish_function("AA"), + escrow::cancel_time(env.now() + 100s), + fee(txnFees), + ter(temTEMP_DISABLED)); + env.close(); + + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + escrow::cancel_time(env.now() + 100s), + fee(txnFees), + ter(temTEMP_DISABLED)); + env.close(); + } + + { + // Data without FinishFunction + Env env(*this, features); + XRPAmount const txnFees = env.current()->fees().base + 100000; + // create escrow + env.fund(XRP(5000), alice, carol); + + auto const escrowCreate = escrow::create(alice, carol, XRP(500)); + + std::string const longData(4, 'A'); + env(escrowCreate, + escrow::data(longData), + escrow::finish_time(env.now() + 100s), + fee(txnFees), + ter(temMALFORMED)); + env.close(); + } + + { + // Data > max length + Env env(*this, features); + XRPAmount const txnFees = env.current()->fees().base + 100000; + // create escrow + env.fund(XRP(5000), alice, carol); + + auto const escrowCreate = escrow::create(alice, carol, XRP(500)); + + // string of length maxWasmDataLength * 2 + 2 + std::string const longData((maxWasmDataLength + 1) * 2, 'B'); + env(escrowCreate, + escrow::data(longData), + escrow::finish_function(ledgerSqnWasmHex), + escrow::cancel_time(env.now() + 100s), + fee(txnFees), + ter(temMALFORMED)); + env.close(); + } + + Env env( + *this, + envconfig([](std::unique_ptr cfg) { + cfg->START_UP = StartUpType::Fresh; + return cfg; + }), + features); + XRPAmount const txnFees = env.current()->fees().base * 10 + ledgerSqnWasmHex.size() / 2 * 5; + // create escrow + env.fund(XRP(5000), alice, carol); + + auto escrowCreate = escrow::create(alice, carol, XRP(500)); + + // Success situations + { + // FinishFunction + CancelAfter + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + escrow::cancel_time(env.now() + 20s), + fee(txnFees)); + env.close(); + } + { + // FinishFunction + Condition + CancelAfter + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + escrow::cancel_time(env.now() + 30s), + escrow::condition(escrow::cb1), + fee(txnFees)); + env.close(); + } + { + // FinishFunction + FinishAfter + CancelAfter + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + escrow::cancel_time(env.now() + 40s), + escrow::finish_time(env.now() + 2s), + fee(txnFees)); + env.close(); + } + { + // FinishFunction + FinishAfter + Condition + CancelAfter + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + escrow::cancel_time(env.now() + 50s), + escrow::condition(escrow::cb1), + escrow::finish_time(env.now() + 2s), + fee(txnFees)); + env.close(); + } + + // Failure situations (i.e. all other combinations) + { + // only FinishFunction + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + fee(txnFees), + ter(temBAD_EXPIRATION)); + env.close(); + } + { + // FinishFunction + FinishAfter + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + escrow::finish_time(env.now() + 2s), + fee(txnFees), + ter(temBAD_EXPIRATION)); + env.close(); + } + { + // FinishFunction + Condition + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + escrow::condition(escrow::cb1), + fee(txnFees), + ter(temBAD_EXPIRATION)); + env.close(); + } + { + // FinishFunction + FinishAfter + Condition + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + escrow::condition(escrow::cb1), + escrow::finish_time(env.now() + 2s), + fee(txnFees), + ter(temBAD_EXPIRATION)); + env.close(); + } + { + // FinishFunction 0 length + env(escrowCreate, + escrow::finish_function(""), + escrow::cancel_time(env.now() + 60s), + fee(txnFees), + ter(temMALFORMED)); + env.close(); + } + { + // Not enough fees + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + escrow::cancel_time(env.now() + 70s), + fee(txnFees - 1), + ter(telINSUF_FEE_P)); + env.close(); + } + + { + // FinishFunction nonexistent host function + // pub fn finish() -> bool { + // unsafe { host_lib::bad() >= 5 } + // } + auto const badWasmHex = + "0061736d010000000105016000017f02100108686f73745f6c696203626164" + "00000302010005030100100611027f00418080c0000b7f00418080c0000b07" + "2e04066d656d6f727902000666696e69736800010a5f5f646174615f656e64" + "03000b5f5f686561705f6261736503010a09010700100041044a0b004d0970" + "726f64756365727302086c616e6775616765010452757374000c70726f6365" + "737365642d6279010572757374631d312e38352e3120283465623136313235" + "3020323032352d30332d31352900490f7461726765745f6665617475726573" + "042b0f6d757461626c652d676c6f62616c732b087369676e2d6578742b0f72" + "65666572656e63652d74797065732b0a6d756c746976616c7565"; + env(escrowCreate, + escrow::finish_function(badWasmHex), + escrow::cancel_time(env.now() + 100s), + fee(txnFees), + ter(temBAD_WASM)); + env.close(); + } + } + + void + testFinishWasmFailures(FeatureBitset features) + { + testcase("EscrowFinish Smart Escrow failures"); + + using namespace jtx; + using namespace std::chrono; + + Account const alice{"alice"}; + Account const carol{"carol"}; + + // Tests whether the ledger index is >= 5 + // getLedgerSqn() >= 5} + + { + // featureSmartEscrow disabled + Env env(*this, features - featureSmartEscrow); + env.fund(XRP(5000), alice, carol); + XRPAmount const txnFees = + env.current()->fees().base * 10 + ledgerSqnWasmHex.size() / 2 * 5; + env(escrow::finish(carol, alice, 1), + fee(txnFees), + escrow::comp_allowance(4), + ter(temDISABLED)); + env.close(); + } + + { + // ComputationAllowance > max compute limit + Env env( + *this, + envconfig([](std::unique_ptr cfg) { + cfg->FEES.extension_compute_limit = 1'000; // in gas + return cfg; + }), + features); + env.fund(XRP(5000), alice, carol); + // Run past the flag ledger so that a Fee change vote occurs and + // updates FeeSettings. (It also activates all supported + // amendments.) + for (auto i = env.current()->seq(); i <= 257; ++i) + env.close(); + + auto const allowance = 1'001; + env(escrow::finish(carol, alice, 1), + fee(env.current()->fees().base + allowance), + escrow::comp_allowance(allowance), + ter(temBAD_LIMIT)); + } + + { + // WASM compute disabled + using namespace test::jtx; + using namespace std::chrono; + Env env{*this, envconfig([](std::unique_ptr cfg) { + cfg->FEES.extension_compute_limit = 0; + return cfg; + })}; + + Account const alice{"alice"}; + env.fund(XRP(1000), alice); + env.close(); + + auto const seq = env.seq(alice); + auto const keylet = keylet::escrow(alice.id(), seq); + env(noop(alice)); // to align sequence numbers + + // This adds the Escrow ledger object by hand, bypassing normal + // transaction processing This is necessary because the config + // cannot be updated in the middle of a test, and we cannot easily + // create a Smart Escrow while the compute limit is set to 0 + env.app().getOpenLedger().modify([&](OpenView& view, beast::Journal j) { + auto sle = std::make_shared(keylet); + + sle->setAccountID(sfAccount, alice.id()); + sle->setFieldAmount(sfAmount, XRP(100)); + sle->setFieldU32(sfCancelAfter, 110); + sle->setAccountID(sfDestination, alice.id()); + sle->setFieldVL(sfFinishFunction, strUnHex(ledgerSqnWasmHex).value()); + sle->setFieldU32(sfFlags, 0); + sle->setFieldU64(sfOwnerNode, 0); + uint256 tmp; + BEAST_EXPECT(tmp.parseHex( + "F63D1A452A96C19EFD77901FB37D236C59EAA746771A6" + "85D1BBA57A2238B9401")); + sle->setFieldH256(sfPreviousTxnID, tmp); + sle->setFieldU32(sfPreviousTxnLgrSeq, 4); + sle->setFieldU32(sfSequence, seq); + + view.rawInsert(sle); + return true; + }); + BEAST_EXPECT(env.le(keylet)); + + env(escrow::finish(alice, alice, seq), + escrow::comp_allowance(1000), + fee(env.current()->fees().base + 1000), + ter(temTEMP_DISABLED)); + } + + Env env(*this, features); + + // Run past the flag ledger so that a Fee change vote occurs and + // updates FeeSettings. (It also activates all supported + // amendments.) + for (auto i = env.current()->seq(); i <= 257; ++i) + env.close(); + + XRPAmount const txnFees = env.current()->fees().base * 10 + ledgerSqnWasmHex.size() / 2 * 5; + env.fund(XRP(5000), alice, carol); + + // create escrow + auto const seq = env.seq(alice); + env(escrow::create(alice, carol, XRP(500)), + escrow::finish_function(ledgerSqnWasmHex), + escrow::cancel_time(env.now() + 100s), + fee(txnFees)); + env.close(); + + { + // no ComputationAllowance field + env(escrow::finish(carol, alice, seq), ter(tefWASM_FIELD_NOT_INCLUDED)); + } + + { + // ComputationAllowance value of 0 + env(escrow::finish(carol, alice, seq), escrow::comp_allowance(0), ter(temBAD_LIMIT)); + } + + { + // not enough fees + // This function takes 4 gas + // In testing, 1 gas costs 1 drop + auto const finishFee = env.current()->fees().base + 3; + env(escrow::finish(carol, alice, seq), + fee(finishFee), + escrow::comp_allowance(4), + ter(telINSUF_FEE_P)); + } + + { + // not enough gas + // This function takes 4 gas + // In testing, 1 gas costs 1 drop + auto const finishFee = env.current()->fees().base + 4; + env(escrow::finish(carol, alice, seq), + fee(finishFee), + escrow::comp_allowance(2), + ter(tecFAILED_PROCESSING)); + } + + { + // ComputationAllowance field included w/no FinishFunction on + // escrow + auto const seq2 = env.seq(alice); + env(escrow::create(alice, carol, XRP(500)), + escrow::finish_time(env.now() + 10s), + escrow::cancel_time(env.now() + 100s)); + env.close(); + + auto const allowance = 100; + env(escrow::finish(carol, alice, seq2), + fee(env.current()->fees().base + + (allowance * env.current()->fees().gasPrice) / MICRO_DROPS_PER_DROP + 1), + escrow::comp_allowance(allowance), + ter(tefNO_WASM)); + } + } + + void + testFinishFunction(FeatureBitset features) + { + testcase("Example escrow function"); + + using namespace jtx; + using namespace std::chrono; + + Account const alice{"alice"}; + Account const carol{"carol"}; + + // Tests whether the ledger index is >= 5 + // getLedgerSqn() >= 5} + std::uint32_t const allowance = 467; + auto escrowCreate = escrow::create(alice, carol, XRP(1000)); + auto [createFee, finishFee] = [&]() { + Env const env(*this, features); + auto createFee = env.current()->fees().base * 10 + ledgerSqnWasmHex.size() / 2 * 5; + auto finishFee = env.current()->fees().base + + (allowance * env.current()->fees().gasPrice) / MICRO_DROPS_PER_DROP + 1; + return std::make_pair(createFee, finishFee); + }(); + + { + // basic FinishFunction situation + Env env(*this, features); + // create escrow + env.fund(XRP(5000), alice, carol); + auto const seq = env.seq(alice); + BEAST_EXPECT(env.ownerCount(alice) == 0); + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + escrow::cancel_time(env.now() + 100s), + fee(createFee)); + env.close(); + + if (BEAST_EXPECT(env.ownerCount(alice) == 2)) + { + env.require(balance(alice, XRP(4000) - createFee)); + env.require(balance(carol, XRP(5000))); + + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecWASM_REJECTED)); + env(escrow::finish(alice, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecWASM_REJECTED)); + env(escrow::finish(alice, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecWASM_REJECTED)); + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecWASM_REJECTED)); + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecWASM_REJECTED)); + env.close(); + + { + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed))) + BEAST_EXPECTS( + env.meta()->getFieldU32(sfGasUsed) == allowance, + std::to_string(env.meta()->getFieldU32(sfGasUsed))); + } + + env(escrow::finish(alice, alice, seq), + fee(finishFee), + escrow::comp_allowance(allowance), + ter(tesSUCCESS)); + + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed))) + BEAST_EXPECTS( + txMeta->getFieldU32(sfGasUsed) == allowance, + std::to_string(txMeta->getFieldU32(sfGasUsed))); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode))) + BEAST_EXPECTS( + txMeta->getFieldI32(sfWasmReturnCode) == 5, + std::to_string(txMeta->getFieldI32(sfWasmReturnCode))); + + BEAST_EXPECT(env.ownerCount(alice) == 0); + } + } + + { + // FinishFunction + Condition + Env env(*this, features); + env.fund(XRP(5000), alice, carol); + BEAST_EXPECT(env.ownerCount(alice) == 0); + auto const seq = env.seq(alice); + // create escrow + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + escrow::condition(escrow::cb1), + escrow::cancel_time(env.now() + 100s), + fee(createFee)); + env.close(); + auto const conditionFinishFee = + finishFee + env.current()->fees().base * (32 + (escrow::fb1.size() / 16)); + + if (BEAST_EXPECT(env.ownerCount(alice) == 2)) + { + env.require(balance(alice, XRP(4000) - createFee)); + env.require(balance(carol, XRP(5000))); + + // no fulfillment provided, function fails + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecCRYPTOCONDITION_ERROR)); + // fulfillment provided, function fails + env(escrow::finish(carol, alice, seq), + escrow::condition(escrow::cb1), + escrow::fulfillment(escrow::fb1), + escrow::comp_allowance(allowance), + fee(conditionFinishFee), + ter(tecWASM_REJECTED)); + if (BEAST_EXPECT(env.meta()->isFieldPresent(sfGasUsed))) + BEAST_EXPECTS( + env.meta()->getFieldU32(sfGasUsed) == allowance, + std::to_string(env.meta()->getFieldU32(sfGasUsed))); + env.close(); + // no fulfillment provided, function succeeds + env(escrow::finish(alice, alice, seq), + escrow::comp_allowance(allowance), + fee(conditionFinishFee), + ter(tecCRYPTOCONDITION_ERROR)); + // wrong fulfillment provided, function succeeds + env(escrow::finish(alice, alice, seq), + escrow::condition(escrow::cb1), + escrow::fulfillment(escrow::fb2), + escrow::comp_allowance(allowance), + fee(conditionFinishFee), + ter(tecCRYPTOCONDITION_ERROR)); + // fulfillment provided, function succeeds, tx succeeds + env(escrow::finish(alice, alice, seq), + escrow::condition(escrow::cb1), + escrow::fulfillment(escrow::fb1), + escrow::comp_allowance(allowance), + fee(conditionFinishFee), + ter(tesSUCCESS)); + + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed))) + BEAST_EXPECTS( + txMeta->getFieldU32(sfGasUsed) == allowance, + std::to_string(txMeta->getFieldU32(sfGasUsed))); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode))) + BEAST_EXPECTS( + txMeta->getFieldI32(sfWasmReturnCode) == 5, + std::to_string(txMeta->getFieldI32(sfWasmReturnCode))); + + env.close(); + BEAST_EXPECT(env.ownerCount(alice) == 0); + } + } + + { + // FinishFunction + FinishAfter + Env env(*this, features); + // create escrow + env.fund(XRP(5000), alice, carol); + auto const seq = env.seq(alice); + BEAST_EXPECT(env.ownerCount(alice) == 0); + auto const ts = env.now() + 97s; + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + escrow::finish_time(ts), + escrow::cancel_time(env.now() + 1000s), + fee(createFee)); + env.close(); + + if (BEAST_EXPECT(env.ownerCount(alice) == 2)) + { + env.require(balance(alice, XRP(4000) - createFee)); + env.require(balance(carol, XRP(5000))); + + // finish time hasn't passed, function fails + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee + 1), + ter(tecNO_PERMISSION)); + env.close(); + // finish time hasn't passed, function succeeds + for (; env.now() < ts; env.close()) + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee + 2), + ter(tecNO_PERMISSION)); + + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee + 1), + ter(tesSUCCESS)); + + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed))) + BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode))) + BEAST_EXPECTS( + txMeta->getFieldI32(sfWasmReturnCode) == 5, + std::to_string(txMeta->getFieldI32(sfWasmReturnCode))); + + BEAST_EXPECT(env.ownerCount(alice) == 0); + } + } + + { + // FinishFunction + FinishAfter #2 + Env env(*this, features); + // create escrow + env.fund(XRP(5000), alice, carol); + auto const seq = env.seq(alice); + BEAST_EXPECT(env.ownerCount(alice) == 0); + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + escrow::finish_time(env.now() + 2s), + escrow::cancel_time(env.now() + 100s), + fee(createFee)); + // Don't close the ledger here + + if (BEAST_EXPECT(env.ownerCount(alice) == 2)) + { + env.require(balance(alice, XRP(4000) - createFee)); + env.require(balance(carol, XRP(5000))); + + // finish time hasn't passed, function fails + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecNO_PERMISSION)); + env.close(); + + // finish time has passed, function fails + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecWASM_REJECTED)); + if (BEAST_EXPECT(env.meta()->isFieldPresent(sfGasUsed))) + BEAST_EXPECTS( + env.meta()->getFieldU32(sfGasUsed) == allowance, + std::to_string(env.meta()->getFieldU32(sfGasUsed))); + env.close(); + // finish time has passed, function succeeds, tx succeeds + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tesSUCCESS)); + + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed))) + BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode))) + BEAST_EXPECTS( + txMeta->getFieldI32(sfWasmReturnCode) == 5, + std::to_string(txMeta->getFieldI32(sfWasmReturnCode))); + + env.close(); + BEAST_EXPECT(env.ownerCount(alice) == 0); + } + } + } + + void + testUpdateDataOnFailure(FeatureBitset features) + { + testcase("Update escrow data on failure"); + + using namespace jtx; + using namespace std::chrono; + + // wasm that always fails + Account const alice{"alice"}; + Account const carol{"carol"}; + + Env env(*this, features); + // create escrow + env.fund(XRP(5000), alice); + auto const seq = env.seq(alice); + BEAST_EXPECT(env.ownerCount(alice) == 0); + auto escrowCreate = escrow::create(alice, alice, XRP(1000)); + XRPAmount const txnFees = + env.current()->fees().base * 10 + updateDataWasmHex.size() / 2 * 5; + env(escrowCreate, + escrow::finish_function(updateDataWasmHex), + escrow::finish_time(env.now() + 2s), + escrow::cancel_time(env.now() + 100s), + fee(txnFees)); + env.close(); + env.close(); + env.close(); + + if (BEAST_EXPECT(env.ownerCount(alice) == (1 + updateDataWasmHex.size() / 2 / 500))) + { + env.require(balance(alice, XRP(4000) - txnFees)); + + auto const allowance = 1420; + XRPAmount const finishFee = env.current()->fees().base + + (allowance * env.current()->fees().gasPrice) / MICRO_DROPS_PER_DROP + 1; + + // FinishAfter time hasn't passed + env(escrow::finish(alice, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecWASM_REJECTED)); + + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta && txMeta->isFieldPresent(sfGasUsed))) + BEAST_EXPECTS( + txMeta->getFieldU32(sfGasUsed) == allowance, + std::to_string(txMeta->getFieldU32(sfGasUsed))); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode))) + BEAST_EXPECTS( + txMeta->getFieldI32(sfWasmReturnCode) == -256, + std::to_string(txMeta->getFieldI32(sfWasmReturnCode))); + + auto const sle = env.le(keylet::escrow(alice, seq)); + if (BEAST_EXPECT(sle && sle->isFieldPresent(sfData))) + BEAST_EXPECTS(checkVL(sle, sfData, "Data"), strHex(sle->getFieldVL(sfData))); + } + } + + void + testFees(FeatureBitset features) + { + testcase("Fees"); + + using namespace jtx; + using namespace std::chrono; + + Account const alice{"alice"}; + Account const carol{"carol"}; + + // Tests whether the ledger index is >= 5 + // getLedgerSqn() >= 5} + uint64_t const allowance = 467; + auto escrowCreate = escrow::create(alice, carol, XRP(1000)); + auto createFee = [&]() { + Env const env(*this, features); + auto createFee = env.current()->fees().base * 10 + ledgerSqnWasmHex.size() / 2 * 5; + return createFee; + }(); + + { + // ensure fees don't overflow + Env env( + *this, + envconfig([](std::unique_ptr cfg) { + cfg->FEES.gas_price = 1'000'000; // in gas + return cfg; + }), + features); + // Run past the flag ledger so that a Fee change vote occurs and + // updates FeeSettings. (It also activates all supported + // amendments.) + for (auto i = env.current()->seq(); i <= 257; ++i) + env.close(); + + // create escrow + env.fund(XRP(5000), alice, carol); + auto const seq = env.seq(alice); + BEAST_EXPECT(env.ownerCount(alice) == 0); + env(escrowCreate, + escrow::finish_function(ledgerSqnWasmHex), + escrow::cancel_time(env.now() + 100s), + fee(createFee)); + env.close(); + + if (BEAST_EXPECT(env.ownerCount(alice) == 2)) + { + env.require(balance(alice, XRP(4000) - createFee)); + env.require(balance(carol, XRP(5000))); + env.close(); + + auto const bigAllowance = 996'433; + uint64_t const partialFeeCalc = + (static_cast(bigAllowance) * 1'000'000) / MICRO_DROPS_PER_DROP + + 1; // to avoid an overflow + auto finishFee = env.current()->fees().base + partialFeeCalc; + BEAST_EXPECT(finishFee.drops() > bigAllowance); + + // Intentional low value to test overflow handling + auto finishFeeOverflow = drops(30); + + env(escrow::finish(alice, alice, seq), + fee(finishFeeOverflow), // enough if there's an overflow + escrow::comp_allowance(bigAllowance), + ter(telINSUF_FEE_P)); + + env(escrow::finish(alice, alice, seq), + fee(finishFee - 1), + escrow::comp_allowance(bigAllowance), + ter(telINSUF_FEE_P)); + + env(escrow::finish(alice, alice, seq), + fee(finishFee), + escrow::comp_allowance(bigAllowance), + ter(tesSUCCESS)); + + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed))) + BEAST_EXPECTS( + txMeta->getFieldU32(sfGasUsed) == allowance, + std::to_string(txMeta->getFieldU32(sfGasUsed))); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode))) + BEAST_EXPECTS( + txMeta->getFieldI32(sfWasmReturnCode) == 5, + std::to_string(txMeta->getFieldI32(sfWasmReturnCode))); + + BEAST_EXPECT(env.ownerCount(alice) == 0); + } + } + } + + void + testAllHostFunctions(FeatureBitset features) + { + testcase("Test all host functions"); + + using namespace jtx; + using namespace std::chrono; + + Account const alice{"alice"}; + Account const carol{"carol"}; + + { + Env env(*this, features); + // create escrow + env.fund(XRP(5000), alice, carol); + auto const seq = env.seq(alice); + BEAST_EXPECT(env.ownerCount(alice) == 0); + auto escrowCreate = escrow::create(alice, carol, XRP(1000)); + XRPAmount const txnFees = + env.current()->fees().base * 10 + allHostFunctionsWasmHex.size() / 2 * 5; + env(escrowCreate, + escrow::finish_function(allHostFunctionsWasmHex), + escrow::finish_time(env.now() + 11s), + escrow::cancel_time(env.now() + 100s), + escrow::data("1000000000"), // 1000 XRP in drops + fee(txnFees)); + env.close(); + + if (BEAST_EXPECT( + env.ownerCount(alice) == (1 + allHostFunctionsWasmHex.size() / 2 / 500))) + { + env.require(balance(alice, XRP(4000) - txnFees)); + env.require(balance(carol, XRP(5000))); + + auto const allowance = 1'000'000; + XRPAmount const finishFee = env.current()->fees().base + + (allowance * env.current()->fees().gasPrice) / MICRO_DROPS_PER_DROP + 1; + + // FinishAfter time hasn't passed + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tecNO_PERMISSION)); + env.close(); + env.close(); + env.close(); + + // reduce the destination balance + env(pay(carol, alice, XRP(4500))); + env.close(); + env.close(); + + env(escrow::finish(alice, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee), + ter(tesSUCCESS)); + + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta && txMeta->isFieldPresent(sfGasUsed))) + BEAST_EXPECTS( + txMeta->getFieldU32(sfGasUsed) == 64'292, + std::to_string(txMeta->getFieldU32(sfGasUsed))); + if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode))) + BEAST_EXPECT(txMeta->getFieldI32(sfWasmReturnCode) == 1); + + env.close(); + BEAST_EXPECT(env.ownerCount(alice) == 0); + } + } + } + + void + testKeyletHostFunctions(FeatureBitset features) + { + testcase("Test all keylet host functions"); + + using namespace jtx; + using namespace std::chrono; + + // TODO: create wasm module for all host functions + Account const alice{"alice"}; + Account const carol{"carol"}; + + { + Env env{*this}; + env.fund(XRP(10000), alice, carol); + + BEAST_EXPECT(env.seq(alice) == 4); + BEAST_EXPECT(env.ownerCount(alice) == 0); + + // base objects that need to be created first + auto const tokenId = token::getNextID(env, alice, 0, tfTransferable); + env(token::mint(alice, 0u), txflags(tfTransferable)); + env(trust(alice, carol["USD"](1'000'000))); + env.close(); + BEAST_EXPECT(env.seq(alice) == 6); + BEAST_EXPECT(env.ownerCount(alice) == 2); + + // set up a bunch of objects to check their keylets + AMM const amm(env, carol, XRP(10), carol["USD"](1000)); + env(check::create(alice, carol, XRP(100))); + env(credentials::create(alice, alice, "termsandconditions")); + env(delegate::set(alice, carol, {"TrustSet"})); + env(deposit::auth(alice, carol)); + env(did::set(alice), did::data("alice_did")); + env(escrow::create(alice, carol, XRP(100)), escrow::finish_time(env.now() + 100s)); + MPTTester mptTester{env, alice, {.fund = false}}; + mptTester.create(); + mptTester.authorize({.account = carol}); + env(token::createOffer(carol, tokenId, XRP(100)), token::owner(alice)); + env(offer(alice, carol["GBP"](0.1), XRP(100))); + env(paychan::create(alice, carol, XRP(1000), 100s, alice.pk())); + pdomain::Credentials const credentials{{alice, "first credential"}}; + env(pdomain::setTx(alice, credentials)); + env(signers(alice, 1, {{carol, 1}})); + env(ticket::create(alice, 1)); + Vault const vault{env}; + auto [tx, _keylet] = vault.create({.owner = alice, .asset = xrpIssue()}); + env(tx); + env.close(); + + BEAST_EXPECTS(env.ownerCount(alice) == 17, std::to_string(env.ownerCount(alice))); + if (BEAST_EXPECTS(env.seq(alice) == 20, std::to_string(env.seq(alice)))) + { + auto const seq = env.seq(alice); + XRPAmount const txnFees = + env.current()->fees().base * 10 + allKeyletsWasmHex.size() / 2 * 5; + env(escrow::create(alice, carol, XRP(1000)), + escrow::finish_function(allKeyletsWasmHex), + escrow::finish_time(env.now() + 2s), + escrow::cancel_time(env.now() + 100s), + fee(txnFees)); + env.close(); + env.close(); + env.close(); + + auto const allowance = 184'444; + auto const finishFee = env.current()->fees().base + + (allowance * env.current()->fees().gasPrice) / MICRO_DROPS_PER_DROP + 1; + env(escrow::finish(carol, alice, seq), + escrow::comp_allowance(allowance), + fee(finishFee)); + env.close(); + + auto const txMeta = env.meta(); + if (BEAST_EXPECT(txMeta && txMeta->isFieldPresent(sfGasUsed))) + { + auto const gasUsed = txMeta->getFieldU32(sfGasUsed); + BEAST_EXPECTS(gasUsed == allowance, std::to_string(gasUsed)); + } + BEAST_EXPECTS(env.ownerCount(alice) == 17, std::to_string(env.ownerCount(alice))); + } + } + } + + void + testLargeWasmModules(FeatureBitset features) + { + testcase("Test large wasm modules"); + + using namespace jtx; + using namespace std::chrono; + using namespace wasm_constants; + + enum class ExpectedStatus { Success, Malformed, Crash }; + + auto runTest = [&](std::vector const& wasm, + std::optional sizeLimit, + ExpectedStatus expectedStatus, + std::source_location const& loc = std::source_location::current()) { + auto makeEnv = [&]() -> Env { + if (sizeLimit) + return Env( + *this, + envconfig([&sizeLimit](std::unique_ptr cfg) { + cfg->FEES.extension_size_limit = *sizeLimit; + return cfg; + }), + features); + else + return Env(*this, features); + }; + Env env = makeEnv(); + + auto const alice = Account("alice"); + env.fund(XRP(1'000'000), alice); + env.close(); + + auto const wasmHex = strHex(wasm); + try + { + env(escrow::create(alice, alice, XRP(1000)), + escrow::finish_function(wasmHex), + escrow::cancel_time(env.now() + 100s), + fee(env.current()->fees().base * 10 + wasmHex.size() / 2 * 5), + ter(expectedStatus == ExpectedStatus::Success ? TER{tesSUCCESS} + : TER{temMALFORMED})); + if (expectedStatus == ExpectedStatus::Crash) + fail("Expected crash", loc.file_name(), loc.line()); + else + pass(); + } + catch (std::exception const& e) + { + if (expectedStatus == ExpectedStatus::Crash) + pass(); + else + fail(e.what(), loc.file_name(), loc.line()); + } + }; + + // Table-driven test cases + struct TestCase + { + enum class BlobType { Code, Data }; + BlobType type; + uint32_t size; + std::optional sizeLimit; + ExpectedStatus expected; + }; + + std::vector const testCases = { + // Code blob tests + {TestCase::BlobType::Code, + 99'959, + std::nullopt, + ExpectedStatus::Success}, // just under 100kb + {TestCase::BlobType::Code, + 99'961, + std::nullopt, + ExpectedStatus::Malformed}, // just over 100kb + {TestCase::BlobType::Code, 200'000, 10'000'000, ExpectedStatus::Success}, // ~200kb + {TestCase::BlobType::Code, + 490'000, + 10'000'000, + ExpectedStatus::Success}, // just under 1MB JSON + {TestCase::BlobType::Code, + 999'999, + 10'000'000, + ExpectedStatus::Crash}, // just over 1MB JSON + // Data blob tests + {TestCase::BlobType::Data, + 99'946, + std::nullopt, + ExpectedStatus::Success}, // just under 100kb + {TestCase::BlobType::Data, + 99'948, + std::nullopt, + ExpectedStatus::Malformed}, // just over 100kb + {TestCase::BlobType::Data, 200'000, 10'000'000, ExpectedStatus::Success}, // ~200kb + {TestCase::BlobType::Data, + 490'000, + 10'000'000, + ExpectedStatus::Success}, // just under 1MB JSON + {TestCase::BlobType::Data, + 999'950, + 10'000'000, + ExpectedStatus::Crash}, // just over 1MB JSON + }; + + for (auto const& tc : testCases) + { + auto const wasm = tc.type == TestCase::BlobType::Code ? generateCodeBlob(tc.size) + : generateDataBlob(tc.size); + runTest(wasm, tc.sizeLimit, tc.expected); + } + } + + void + testWithFeats(FeatureBitset features) + { + testCreateFinishFunctionPreflight(features); + testFinishWasmFailures(features); + testFinishFunction(features); + testUpdateDataOnFailure(features); + testFees(features); + + // TODO: Update module with new host functions + testAllHostFunctions(features); + testKeyletHostFunctions(features); + + testLargeWasmModules(features); + } + +public: + void + run() override + { + using namespace test::jtx; + FeatureBitset const all{testable_amendments()}; + testWithFeats(all); + } +}; + +BEAST_DEFINE_TESTSUITE(EscrowSmart, app, xrpl); + +} // namespace test +} // namespace xrpl diff --git a/src/test/app/Escrow_test.cpp b/src/test/app/Escrow_test.cpp index 3e76524cf1e..72951d32ec3 100644 --- a/src/test/app/Escrow_test.cpp +++ b/src/test/app/Escrow_test.cpp @@ -1479,7 +1479,7 @@ struct Escrow_test : public beast::unit_test::Suite Account const alice{"alice"}; Account const bob{"bob"}; Account const carol{"carol"}; - Account const dillon{"dillon "}; + Account const dillon{"dillon"}; Account const zelda{"zelda"}; char const credType[] = "abcde"; @@ -1636,6 +1636,8 @@ struct Escrow_test : public beast::unit_test::Suite FeatureBitset const all{testableAmendments()}; testWithFeats(all); testWithFeats(all - featureTokenEscrow); + testWithFeats(all - featureSmartEscrow); + testWithFeats(all - featureTokenEscrow - featureSmartEscrow); testTags(all - fixIncludeKeyletFields); } }; diff --git a/src/test/app/FeeVote_test.cpp b/src/test/app/FeeVote_test.cpp index 22e8322bb56..64f3af1b10f 100644 --- a/src/test/app/FeeVote_test.cpp +++ b/src/test/app/FeeVote_test.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -45,10 +46,17 @@ struct FeeSettingsFields std::optional baseFeeDrops = std::nullopt; std::optional reserveBaseDrops = std::nullopt; std::optional reserveIncrementDrops = std::nullopt; + std::optional extensionComputeLimit = std::nullopt; + std::optional extensionSizeLimit = std::nullopt; + std::optional gasPrice = std::nullopt; }; STTx -createFeeTx(Rules const& rules, std::uint32_t seq, FeeSettingsFields const& fields) +createFeeTx( + Rules const& rules, + std::uint32_t seq, + FeeSettingsFields const& fields, + bool forceAllFields = false) { auto fill = [&](auto& obj) { obj.setAccountID(sfAccount, AccountID()); @@ -76,6 +84,24 @@ createFeeTx(Rules const& rules, std::uint32_t seq, FeeSettingsFields const& fiel obj.setFieldU32( sfReferenceFeeUnits, fields.referenceFeeUnits ? *fields.referenceFeeUnits : 0); } + if (rules.enabled(featureSmartEscrow) || forceAllFields) + { + obj.setFieldU32( + sfExtensionComputeLimit, + fields.extensionComputeLimit ? *fields.extensionComputeLimit : 0); + obj.setFieldU32( + sfExtensionSizeLimit, fields.extensionSizeLimit ? *fields.extensionSizeLimit : 0); + obj.setFieldU32(sfGasPrice, fields.gasPrice ? *fields.gasPrice : 0); + } + if (rules.enabled(featureSmartEscrow) || forceAllFields) + { + obj.setFieldU32( + sfExtensionComputeLimit, + fields.extensionComputeLimit ? *fields.extensionComputeLimit : 0); + obj.setFieldU32( + sfExtensionSizeLimit, fields.extensionSizeLimit ? *fields.extensionSizeLimit : 0); + obj.setFieldU32(sfGasPrice, fields.gasPrice ? *fields.gasPrice : 0); + } }; return STTx(ttFEE, fill); } @@ -124,6 +150,12 @@ createInvalidFeeTx( obj.setFieldU32(sfReserveIncrement, 50000); obj.setFieldU32(sfReferenceFeeUnits, 10); } + if (rules.enabled(featureSmartEscrow)) + { + obj.setFieldU32(sfExtensionComputeLimit, 100 + uniqueValue); + obj.setFieldU32(sfExtensionSizeLimit, 200 + uniqueValue); + obj.setFieldU32(sfGasPrice, 300 + uniqueValue); + } } // If missingRequiredFields is true, we don't add the required fields // (default behavior) @@ -131,7 +163,7 @@ createInvalidFeeTx( return STTx(ttFEE, fill); } -bool +TER applyFeeAndTestResult(jtx::Env& env, OpenView& view, STTx const& tx) { auto const res = apply(env.app(), view, tx, ApplyFlags::TapNone, env.journal); @@ -186,6 +218,22 @@ verifyFeeObject( if (!checkEquality(sfReferenceFeeUnits, expected.referenceFeeUnits)) return false; } + if (rules.enabled(featureSmartEscrow)) + { + if (!checkEquality(sfExtensionComputeLimit, expected.extensionComputeLimit.value_or(0))) + return false; + if (!checkEquality(sfExtensionSizeLimit, expected.extensionSizeLimit.value_or(0))) + return false; + if (!checkEquality(sfGasPrice, expected.gasPrice.value_or(0))) + return false; + } + else + { + if (feeObject->isFieldPresent(sfExtensionComputeLimit) || + feeObject->isFieldPresent(sfExtensionSizeLimit) || + feeObject->isFieldPresent(sfGasPrice)) + return false; + } return true; } @@ -208,6 +256,7 @@ class FeeVote_test : public beast::unit_test::Suite void testSetup() { + testcase("FeeVote setup"); FeeSetup const defaultSetup; { // defaults @@ -216,6 +265,9 @@ class FeeVote_test : public beast::unit_test::Suite BEAST_EXPECT(setup.referenceFee == defaultSetup.referenceFee); BEAST_EXPECT(setup.accountReserve == defaultSetup.accountReserve); BEAST_EXPECT(setup.ownerReserve == defaultSetup.ownerReserve); + BEAST_EXPECT(setup.extension_compute_limit == defaultSetup.extension_compute_limit); + BEAST_EXPECT(setup.extension_size_limit == defaultSetup.extension_size_limit); + BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price); } { Section config; @@ -225,26 +277,51 @@ class FeeVote_test : public beast::unit_test::Suite BEAST_EXPECT(setup.referenceFee == 50); BEAST_EXPECT(setup.accountReserve == 1234567); BEAST_EXPECT(setup.ownerReserve == 1234); + "extension_compute_limit = 100", + "extension_size_limit = 200", + " gas_price = 300"}); + BEAST_EXPECT(setup.extension_compute_limit == 100); + BEAST_EXPECT(setup.extension_size_limit == 200); + BEAST_EXPECT(setup.gas_price == 300); } { Section config; config.append( - {"reference_fee = blah", "account_reserve = yada", "owner_reserve = foo"}); + {"reference_fee = blah", + "account_reserve = yada", + "owner_reserve = foo", + "extension_compute_limit = bar", + "extension_size_limit = baz", + "gas_price = qux"}); // Illegal values are ignored, and the defaults left unchanged auto setup = setupFeeVote(config); BEAST_EXPECT(setup.referenceFee == defaultSetup.referenceFee); BEAST_EXPECT(setup.accountReserve == defaultSetup.accountReserve); BEAST_EXPECT(setup.ownerReserve == defaultSetup.ownerReserve); + BEAST_EXPECT(setup.extension_compute_limit == defaultSetup.extension_compute_limit); + BEAST_EXPECT(setup.extension_size_limit == defaultSetup.extension_size_limit); + BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price); } { Section config; config.append( - {"reference_fee = -50", "account_reserve = -1234567", "owner_reserve = -1234"}); + {"reference_fee = -50", + "account_reserve = -1234567", + "owner_reserve = -1234", + "extension_compute_limit = -100", + "extension_size_limit = -200", + "gas_price = -300"}); + // Negative values wrap to large positive uint32_t values. + // reference_fee is uint64_t and has bounds checking, so it keeps + // the default. // Illegal values are ignored, and the defaults left unchanged auto setup = setupFeeVote(config); BEAST_EXPECT(setup.referenceFee == defaultSetup.referenceFee); BEAST_EXPECT(setup.accountReserve == static_cast(-1234567)); BEAST_EXPECT(setup.ownerReserve == static_cast(-1234)); + BEAST_EXPECT(setup.extension_compute_limit == static_cast(-100)); + BEAST_EXPECT(setup.extension_size_limit == static_cast(-200)); + BEAST_EXPECT(setup.gas_price == static_cast(-300)); } { auto const big64 = std::to_string( @@ -253,12 +330,18 @@ class FeeVote_test : public beast::unit_test::Suite config.append( {"reference_fee = " + big64, "account_reserve = " + big64, - "owner_reserve = " + big64}); + "owner_reserve = " + big64, + "extension_compute_limit = " + big64, + "extension_size_limit = " + big64, + "gas_price = " + big64}); // Illegal values are ignored, and the defaults left unchanged auto setup = setupFeeVote(config); BEAST_EXPECT(setup.referenceFee == defaultSetup.referenceFee); BEAST_EXPECT(setup.accountReserve == defaultSetup.accountReserve); BEAST_EXPECT(setup.ownerReserve == defaultSetup.ownerReserve); + BEAST_EXPECT(setup.extension_compute_limit == defaultSetup.extension_compute_limit); + BEAST_EXPECT(setup.extension_size_limit == defaultSetup.extension_size_limit); + BEAST_EXPECT(setup.gas_price == defaultSetup.gas_price); } } @@ -290,7 +373,7 @@ class FeeVote_test : public beast::unit_test::Suite auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields); OpenView accum(ledger.get()); - BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx)); + BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx))); accum.apply(*ledger); // Verify fee object was created/updated correctly @@ -318,12 +401,71 @@ class FeeVote_test : public beast::unit_test::Suite auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields); OpenView accum(ledger.get()); - BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx)); + BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx))); + accum.apply(*ledger); + + // Verify fee object was created/updated correctly + BEAST_EXPECT(verifyFeeObject(ledger, ledger->rules(), fields)); + } + + // Test with both XRPFees and SmartEscrow enabled + { + jtx::Env env(*this, jtx::testable_amendments()); + auto ledger = std::make_shared( + create_genesis, + Rules{env.app().config().features}, + env.app().config().FEES.toFees(), + std::vector{}, + env.app().getNodeFamily()); + + // Create the next ledger to apply transaction to + ledger = std::make_shared(*ledger, env.app().getTimeKeeper().closeTime()); + + FeeSettingsFields const fields{ + .baseFeeDrops = XRPAmount{10}, + .reserveBaseDrops = XRPAmount{200000}, + .reserveIncrementDrops = XRPAmount{50000}, + .extensionComputeLimit = 100, + .extensionSizeLimit = 200, + .gasPrice = 300}; + // Test successful fee transaction with new fields + auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields); + + OpenView accum(ledger.get()); + BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx))); accum.apply(*ledger); // Verify fee object was created/updated correctly BEAST_EXPECT(verifyFeeObject(ledger, ledger->rules(), fields)); } + + // Test that the Smart Escrow fields are rejected if the + // feature is disabled + { + jtx::Env env(*this, jtx::testable_amendments() - featureSmartEscrow); + auto ledger = std::make_shared( + create_genesis, + Rules{env.app().config().features}, + env.app().config().FEES.toFees(), + std::vector{}, + env.app().getNodeFamily()); + + // Create the next ledger to apply transaction to + ledger = std::make_shared(*ledger, env.app().getTimeKeeper().closeTime()); + + FeeSettingsFields const fields{ + .baseFeeDrops = XRPAmount{10}, + .reserveBaseDrops = XRPAmount{200000}, + .reserveIncrementDrops = XRPAmount{50000}, + .extensionComputeLimit = 100, + .extensionSizeLimit = 200, + .gasPrice = 300}; + // Test successful fee transaction with new fields + auto feeTx = createFeeTx(ledger->rules(), ledger->seq(), fields, true); + + OpenView accum(ledger.get()); + BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, feeTx))); + } } void @@ -346,11 +488,11 @@ class FeeVote_test : public beast::unit_test::Suite // Test transaction with missing required legacy fields auto invalidTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), true, false, 1); OpenView accum(ledger.get()); - BEAST_EXPECT(!applyFeeAndTestResult(env, accum, invalidTx)); + BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, invalidTx))); // Test transaction with new format fields when XRPFees is disabled auto disallowedTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), false, true, 2); - BEAST_EXPECT(!applyFeeAndTestResult(env, accum, disallowedTx)); + BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, disallowedTx))); } { @@ -368,11 +510,33 @@ class FeeVote_test : public beast::unit_test::Suite // Test transaction with missing required new fields auto invalidTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), true, false, 3); OpenView accum(ledger.get()); - BEAST_EXPECT(!applyFeeAndTestResult(env, accum, invalidTx)); + BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, invalidTx))); // Test transaction with legacy fields when XRPFees is enabled auto disallowedTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), false, true, 4); - BEAST_EXPECT(!applyFeeAndTestResult(env, accum, disallowedTx)); + BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, disallowedTx))); + } + + { + jtx::Env env(*this, jtx::testable_amendments() | featureXRPFees | featureSmartEscrow); + auto ledger = std::make_shared( + create_genesis, + Rules{env.app().config().features}, + env.app().config().FEES.toFees(), + std::vector{}, + env.app().getNodeFamily()); + + // Create the next ledger to apply transaction to + ledger = std::make_shared(*ledger, env.app().getTimeKeeper().closeTime()); + + // Test transaction with missing required new fields + auto invalidTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), true, false, 5); + OpenView accum(ledger.get()); + BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, invalidTx))); + + // Test transaction with legacy fields when XRPFees is enabled + auto disallowedTx = createInvalidFeeTx(ledger->rules(), ledger->seq(), false, true, 6); + BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, disallowedTx))); } } @@ -411,7 +575,7 @@ class FeeVote_test : public beast::unit_test::Suite // But can be applied to a closed ledger { OpenView closedAccum(ledger.get()); - BEAST_EXPECT(applyFeeAndTestResult(env, closedAccum, feeTx)); + BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, closedAccum, feeTx))); } } @@ -438,7 +602,7 @@ class FeeVote_test : public beast::unit_test::Suite { OpenView accum(ledger.get()); - BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx1)); + BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx1))); accum.apply(*ledger); } @@ -455,7 +619,7 @@ class FeeVote_test : public beast::unit_test::Suite { OpenView accum(ledger.get()); - BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx2)); + BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx2))); accum.apply(*ledger); } @@ -491,7 +655,7 @@ class FeeVote_test : public beast::unit_test::Suite // The transaction should still succeed as long as other fields are // valid // The ledger sequence field is only used for informational purposes - BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx)); + BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx))); } void @@ -517,7 +681,7 @@ class FeeVote_test : public beast::unit_test::Suite { OpenView accum(ledger.get()); - BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx1)); + BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx1))); accum.apply(*ledger); } @@ -532,7 +696,7 @@ class FeeVote_test : public beast::unit_test::Suite { OpenView accum(ledger.get()); - BEAST_EXPECT(applyFeeAndTestResult(env, accum, feeTx2)); + BEAST_EXPECT(isTesSuccess(applyFeeAndTestResult(env, accum, feeTx2))); accum.apply(*ledger); } @@ -567,7 +731,7 @@ class FeeVote_test : public beast::unit_test::Suite }); OpenView accum(ledger.get()); - BEAST_EXPECT(!applyFeeAndTestResult(env, accum, invalidTx)); + BEAST_EXPECT(!isTesSuccess(applyFeeAndTestResult(env, accum, invalidTx))); } void @@ -729,6 +893,143 @@ class FeeVote_test : public beast::unit_test::Suite feeTx.getFieldAmount(sfReserveIncrementDrops) == XRPAmount{setup.ownerReserve}); } + void + testDoVotingSmartEscrow() + { + testcase("doVoting with Smart Escrow"); + + using namespace jtx; + + Env env(*this, testable_amendments() | featureXRPFees | featureSmartEscrow); + + // establish what the current fees are + BEAST_EXPECT(env.current()->fees().base == XRPAmount{UNIT_TEST_REFERENCE_FEE}); + BEAST_EXPECT(env.current()->fees().reserve == XRPAmount{200'000'000}); + BEAST_EXPECT(env.current()->fees().increment == XRPAmount{50'000'000}); + BEAST_EXPECT(env.current()->fees().extensionComputeLimit == 0); + BEAST_EXPECT(env.current()->fees().extensionSizeLimit == 0); + BEAST_EXPECT(env.current()->fees().gasPrice == 0); + + auto const createFeeTxFromVoting = + [&](FeeSetup const& setup) -> std::pair> { + auto feeVote = make_FeeVote(setup, env.app().getJournal("FeeVote")); + auto ledger = std::make_shared( + create_genesis, + Rules{env.app().config().features}, + env.app().config().FEES.toFees(), + std::vector{}, + env.app().getNodeFamily()); + + // doVoting requires a flag ledger (every 256th ledger) + // We need to create a ledger at sequence 256 to make it a flag + // ledger + for (int i = 0; i < 256 - 1; ++i) + { + ledger = std::make_shared(*ledger, env.app().getTimeKeeper().closeTime()); + } + BEAST_EXPECT(ledger->isFlagLedger()); + + // Create some mock validations with fee votes + std::vector> validations; + + for (int i = 0; i < 5; i++) + { + auto sec = randomSecretKey(); + auto pub = derivePublicKey(KeyType::secp256k1, sec); + + auto val = std::make_shared( + env.app().getTimeKeeper().now(), + pub, + sec, + calcNodeID(pub), + [&](STValidation& v) { + v.setFieldU32(sfLedgerSequence, ledger->seq()); + // Vote for different fees than current + v.setFieldAmount(sfBaseFeeDrops, XRPAmount{setup.referenceFee}); + v.setFieldAmount(sfReserveBaseDrops, XRPAmount{setup.accountReserve}); + v.setFieldAmount(sfReserveIncrementDrops, XRPAmount{setup.ownerReserve}); + v.setFieldU32(sfExtensionComputeLimit, setup.extension_compute_limit); + v.setFieldU32(sfExtensionSizeLimit, setup.extension_size_limit); + v.setFieldU32(sfGasPrice, setup.gas_price); + }); + if (i % 2) + val->setTrusted(); + validations.push_back(val); + } + + auto txSet = + std::make_shared(SHAMapType::TRANSACTION, env.app().getNodeFamily()); + + // This should not throw since we have a flag ledger + feeVote->doVoting(ledger, validations, txSet); + + auto const txs = getTxs(txSet); + BEAST_EXPECT(txs.size() == 1); + return {txs[0], ledger}; + }; + + auto checkFeeTx = [&](FeeSetup const& setup, + STTx const& feeTx, + std::shared_ptr const& ledger, + std::source_location const loc = std::source_location::current()) { + auto const line = " (" + std::to_string(loc.line()) + ")"; + BEAST_EXPECTS(feeTx.getTxnType() == ttFEE, line); + + BEAST_EXPECTS(feeTx.getAccountID(sfAccount) == AccountID(), line); + BEAST_EXPECTS(feeTx.getFieldU32(sfLedgerSequence) == ledger->seq() + 1, line); + + BEAST_EXPECTS(feeTx.isFieldPresent(sfBaseFeeDrops), line); + BEAST_EXPECTS(feeTx.isFieldPresent(sfReserveBaseDrops), line); + BEAST_EXPECTS(feeTx.isFieldPresent(sfReserveIncrementDrops), line); + + // The legacy fields should NOT be present + BEAST_EXPECTS(!feeTx.isFieldPresent(sfBaseFee), line); + BEAST_EXPECTS(!feeTx.isFieldPresent(sfReserveBase), line); + BEAST_EXPECTS(!feeTx.isFieldPresent(sfReserveIncrement), line); + BEAST_EXPECTS(!feeTx.isFieldPresent(sfReferenceFeeUnits), line); + + // Check the values + BEAST_EXPECTS( + feeTx.getFieldAmount(sfBaseFeeDrops) == XRPAmount{setup.referenceFee}, line); + BEAST_EXPECTS( + feeTx.getFieldAmount(sfReserveBaseDrops) == XRPAmount{setup.accountReserve}, line); + BEAST_EXPECTS( + feeTx.getFieldAmount(sfReserveIncrementDrops) == XRPAmount{setup.ownerReserve}, + line); + BEAST_EXPECTS( + feeTx.getFieldU32(sfExtensionComputeLimit) == setup.extension_compute_limit, line); + BEAST_EXPECTS( + feeTx.getFieldU32(sfExtensionSizeLimit) == setup.extension_size_limit, line); + BEAST_EXPECTS(feeTx.getFieldU32(sfGasPrice) == setup.gas_price, line); + }; + + { + FeeSetup setup; + setup.referenceFee = 42; + setup.accountReserve = 1234567; + setup.ownerReserve = 7654321; + setup.extension_compute_limit = 100; + setup.extension_size_limit = 200; + setup.gas_price = 300; + auto const [feeTx, ledger] = createFeeTxFromVoting(setup); + + checkFeeTx(setup, feeTx, ledger); + } + + { + FeeSetup setup; + setup.referenceFee = 42; + setup.accountReserve = 1234567; + setup.ownerReserve = 7654321; + setup.extension_compute_limit = 0; + setup.extension_size_limit = 0; + setup.gas_price = 300; + auto const [feeTx, ledger] = createFeeTxFromVoting(setup); + + checkFeeTx(setup, feeTx, ledger); + } + } + void run() override { @@ -742,6 +1043,7 @@ class FeeVote_test : public beast::unit_test::Suite testSingleInvalidTransaction(); testDoValidation(); testDoVoting(); + testDoVotingSmartEscrow(); } }; diff --git a/src/test/app/HostFuncImpl_test.cpp b/src/test/app/HostFuncImpl_test.cpp new file mode 100644 index 00000000000..e46634ca447 --- /dev/null +++ b/src/test/app/HostFuncImpl_test.cpp @@ -0,0 +1,2916 @@ +#include + +#include +#include + +namespace xrpl { +namespace test { + +static Bytes +toBytes(std::uint8_t value) +{ + return {value}; +} + +static Bytes +toBytes(std::uint16_t value) +{ + auto const* b = reinterpret_cast(&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(Asset const& asset) +{ + if (asset.holds()) + { + Serializer s; + auto const& issue = asset.get(); + s.addBitString(issue.currency); + if (!isXRP(issue.currency)) + s.addBitString(issue.account); + auto const data = s.getData(); + return data; + } + + auto const& mptIssue = asset.get(); + auto const& mptID = mptIssue.getMptID(); + return Bytes{mptID.cbegin(), mptID.cend()}; +} + +static Bytes +toBytes(STAmount const& amount) +{ + Serializer msg; + amount.add(msg); + auto const data = msg.getData(); + + return data; +} + +static ApplyContext +createApplyContext( + test::jtx::Env& env, + OpenView& ov, + beast::Journal j, + STTx const& tx = STTx(ttESCROW_FINISH, [](STObject&) {})) +{ + ApplyContext ac{env.app(), ov, tx, tesSUCCESS, env.current()->fees().base, tapNONE, j}; + return ac; +} + +static ApplyContext +createApplyContext( + test::jtx::Env& env, + OpenView& ov, + STTx const& tx = STTx(ttESCROW_FINISH, [](STObject&) {})) +{ + return createApplyContext(env, ov, env.journal, tx); +} + +struct HostFuncImpl_test : public beast::unit_test::suite +{ + void + testGetLedgerSqn() + { + testcase("getLedgerSqn"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + auto const result = hfs.getLedgerSqn(); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == env.current()->header().seq); + } + + void + testGetParentLedgerTime() + { + testcase("getParentLedgerTime"); + using namespace test::jtx; + + Env env{*this}; + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + + { + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + auto const result = hfs.getParentLedgerTime(); + if (BEAST_EXPECT(result.has_value())) + { + BEAST_EXPECT( + result.value() == env.current()->parentCloseTime().time_since_epoch().count()); + } + } + } + + void + testGetParentLedgerHash() + { + testcase("getParentLedgerHash"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + auto const result = hfs.getParentLedgerHash(); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == env.current()->header().parentHash); + } + + void + testGetBaseFee() + { + testcase("getBaseFee"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + auto const result = hfs.getBaseFee(); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == env.current()->fees().base.drops()); + } + + void + testIsAmendmentEnabled() + { + testcase("isAmendmentEnabled"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + // Use featureTokenEscrow for testing + auto const amendmentId = featureTokenEscrow; + + // Test by id + { + auto const result = hfs.isAmendmentEnabled(amendmentId); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == 1); + } + + // Test by name + std::string const amendmentName = "TokenEscrow"; + { + auto const result = hfs.isAmendmentEnabled(amendmentName); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == 1); + } + + // Test with a fake amendment id (all zeros) + uint256 const fakeId; + { + auto const result = hfs.isAmendmentEnabled(fakeId); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == 0); + } + + // Test with a fake amendment name + std::string const fakeName = "FakeAmendment"; + { + auto const result = hfs.isAmendmentEnabled(fakeName); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == 0); + } + } + + void + testCacheLedgerObj() + { + testcase("cacheLedgerObj"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, 2); + auto const accountKeylet = keylet::account(env.master); + { + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + BEAST_EXPECT( + hfs.cacheLedgerObj(accountKeylet.key, -1).error() == + HostFunctionError::SLOT_OUT_RANGE); + BEAST_EXPECT( + hfs.cacheLedgerObj(accountKeylet.key, 257).error() == + HostFunctionError::SLOT_OUT_RANGE); + BEAST_EXPECT( + hfs.cacheLedgerObj(dummyEscrow.key, 0).error() == + HostFunctionError::LEDGER_OBJ_NOT_FOUND); + BEAST_EXPECT(hfs.cacheLedgerObj(accountKeylet.key, 0).value() == 1); + + for (int i = 1; i <= 256; ++i) + { + auto const result = hfs.cacheLedgerObj(accountKeylet.key, i); + BEAST_EXPECT(result.has_value() && result.value() == i); + } + BEAST_EXPECT( + hfs.cacheLedgerObj(accountKeylet.key, 0).error() == HostFunctionError::SLOTS_FULL); + } + + { + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + for (int i = 1; i <= 256; ++i) + { + auto const result = hfs.cacheLedgerObj(accountKeylet.key, 0); + BEAST_EXPECT(result.has_value() && result.value() == i); + } + BEAST_EXPECT( + hfs.cacheLedgerObj(accountKeylet.key, 0).error() == HostFunctionError::SLOTS_FULL); + } + } + + void + testGetTxField() + { + testcase("getTxField"); + using namespace test::jtx; + + std::string const credIdHex = + "0011223344556677889900112233445566778899001122334455667788990011"; + uint256 credId; + BEAST_EXPECT(credId.parseHex(credIdHex)); + + Env env{*this}; + OpenView ov{*env.current()}; + STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) { + obj.setAccountID(sfAccount, env.master.id()); + obj.setAccountID(sfOwner, env.master.id()); + obj.setFieldU32(sfOfferSequence, env.seq(env.master)); + obj.setFieldArray(sfMemos, STArray{}); + STVector256 credIds; + credIds.push_back(credId); + obj.setFieldV256(sfCredentialIDs, credIds); + }); + ApplyContext ac = createApplyContext(env, ov, stx); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + + { + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + auto const account = hfs.getTxField(sfAccount); + BEAST_EXPECT(account && std::ranges::equal(*account, env.master.id())); + + auto const owner = hfs.getTxField(sfOwner); + BEAST_EXPECT(owner && std::ranges::equal(*owner, env.master.id())); + + auto const txType = hfs.getTxField(sfTransactionType); + BEAST_EXPECT(txType && *txType == toBytes(ttESCROW_FINISH)); + + auto const offerSeq = hfs.getTxField(sfOfferSequence); + BEAST_EXPECT(offerSeq && *offerSeq == toBytes(env.seq(env.master))); + + auto const notPresent = hfs.getTxField(sfDestination); + if (BEAST_EXPECT(!notPresent.has_value())) + BEAST_EXPECT(notPresent.error() == HostFunctionError::FIELD_NOT_FOUND); + + auto const memos = hfs.getTxField(sfMemos); + if (BEAST_EXPECT(!memos.has_value())) + BEAST_EXPECT(memos.error() == HostFunctionError::NOT_LEAF_FIELD); + + auto const credentialIds = hfs.getTxField(sfCredentialIDs); + if (BEAST_EXPECT(!credentialIds.has_value())) + { + BEAST_EXPECTS( + credentialIds.error() == HostFunctionError::NOT_LEAF_FIELD, + std::to_string(HfErrorToInt(credentialIds.error()))); + } + + auto const nonField = hfs.getTxField(sfInvalid); + if (BEAST_EXPECT(!nonField.has_value())) + BEAST_EXPECT(nonField.error() == HostFunctionError::FIELD_NOT_FOUND); + + auto const nonField2 = hfs.getTxField(sfGeneric); + if (BEAST_EXPECT(!nonField2.has_value())) + BEAST_EXPECT(nonField2.error() == HostFunctionError::FIELD_NOT_FOUND); + } + + { + auto const iouAsset = env.master["USD"]; + STTx const stx2 = STTx(ttAMM_DEPOSIT, [&](auto& obj) { + obj.setAccountID(sfAccount, env.master.id()); + obj.setFieldIssue(sfAsset, STIssue{sfAsset, xrpIssue()}); + obj.setFieldIssue(sfAsset2, STIssue{sfAsset2, iouAsset.issue()}); + }); + ApplyContext ac2 = createApplyContext(env, ov, stx2); + WasmHostFunctionsImpl const hfs(ac2, dummyEscrow); + + auto const asset = hfs.getTxField(sfAsset); + std::vector const expectedAsset(20, 0); + BEAST_EXPECT(asset && *asset == expectedAsset); + + auto const asset2 = hfs.getTxField(sfAsset2); + BEAST_EXPECT(asset2 && *asset2 == toBytes(Asset(iouAsset))); + } + + { + auto const iouAsset = env.master["GBP"]; + auto const mptId = makeMptID(1, env.master); + STTx const stx2 = STTx(ttAMM_DEPOSIT, [&](auto& obj) { + obj.setAccountID(sfAccount, env.master.id()); + obj.setFieldIssue(sfAsset, STIssue{sfAsset, iouAsset.issue()}); + obj.setFieldIssue(sfAsset2, STIssue{sfAsset2, MPTIssue{mptId}}); + }); + ApplyContext ac2 = createApplyContext(env, ov, stx2); + WasmHostFunctionsImpl const hfs(ac2, dummyEscrow); + + auto const asset = hfs.getTxField(sfAsset); + if (BEAST_EXPECT(asset.has_value())) + { + BEAST_EXPECT(*asset == toBytes(Asset(iouAsset))); + } + + auto const asset2 = hfs.getTxField(sfAsset2); + if (BEAST_EXPECT(asset2.has_value())) + { + BEAST_EXPECT(*asset2 == toBytes(Asset(mptId))); + } + } + + { + std::uint8_t const expectedScale = 8; + STTx const stx2 = STTx(ttMPTOKEN_ISSUANCE_CREATE, [&](auto& obj) { + obj.setAccountID(sfAccount, env.master.id()); + obj.setFieldU8(sfAssetScale, expectedScale); + }); + ApplyContext ac2 = createApplyContext(env, ov, stx2); + WasmHostFunctionsImpl const hfs(ac2, dummyEscrow); + + auto const actualScale = hfs.getTxField(sfAssetScale); + if (BEAST_EXPECT(actualScale.has_value())) + { + BEAST_EXPECT(std::ranges::equal(*actualScale, toBytes(expectedScale))); + } + } + } + + void + testGetCurrentLedgerObjField() + { + testcase("getCurrentLedgerObjField"); + using namespace test::jtx; + using namespace std::chrono; + + Env env{*this}; + + // Fund the account and create an escrow so the ledger object exists + env(escrow::create(env.master, env.master, XRP(100)), escrow::finish_time(env.now() + 1s)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + // Find the escrow ledger object + auto const escrowKeylet = keylet::escrow(env.master, env.seq(env.master) - 1); + BEAST_EXPECT(env.le(escrowKeylet)); + + WasmHostFunctionsImpl const hfs(ac, escrowKeylet); + + // Should return the Account field from the escrow ledger object + auto const account = hfs.getCurrentLedgerObjField(sfAccount); + if (BEAST_EXPECTS(account.has_value(), std::to_string(static_cast(account.error())))) + BEAST_EXPECT(std::ranges::equal(*account, env.master.id())); + + // Should return the Amount field from the escrow ledger object + auto const amountField = hfs.getCurrentLedgerObjField(sfAmount); + if (BEAST_EXPECT(amountField.has_value())) + { + BEAST_EXPECT(*amountField == toBytes(XRP(100))); + } + + // Should return the PreviousTxnID field from the escrow ledger object + auto const previousTxnId = hfs.getCurrentLedgerObjField(sfPreviousTxnID); + if (BEAST_EXPECT(previousTxnId.has_value())) + { + BEAST_EXPECT(*previousTxnId == toBytes(env.tx()->getTransactionID())); + } + + // Should return nullopt for a field not present + auto const notPresent = hfs.getCurrentLedgerObjField(sfOwner); + BEAST_EXPECT( + !notPresent.has_value() && notPresent.error() == HostFunctionError::FIELD_NOT_FOUND); + + { + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master) + 5); + WasmHostFunctionsImpl const hfs2(ac, dummyEscrow); + auto const account = hfs2.getCurrentLedgerObjField(sfAccount); + if (BEAST_EXPECT(!account.has_value())) + { + BEAST_EXPECT(account.error() == HostFunctionError::LEDGER_OBJ_NOT_FOUND); + } + } + } + + void + testGetLedgerObjField() + { + testcase("getLedgerObjField"); + using namespace test::jtx; + using namespace std::chrono; + + Env env{*this}; + // Fund the account and create an escrow so the ledger object exists + env(escrow::create(env.master, env.master, XRP(100)), escrow::finish_time(env.now() + 1s)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const accountKeylet = keylet::account(env.master.id()); + auto const escrowKeylet = keylet::escrow(env.master.id(), env.seq(env.master) - 1); + WasmHostFunctionsImpl hfs(ac, escrowKeylet); + + // Cache the escrow ledger object in slot 1 + auto cacheResult = hfs.cacheLedgerObj(accountKeylet.key, 1); + BEAST_EXPECT(cacheResult.has_value() && cacheResult.value() == 1); + + // Should return the Account field from the cached ledger object + auto const account = hfs.getLedgerObjField(1, sfAccount); + if (BEAST_EXPECTS(account.has_value(), std::to_string(static_cast(account.error())))) + BEAST_EXPECT(std::ranges::equal(*account, env.master.id())); + + // Should return the Balance field from the cached ledger object + auto const balanceField = hfs.getLedgerObjField(1, sfBalance); + if (BEAST_EXPECT(balanceField.has_value())) + { + BEAST_EXPECT(*balanceField == toBytes(env.balance(env.master))); + } + + // Should return error for slot out of range + auto const outOfRange = hfs.getLedgerObjField(0, sfAccount); + BEAST_EXPECT( + !outOfRange.has_value() && outOfRange.error() == HostFunctionError::SLOT_OUT_RANGE); + + auto const tooHigh = hfs.getLedgerObjField(257, sfAccount); + BEAST_EXPECT(!tooHigh.has_value() && tooHigh.error() == HostFunctionError::SLOT_OUT_RANGE); + + // Should return error for empty slot + auto const emptySlot = hfs.getLedgerObjField(2, sfAccount); + BEAST_EXPECT(!emptySlot.has_value() && emptySlot.error() == HostFunctionError::EMPTY_SLOT); + + // Should return error for field not present + auto const notPresent = hfs.getLedgerObjField(1, sfOwner); + BEAST_EXPECT( + !notPresent.has_value() && notPresent.error() == HostFunctionError::FIELD_NOT_FOUND); + } + + void + testGetTxNestedField() + { + testcase("getTxNestedField"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + + std::string const credIdHex = + "0011223344556677889900112233445566778899001122334455667788990011"; + uint256 credId; + BEAST_EXPECT(credId.parseHex(credIdHex)); + + // Create a transaction with a nested array field + STTx const stx = STTx(ttESCROW_FINISH, [&](auto& obj) { + obj.setAccountID(sfAccount, env.master.id()); + STArray memos; + STObject memoObj(sfMemo); + memoObj.setFieldVL(sfMemoData, Slice("hello", 5)); + memos.push_back(memoObj); + obj.setFieldArray(sfMemos, memos); + STVector256 credIds; + credIds.push_back(credId); + obj.setFieldV256(sfCredentialIDs, credIds); + }); + + ApplyContext ac = createApplyContext(env, ov, stx); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + { + // Locator for sfMemos[0].sfMemo.sfMemoData + // Locator is a sequence of int32_t codes: + // [sfMemos.fieldCode, 0, sfMemoData.fieldCode] + std::vector locatorVec = {sfMemos.fieldCode, 0, sfMemoData.fieldCode}; + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + + auto const result = hfs.getTxNestedField(locator); + if (BEAST_EXPECTS(result.has_value(), std::to_string(static_cast(result.error())))) + { + std::string const memoData(result.value().begin(), result.value().end()); + BEAST_EXPECT(memoData == "hello"); + } + } + + { + // Locator for sfCredentialIDs[0] + std::vector locatorVec = {sfCredentialIDs.fieldCode, 0}; + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + + auto const result = hfs.getTxNestedField(locator); + if (BEAST_EXPECTS(result.has_value(), std::to_string(static_cast(result.error())))) + { + std::string const credIdResult(result.value().begin(), result.value().end()); + BEAST_EXPECT(strHex(credIdResult) == credIdHex); + } + } + + { + // can use the nested locator for base fields too + std::vector locatorVec = {sfAccount.fieldCode}; + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + + auto const account = hfs.getTxNestedField(locator); + if (BEAST_EXPECTS( + account.has_value(), std::to_string(static_cast(account.error())))) + { + BEAST_EXPECT(std::ranges::equal(*account, env.master.id())); + } + } + + { + // unaligned locator + std::vector locatorVec(sizeof(int32_t) + 1); + memcpy(locatorVec.data() + 1, &sfAccount.fieldCode, sizeof(int32_t)); + Slice const locator( + reinterpret_cast(locatorVec.data() + 1), sizeof(int32_t)); + + auto const account = hfs.getTxNestedField(locator); + if (BEAST_EXPECTS( + account.has_value(), std::to_string(static_cast(account.error())))) + { + BEAST_EXPECT(std::ranges::equal(*account, env.master.id())); + } + } + + auto expectError = [&](std::vector const& locatorVec, + HostFunctionError expectedError) { + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result = hfs.getTxNestedField(locator); + if (BEAST_EXPECT(!result.has_value())) + { + BEAST_EXPECTS( + result.error() == expectedError, + std::to_string(static_cast(result.error()))); + } + }; + // Locator for non-existent base field + expectError( + {sfSigners.fieldCode, // sfSigners does not exist + 0, + sfAccount.fieldCode}, + HostFunctionError::FIELD_NOT_FOUND); + + // Locator for non-existent index + expectError( + {sfMemos.fieldCode, + 1, // index 1 does not exist + sfMemoData.fieldCode}, + HostFunctionError::INDEX_OUT_OF_BOUNDS); + + // Locator for non-existent index + expectError( + {sfCredentialIDs.fieldCode, 1}, // index 1 does not exist + HostFunctionError::INDEX_OUT_OF_BOUNDS); + + // Locator for negative index (STArray) + expectError( + {sfMemos.fieldCode, + -1, // negative index + sfMemoData.fieldCode}, + HostFunctionError::INDEX_OUT_OF_BOUNDS); + + // Locator for negative index (STVector256) + expectError( + {sfCredentialIDs.fieldCode, -1}, // negative index + HostFunctionError::INDEX_OUT_OF_BOUNDS); + + // Locator for non-existent nested field + expectError( + {sfMemos.fieldCode, 0, sfURI.fieldCode}, // sfURI does not exist in the memo + HostFunctionError::FIELD_NOT_FOUND); + + // Locator for non-existent base sfield + expectError( + {field_code(20000, 20000), // nonexistent SField code + 0, + sfAccount.fieldCode}, + HostFunctionError::INVALID_FIELD); + + // Locator for non-existent nested sfield + expectError( + {sfMemos.fieldCode, // nonexistent SField code + 0, + field_code(20000, 20000)}, + HostFunctionError::INVALID_FIELD); + + // Locator for negative base sfield code (-1 = sfInvalid, exists in map but not in tx) + expectError( + {-1, // sfInvalid's field code + 0, + sfAccount.fieldCode}, + HostFunctionError::FIELD_NOT_FOUND); + + // Locator for zero base sfield code (0 = sfGeneric, exists in map but not in tx) + expectError( + {0, // sfGeneric's field code + 0, + sfAccount.fieldCode}, + HostFunctionError::FIELD_NOT_FOUND); + + // Locator for very negative base sfield code (not in knownCodeToField map) + expectError( + {std::numeric_limits::min(), 0, sfAccount.fieldCode}, + HostFunctionError::INVALID_FIELD); + + // Locator for negative nested sfield code in STObject context + // (sfMemos[0] is an STObject, then -1 is looked up as SField) + expectError( + {sfMemos.fieldCode, 0, -1}, // -1 = sfInvalid, exists in map but not in memo object + HostFunctionError::FIELD_NOT_FOUND); + + // Locator for STArray + expectError({sfMemos.fieldCode}, HostFunctionError::NOT_LEAF_FIELD); + + // Locator for STVector256 + expectError({sfCredentialIDs.fieldCode}, HostFunctionError::NOT_LEAF_FIELD); + + // Locator for nesting into non-array/object field + expectError( + {sfAccount.fieldCode, // sfAccount is not an array or object + 0, + sfAccount.fieldCode}, + HostFunctionError::LOCATOR_MALFORMED); + + // Locator for empty locator + expectError({}, HostFunctionError::LOCATOR_MALFORMED); + + // Locator for malformed locator (not multiple of 4) + { + std::vector locatorVec = {sfMemos.fieldCode}; + Slice const malformedLocator(reinterpret_cast(locatorVec.data()), 3); + auto const malformedResult = hfs.getTxNestedField(malformedLocator); + BEAST_EXPECT( + !malformedResult.has_value() && + malformedResult.error() == HostFunctionError::LOCATOR_MALFORMED); + } + } + + void + testGetCurrentLedgerObjNestedField() + { + testcase("getCurrentLedgerObjNestedField"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const becky("becky"); + // Create a SignerList for env.master + env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + // Find the signer ledger object + auto const signerKeylet = keylet::signers(env.master.id()); + BEAST_EXPECT(env.le(signerKeylet)); + + WasmHostFunctionsImpl hfs(ac, signerKeylet); + + // Locator for base field + std::vector baseLocator = {sfSignerQuorum.fieldCode}; + Slice const baseLocatorSlice( + reinterpret_cast(baseLocator.data()), + baseLocator.size() * sizeof(int32_t)); + auto const signerQuorum = hfs.getCurrentLedgerObjNestedField(baseLocatorSlice); + if (BEAST_EXPECTS( + signerQuorum.has_value(), std::to_string(static_cast(signerQuorum.error())))) + { + BEAST_EXPECT(*signerQuorum == toBytes(static_cast(2))); + } + + auto expectError = [&](std::vector const& locatorVec, + HostFunctionError expectedError) { + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result = hfs.getCurrentLedgerObjNestedField(locator); + if (BEAST_EXPECT(!result.has_value())) + { + BEAST_EXPECTS( + result.error() == expectedError, + std::to_string(static_cast(result.error()))); + } + }; + // Locator for non-existent base field + expectError( + {sfSigners.fieldCode, // sfSigners does not exist + 0, + sfAccount.fieldCode}, + HostFunctionError::FIELD_NOT_FOUND); + // Locator for nesting into non-array/object field + expectError( + {sfSignerQuorum.fieldCode, // sfSignerQuorum is not an array or object + 0, + sfAccount.fieldCode}, + HostFunctionError::LOCATOR_MALFORMED); + + // Locator for empty locator + Slice const emptyLocator(nullptr, 0); + auto const emptyResult = hfs.getCurrentLedgerObjNestedField(emptyLocator); + BEAST_EXPECT( + !emptyResult.has_value() && + emptyResult.error() == HostFunctionError::LOCATOR_MALFORMED); + + // Locator for malformed locator (not multiple of 4) + std::vector malformedLocatorVec = {sfMemos.fieldCode}; + Slice const malformedLocator( + reinterpret_cast(malformedLocatorVec.data()), 3); + auto const malformedResult = hfs.getCurrentLedgerObjNestedField(malformedLocator); + BEAST_EXPECT( + !malformedResult.has_value() && + malformedResult.error() == HostFunctionError::LOCATOR_MALFORMED); + + { + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master) + 5); + WasmHostFunctionsImpl const dummyHfs(ac, dummyEscrow); + std::vector const locatorVec = {sfAccount.fieldCode}; + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result = dummyHfs.getCurrentLedgerObjNestedField(locator); + if (BEAST_EXPECT(!result.has_value())) + { + BEAST_EXPECTS( + result.error() == HostFunctionError::LEDGER_OBJ_NOT_FOUND, + std::to_string(static_cast(result.error()))); + } + } + } + + void + testGetLedgerObjNestedField() + { + testcase("getLedgerObjNestedField"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const becky("becky"); + // Create a SignerList for env.master + env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + // Cache the SignerList ledger object in slot 1 + auto const signerListKeylet = keylet::signers(env.master.id()); + auto cacheResult = hfs.cacheLedgerObj(signerListKeylet.key, 1); + BEAST_EXPECT(cacheResult.has_value() && cacheResult.value() == 1); + + // Locator for sfSignerEntries[0].sfAccount + { + std::vector const locatorVec = { + sfSignerEntries.fieldCode, 0, sfAccount.fieldCode}; + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + + auto const result = hfs.getLedgerObjNestedField(1, locator); + if (BEAST_EXPECTS(result.has_value(), std::to_string(static_cast(result.error())))) + { + BEAST_EXPECT(std::ranges::equal(*result, alice.id())); + } + } + + // Locator for sfSignerEntries[1].sfAccount + { + std::vector const locatorVec = { + sfSignerEntries.fieldCode, 1, sfAccount.fieldCode}; + Slice const locator = Slice( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result2 = hfs.getLedgerObjNestedField(1, locator); + if (BEAST_EXPECTS( + result2.has_value(), std::to_string(static_cast(result2.error())))) + { + BEAST_EXPECT(std::ranges::equal(*result2, becky.id())); + } + } + + // Locator for sfSignerEntries[0].sfSignerWeight + { + std::vector const locatorVec = { + sfSignerEntries.fieldCode, 0, sfSignerWeight.fieldCode}; + Slice const locator = Slice( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const weightResult = hfs.getLedgerObjNestedField(1, locator); + if (BEAST_EXPECTS( + weightResult.has_value(), + std::to_string(static_cast(weightResult.error())))) + { + // Should be 1 + auto const expected = toBytes(static_cast(1)); + BEAST_EXPECT(*weightResult == expected); + } + } + + // Locator for base field sfSignerQuorum + { + std::vector const locatorVec = {sfSignerQuorum.fieldCode}; + Slice const locator = Slice( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const quorumResult = hfs.getLedgerObjNestedField(1, locator); + if (BEAST_EXPECTS( + quorumResult.has_value(), + std::to_string(static_cast(quorumResult.error())))) + { + auto const expected = toBytes(static_cast(2)); + BEAST_EXPECT(*quorumResult == expected); + } + } + + // Helper for error checks + auto expectError = [&](std::vector const& locatorVec, + HostFunctionError expectedError, + int slot = 1) { + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result = hfs.getLedgerObjNestedField(slot, locator); + if (BEAST_EXPECT(!result.has_value())) + { + BEAST_EXPECTS( + result.error() == expectedError, + std::to_string(static_cast(result.error()))); + } + }; + + // Error: base field not found + expectError( + {sfSigners.fieldCode, // sfSigners does not exist + 0, + sfAccount.fieldCode}, + HostFunctionError::FIELD_NOT_FOUND); + + // Error: index out of bounds + expectError( + {sfSignerEntries.fieldCode, + 2, // index 2 does not exist + sfAccount.fieldCode}, + HostFunctionError::INDEX_OUT_OF_BOUNDS); + + // Error: nested field not found + expectError( + { + sfSignerEntries.fieldCode, + 0, + sfDestination.fieldCode // sfDestination does not exist + }, + HostFunctionError::FIELD_NOT_FOUND); + + // Error: invalid field code + expectError( + {field_code(99999, 99999), 0, sfAccount.fieldCode}, HostFunctionError::INVALID_FIELD); + + // Error: invalid nested field code + expectError( + {sfSignerEntries.fieldCode, 0, field_code(99999, 99999)}, + HostFunctionError::INVALID_FIELD); + + // Error: slot out of range + expectError({sfSignerQuorum.fieldCode}, HostFunctionError::SLOT_OUT_RANGE, 0); + expectError({sfSignerQuorum.fieldCode}, HostFunctionError::SLOT_OUT_RANGE, 257); + + // Error: empty slot + expectError({sfSignerQuorum.fieldCode}, HostFunctionError::EMPTY_SLOT, 2); + + // Error: locator for STArray (not leaf field) + expectError({sfSignerEntries.fieldCode}, HostFunctionError::NOT_LEAF_FIELD); + + // Error: nesting into non-array/object field + expectError( + {sfSignerQuorum.fieldCode, 0, sfAccount.fieldCode}, + HostFunctionError::LOCATOR_MALFORMED); + + // Error: empty locator + expectError({}, HostFunctionError::LOCATOR_MALFORMED); + + // Error: locator malformed (not multiple of 4) + std::vector const locatorVec = {sfSignerEntries.fieldCode}; + Slice const locator = Slice(reinterpret_cast(locatorVec.data()), 3); + auto const malformed = hfs.getLedgerObjNestedField(1, locator); + BEAST_EXPECT( + !malformed.has_value() && malformed.error() == HostFunctionError::LOCATOR_MALFORMED); + } + + void + testGetTxArrayLen() + { + testcase("getTxArrayLen"); + using namespace test::jtx; + + 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.push_back(credId); + obj.setFieldV256(sfCredentialIDs, credIds); + }); + + ApplyContext ac = createApplyContext(env, ov, stx); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + // Should return 1 for sfMemos + auto const memosLen = hfs.getTxArrayLen(sfMemos); + if (BEAST_EXPECT(memosLen.has_value())) + BEAST_EXPECT(memosLen.value() == 2); + + // Should return error for non-array field + auto const notArray = hfs.getTxArrayLen(sfAccount); + if (BEAST_EXPECT(!notArray.has_value())) + BEAST_EXPECT(notArray.error() == HostFunctionError::NO_ARRAY); + + // Should return error for missing array field + auto const missingArray = hfs.getTxArrayLen(sfSigners); + if (BEAST_EXPECT(!missingArray.has_value())) + BEAST_EXPECT(missingArray.error() == HostFunctionError::FIELD_NOT_FOUND); + + // Should return 1 for sfCredentialIDs + auto const credIdsLen = hfs.getTxArrayLen(sfCredentialIDs); + if (BEAST_EXPECT(credIdsLen.has_value())) + BEAST_EXPECT(credIdsLen.value() == 1); + } + + void + testGetCurrentLedgerObjArrayLen() + { + testcase("getCurrentLedgerObjArrayLen"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const becky("becky"); + // Create a SignerList for env.master + env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const signerKeylet = keylet::signers(env.master.id()); + WasmHostFunctionsImpl const hfs(ac, signerKeylet); + + auto const entriesLen = hfs.getCurrentLedgerObjArrayLen(sfSignerEntries); + if (BEAST_EXPECT(entriesLen.has_value())) + BEAST_EXPECT(entriesLen.value() == 2); + + auto const arrLen = hfs.getCurrentLedgerObjArrayLen(sfMemos); + if (BEAST_EXPECT(!arrLen.has_value())) + BEAST_EXPECT(arrLen.error() == HostFunctionError::FIELD_NOT_FOUND); + + // Should return NO_ARRAY for non-array field + auto const notArray = hfs.getCurrentLedgerObjArrayLen(sfAccount); + if (BEAST_EXPECT(!notArray.has_value())) + BEAST_EXPECT(notArray.error() == HostFunctionError::NO_ARRAY); + + { + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master) + 5); + WasmHostFunctionsImpl const dummyHfs(ac, dummyEscrow); + auto const len = dummyHfs.getCurrentLedgerObjArrayLen(sfMemos); + if (BEAST_EXPECT(!len.has_value())) + BEAST_EXPECT(len.error() == HostFunctionError::LEDGER_OBJ_NOT_FOUND); + } + } + + void + testGetLedgerObjArrayLen() + { + testcase("getLedgerObjArrayLen"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const becky("becky"); + // Create a SignerList for env.master + env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto const signerListKeylet = keylet::signers(env.master.id()); + auto cacheResult = hfs.cacheLedgerObj(signerListKeylet.key, 1); + BEAST_EXPECT(cacheResult.has_value() && cacheResult.value() == 1); + + { + auto const arrLen = hfs.getLedgerObjArrayLen(1, sfSignerEntries); + if (BEAST_EXPECT(arrLen.has_value())) + { + // Should return 2 for sfSignerEntries + BEAST_EXPECT(arrLen.value() == 2); + } + } + { + auto const arrLen = hfs.getLedgerObjArrayLen(0, sfSignerEntries); + if (BEAST_EXPECT(!arrLen.has_value())) + BEAST_EXPECT(arrLen.error() == HostFunctionError::SLOT_OUT_RANGE); + } + + { + // Should return error for non-array field + auto const notArray = hfs.getLedgerObjArrayLen(1, sfAccount); + if (BEAST_EXPECT(!notArray.has_value())) + BEAST_EXPECT(notArray.error() == HostFunctionError::NO_ARRAY); + } + + { + // Should return error for empty slot + auto const emptySlot = hfs.getLedgerObjArrayLen(2, sfSignerEntries); + if (BEAST_EXPECT(!emptySlot.has_value())) + BEAST_EXPECT(emptySlot.error() == HostFunctionError::EMPTY_SLOT); + } + + { + // Should return error for missing array field + auto const missingArray = hfs.getLedgerObjArrayLen(1, sfMemos); + if (BEAST_EXPECT(!missingArray.has_value())) + BEAST_EXPECT(missingArray.error() == HostFunctionError::FIELD_NOT_FOUND); + } + } + + void + testGetTxNestedArrayLen() + { + testcase("getTxNestedArrayLen"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + + STTx 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)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + // Helper for error checks + auto expectError = [&](std::vector const& locatorVec, + HostFunctionError expectedError, + int slot = 1) { + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result = hfs.getTxNestedArrayLen(locator); + if (BEAST_EXPECT(!result.has_value())) + { + BEAST_EXPECTS( + result.error() == expectedError, + std::to_string(static_cast(result.error()))); + } + }; + + // Locator for sfMemos + { + std::vector locatorVec = {sfMemos.fieldCode}; + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const arrLen = hfs.getTxNestedArrayLen(locator); + BEAST_EXPECT(arrLen.has_value() && arrLen.value() == 1); + } + + // Error: non-array field + expectError({sfAccount.fieldCode}, HostFunctionError::NO_ARRAY); + + // Error: missing field + expectError({sfSigners.fieldCode}, HostFunctionError::FIELD_NOT_FOUND); + } + + void + testGetCurrentLedgerObjNestedArrayLen() + { + testcase("getCurrentLedgerObjNestedArrayLen"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const becky("becky"); + // Create a SignerList for env.master + env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const signerKeylet = keylet::signers(env.master.id()); + WasmHostFunctionsImpl hfs(ac, signerKeylet); + + // Helper for error checks + auto expectError = [&](std::vector const& locatorVec, + HostFunctionError expectedError, + int slot = 1) { + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result = hfs.getCurrentLedgerObjNestedArrayLen(locator); + if (BEAST_EXPECT(!result.has_value())) + { + BEAST_EXPECTS( + result.error() == expectedError, + std::to_string(static_cast(result.error()))); + } + }; + + // Locator for sfSignerEntries + { + std::vector locatorVec = {sfSignerEntries.fieldCode}; + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const arrLen = hfs.getCurrentLedgerObjNestedArrayLen(locator); + BEAST_EXPECT(arrLen.has_value() && arrLen.value() == 2); + } + + // Error: non-array field + expectError({sfSignerQuorum.fieldCode}, HostFunctionError::NO_ARRAY); + + // Error: missing field + expectError({sfSigners.fieldCode}, HostFunctionError::FIELD_NOT_FOUND); + + { + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master) + 5); + WasmHostFunctionsImpl const dummyHfs(ac, dummyEscrow); + std::vector locatorVec = {sfAccount.fieldCode}; + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result = dummyHfs.getCurrentLedgerObjNestedArrayLen(locator); + if (BEAST_EXPECT(!result.has_value())) + { + BEAST_EXPECTS( + result.error() == HostFunctionError::LEDGER_OBJ_NOT_FOUND, + std::to_string(static_cast(result.error()))); + } + } + } + + void + testGetLedgerObjNestedArrayLen() + { + testcase("getLedgerObjNestedArrayLen"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + Account const becky("becky"); + env(signers(env.master, 2, {{alice, 1}, {becky, 1}})); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl hfs(ac, dummyEscrow); + + auto const signerListKeylet = keylet::signers(env.master.id()); + auto cacheResult = hfs.cacheLedgerObj(signerListKeylet.key, 1); + BEAST_EXPECT(cacheResult.has_value() && cacheResult.value() == 1); + + // Locator for sfSignerEntries + std::vector locatorVec = {sfSignerEntries.fieldCode}; + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const arrLen = hfs.getLedgerObjNestedArrayLen(1, locator); + if (BEAST_EXPECT(arrLen.has_value())) + BEAST_EXPECT(arrLen.value() == 2); + + // Helper for error checks + auto expectError = [&](std::vector const& locatorVec, + HostFunctionError expectedError, + int slot = 1) { + Slice const locator( + reinterpret_cast(locatorVec.data()), + locatorVec.size() * sizeof(int32_t)); + auto const result = hfs.getLedgerObjNestedArrayLen(slot, locator); + if (BEAST_EXPECT(!result.has_value())) + { + BEAST_EXPECTS( + result.error() == expectedError, + std::to_string(static_cast(result.error()))); + } + }; + + // Error: non-array field + expectError({sfSignerQuorum.fieldCode}, HostFunctionError::NO_ARRAY); + + // Error: missing field + expectError({sfSigners.fieldCode}, HostFunctionError::FIELD_NOT_FOUND); + + // Slot out of range + expectError(locatorVec, HostFunctionError::SLOT_OUT_RANGE, 0); + expectError(locatorVec, HostFunctionError::SLOT_OUT_RANGE, 257); + + // Empty slot + expectError(locatorVec, HostFunctionError::EMPTY_SLOT, 2); + + // Error: empty locator + expectError({}, HostFunctionError::LOCATOR_MALFORMED); + + // Error: locator malformed (not multiple of 4) + Slice const malformedLocator(reinterpret_cast(locator.data()), 3); + auto const malformed = hfs.getLedgerObjNestedArrayLen(1, malformedLocator); + BEAST_EXPECT( + !malformed.has_value() && malformed.error() == HostFunctionError::LOCATOR_MALFORMED); + + // Error: locator for non-STArray field + expectError( + {sfSignerQuorum.fieldCode, 0, sfAccount.fieldCode}, + HostFunctionError::LOCATOR_MALFORMED); + } + + void + testUpdateData() + { + testcase("updateData"); + using namespace test::jtx; + + Env env{*this}; + env(escrow::create(env.master, env.master, XRP(100)), + escrow::finish_time(env.now() + std::chrono::seconds(1))); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const escrowKeylet = keylet::escrow(env.master, env.seq(env.master) - 1); + WasmHostFunctionsImpl hfs(ac, escrowKeylet); + + // Should succeed for small data + std::vector data(10, 0x42); + auto const result = hfs.updateData(Slice(data.data(), data.size())); + BEAST_EXPECT(result.has_value() && result.value() == data.size()); + + // Should fail for too large data + std::vector bigData(maxWasmDataLength + 1, 0x42); + auto const tooBig = hfs.updateData(Slice(bigData.data(), bigData.size())); + if (BEAST_EXPECT(!tooBig.has_value())) + BEAST_EXPECT(tooBig.error() == HostFunctionError::DATA_FIELD_TOO_LARGE); + } + + void + testCheckSignature() + { + testcase("checkSignature"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + // Generate a keypair and sign a message + auto const kp = generateKeyPair(KeyType::secp256k1, randomSeed()); + PublicKey const& pk = kp.first; + SecretKey const& sk = kp.second; + std::string const& message = "hello signature"; + auto const sig = sign(pk, sk, Slice(message.data(), message.size())); + + // Should succeed for valid signature + { + auto const result = hfs.checkSignature( + Slice(message.data(), message.size()), + Slice(sig.data(), sig.size()), + Slice(pk.data(), pk.size())); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == 1); + } + + // Should fail for invalid signature + { + std::string badSig(sig.size(), 0xFF); + auto const result = hfs.checkSignature( + Slice(message.data(), message.size()), + Slice(badSig.data(), badSig.size()), + Slice(pk.data(), pk.size())); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == 0); + } + + // Should fail for invalid public key + { + std::string badPk(pk.size(), 0x00); + auto const result = hfs.checkSignature( + Slice(message.data(), message.size()), + Slice(sig.data(), sig.size()), + Slice(badPk.data(), badPk.size())); + BEAST_EXPECT(!result.has_value()); + BEAST_EXPECT(result.error() == HostFunctionError::INVALID_PARAMS); + } + + // Should fail for empty public key + { + auto const result = hfs.checkSignature( + Slice(message.data(), message.size()), + Slice(sig.data(), sig.size()), + Slice(nullptr, 0)); + BEAST_EXPECT(!result.has_value()); + BEAST_EXPECT(result.error() == HostFunctionError::INVALID_PARAMS); + } + + // Should fail for empty signature + { + auto const result = hfs.checkSignature( + Slice(message.data(), message.size()), + Slice(nullptr, 0), + Slice(pk.data(), pk.size())); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == 0); + } + + // Should fail for empty message + { + auto const result = hfs.checkSignature( + Slice(nullptr, 0), Slice(sig.data(), sig.size()), Slice(pk.data(), pk.size())); + BEAST_EXPECT(result.has_value()); + BEAST_EXPECT(result.value() == 0); + } + } + + void + testComputeSha512HalfHash() + { + testcase("computeSha512HalfHash"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + std::string data = "hello world"; + auto const result = hfs.computeSha512HalfHash(Slice(data.data(), data.size())); + BEAST_EXPECT(result.has_value()); + + // Should match direct call to sha512Half + auto expected = sha512Half(Slice(data.data(), data.size())); + BEAST_EXPECT(result.value() == expected); + } + + void + testKeyletFunctions() + { + testcase("keylet functions"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + auto compareKeylet = [](std::vector const& bytes, Keylet const& kl) { + return std::ranges::equal(bytes, kl.key); + }; +// Lambda to compare a Bytes (std::vector) to a keylet +#define COMPARE_KEYLET(hfsFunc, keyletFunc, ...) \ + { \ + auto actual = hfs.hfsFunc(__VA_ARGS__); \ + auto expected = keyletFunc(__VA_ARGS__); \ + if (BEAST_EXPECT(actual.has_value())) \ + { \ + BEAST_EXPECT(compareKeylet(actual.value(), expected)); \ + } \ + } +#define COMPARE_KEYLET_FAIL(hfsFunc, expected, ...) \ + { \ + auto actual = hfs.hfsFunc(__VA_ARGS__); \ + if (BEAST_EXPECT(!actual.has_value())) \ + { \ + BEAST_EXPECTS( \ + actual.error() == expected, std::to_string(HfErrorToInt(actual.error()))); \ + } \ + } + + COMPARE_KEYLET(accountKeylet, keylet::account, env.master.id()); + COMPARE_KEYLET_FAIL(accountKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount()); + + COMPARE_KEYLET(ammKeylet, keylet::amm, xrpIssue(), env.master["USD"].issue()); + COMPARE_KEYLET_FAIL(ammKeylet, HostFunctionError::INVALID_PARAMS, xrpIssue(), xrpIssue()); + COMPARE_KEYLET_FAIL( + ammKeylet, + HostFunctionError::INVALID_PARAMS, + makeMptID(1, env.master.id()), + xrpIssue()); + + COMPARE_KEYLET(checkKeylet, keylet::check, env.master.id(), 1); + COMPARE_KEYLET_FAIL(checkKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); + + std::string const credType = "test"; + COMPARE_KEYLET( + credentialKeylet, + keylet::credential, + env.master.id(), + env.master.id(), + Slice(credType.data(), credType.size())); + + Account const alice("alice"); + constexpr std::string_view longCredType = + "abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]" + "asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p"; + static_assert(longCredType.size() > maxCredentialTypeLength); + COMPARE_KEYLET_FAIL( + credentialKeylet, + HostFunctionError::INVALID_PARAMS, + env.master.id(), + alice.id(), + Slice(longCredType.data(), longCredType.size())); + COMPARE_KEYLET_FAIL( + credentialKeylet, + HostFunctionError::INVALID_ACCOUNT, + xrpAccount(), + alice.id(), + Slice(credType.data(), credType.size())); + COMPARE_KEYLET_FAIL( + credentialKeylet, + HostFunctionError::INVALID_ACCOUNT, + env.master.id(), + xrpAccount(), + Slice(credType.data(), credType.size())); + + COMPARE_KEYLET(didKeylet, keylet::did, env.master.id()); + COMPARE_KEYLET_FAIL(didKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount()); + + COMPARE_KEYLET(delegateKeylet, keylet::delegate, env.master.id(), alice.id()); + COMPARE_KEYLET_FAIL( + delegateKeylet, HostFunctionError::INVALID_PARAMS, env.master.id(), env.master.id()); + COMPARE_KEYLET_FAIL( + delegateKeylet, HostFunctionError::INVALID_ACCOUNT, env.master.id(), xrpAccount()); + COMPARE_KEYLET_FAIL( + delegateKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), env.master.id()); + + COMPARE_KEYLET(depositPreauthKeylet, keylet::depositPreauth, env.master.id(), alice.id()); + COMPARE_KEYLET_FAIL( + depositPreauthKeylet, + HostFunctionError::INVALID_PARAMS, + env.master.id(), + env.master.id()); + COMPARE_KEYLET_FAIL( + depositPreauthKeylet, + HostFunctionError::INVALID_ACCOUNT, + env.master.id(), + xrpAccount()); + COMPARE_KEYLET_FAIL( + depositPreauthKeylet, + HostFunctionError::INVALID_ACCOUNT, + xrpAccount(), + env.master.id()); + + COMPARE_KEYLET(escrowKeylet, keylet::escrow, env.master.id(), 1); + COMPARE_KEYLET_FAIL(escrowKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); + + Currency const usd = to_currency("USD"); + COMPARE_KEYLET(lineKeylet, keylet::line, env.master.id(), alice.id(), usd); + COMPARE_KEYLET_FAIL( + lineKeylet, HostFunctionError::INVALID_PARAMS, env.master.id(), env.master.id(), usd); + COMPARE_KEYLET_FAIL( + lineKeylet, HostFunctionError::INVALID_ACCOUNT, env.master.id(), xrpAccount(), usd); + COMPARE_KEYLET_FAIL( + lineKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), env.master.id(), usd); + COMPARE_KEYLET_FAIL( + lineKeylet, + HostFunctionError::INVALID_PARAMS, + env.master.id(), + alice.id(), + to_currency("")); + + { + auto actual = hfs.mptIssuanceKeylet(env.master.id(), 1); + auto expected = keylet::mptIssuance(1, env.master.id()); + if (BEAST_EXPECT(actual.has_value())) + { + BEAST_EXPECT(compareKeylet(actual.value(), expected)); + } + } + { + auto actual = hfs.mptIssuanceKeylet(xrpAccount(), 1); + if (BEAST_EXPECT(!actual.has_value())) + BEAST_EXPECT(actual.error() == HostFunctionError::INVALID_ACCOUNT); + } + + auto const sampleMPTID = makeMptID(1, env.master.id()); + COMPARE_KEYLET(mptokenKeylet, keylet::mptoken, sampleMPTID, alice.id()); + COMPARE_KEYLET_FAIL(mptokenKeylet, HostFunctionError::INVALID_PARAMS, MPTID{}, alice.id()); + COMPARE_KEYLET_FAIL( + mptokenKeylet, HostFunctionError::INVALID_ACCOUNT, sampleMPTID, xrpAccount()); + + COMPARE_KEYLET(nftOfferKeylet, keylet::nftoffer, env.master.id(), 1); + COMPARE_KEYLET_FAIL(nftOfferKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); + + COMPARE_KEYLET(offerKeylet, keylet::offer, env.master.id(), 1); + COMPARE_KEYLET_FAIL(offerKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); + + COMPARE_KEYLET(oracleKeylet, keylet::oracle, env.master.id(), 1); + COMPARE_KEYLET_FAIL(oracleKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); + + COMPARE_KEYLET(paychanKeylet, keylet::payChan, env.master.id(), alice.id(), 1); + COMPARE_KEYLET_FAIL( + paychanKeylet, HostFunctionError::INVALID_PARAMS, env.master.id(), env.master.id(), 1); + COMPARE_KEYLET_FAIL( + paychanKeylet, HostFunctionError::INVALID_ACCOUNT, env.master.id(), xrpAccount(), 1); + COMPARE_KEYLET_FAIL( + paychanKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), env.master.id(), 1); + + COMPARE_KEYLET(permissionedDomainKeylet, keylet::permissionedDomain, env.master.id(), 1); + COMPARE_KEYLET_FAIL( + permissionedDomainKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); + + COMPARE_KEYLET(signersKeylet, keylet::signers, env.master.id()); + COMPARE_KEYLET_FAIL(signersKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount()); + + COMPARE_KEYLET(ticketKeylet, keylet::ticket, env.master.id(), 1); + COMPARE_KEYLET_FAIL(ticketKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); + + COMPARE_KEYLET(vaultKeylet, keylet::vault, env.master.id(), 1); + COMPARE_KEYLET_FAIL(vaultKeylet, HostFunctionError::INVALID_ACCOUNT, xrpAccount(), 1); + } + + void + testGetNFT() + { + testcase("getNFT"); + using namespace test::jtx; + + Env env{*this}; + Account const alice("alice"); + env.fund(XRP(1000), alice); + env.close(); + + // Mint NFT for alice + uint256 const nftId = token::getNextID(env, alice, 0u, 0u); + std::string const uri = "https://example.com/nft"; + env(token::mint(alice), token::uri(uri)); + env.close(); + uint256 const nftId2 = token::getNextID(env, alice, 0u, 0u); + env(token::mint(alice)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = keylet::escrow(alice, env.seq(alice)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + // Should succeed for valid NFT + { + auto const result = hfs.getNFT(alice.id(), nftId); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(std::ranges::equal(*result, uri)); + } + + // Should fail for invalid account + { + auto const result = hfs.getNFT(xrpAccount(), nftId); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECT(result.error() == HostFunctionError::INVALID_ACCOUNT); + } + + // Should fail for invalid nftId + { + auto const result = hfs.getNFT(alice.id(), uint256()); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECT(result.error() == HostFunctionError::INVALID_PARAMS); + } + + // Should fail for invalid nftId + { + auto const badId = token::getNextID(env, alice, 0u, 1u); + auto const result = hfs.getNFT(alice.id(), badId); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECT(result.error() == HostFunctionError::LEDGER_OBJ_NOT_FOUND); + } + + { + auto const result = hfs.getNFT(alice.id(), nftId2); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECT(result.error() == HostFunctionError::FIELD_NOT_FOUND); + } + } + + void + testGetNFTIssuer() + { + testcase("getNFTIssuer"); + using namespace test::jtx; + + Env env{*this}; + // Mint NFT for env.master + uint32_t const taxon = 12345; + uint256 const nftId = token::getNextID(env, env.master, taxon); + env(token::mint(env.master, taxon)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + // Should succeed for valid NFT id + { + auto const result = hfs.getNFTIssuer(nftId); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(std::ranges::equal(*result, env.master.id())); + } + + // Should fail for zero NFT id + { + auto const result = hfs.getNFTIssuer(uint256()); + if (BEAST_EXPECT(!result.has_value())) + BEAST_EXPECT(result.error() == HostFunctionError::INVALID_PARAMS); + } + } + + void + testGetNFTTaxon() + { + testcase("getNFTTaxon"); + using namespace test::jtx; + + Env env{*this}; + + uint32_t const taxon = 54321; + uint256 const nftId = token::getNextID(env, env.master, taxon); + env(token::mint(env.master, taxon)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + auto const result = hfs.getNFTTaxon(nftId); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == taxon); + } + + void + testGetNFTFlags() + { + testcase("getNFTFlags"); + using namespace test::jtx; + + Env env{*this}; + + // Mint NFT with default flags + uint256 const nftId = token::getNextID(env, env.master, 0u, tfTransferable); + env(token::mint(env.master, 0), txflags(tfTransferable)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + { + auto const result = hfs.getNFTFlags(nftId); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == tfTransferable); + } + + // Should return 0 for zero NFT id + { + auto const result = hfs.getNFTFlags(uint256()); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == 0); + } + } + + void + testGetNFTTransferFee() + { + testcase("getNFTTransferFee"); + using namespace test::jtx; + + Env env{*this}; + + uint16_t const transferFee = 250; + uint256 const nftId = token::getNextID(env, env.master, 0u, tfTransferable, transferFee); + env(token::mint(env.master, 0), token::xferFee(transferFee), txflags(tfTransferable)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + { + auto const result = hfs.getNFTTransferFee(nftId); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == transferFee); + } + + // Should return 0 for zero NFT id + { + auto const result = hfs.getNFTTransferFee(uint256()); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == 0); + } + } + + void + testGetNFTSerial() + { + testcase("getNFTSerial"); + using namespace test::jtx; + + Env env{*this}; + + // Mint NFT with serial 0 + uint256 const nftId = token::getNextID(env, env.master, 0u); + auto const serial = env.seq(env.master); + env(token::mint(env.master)); + env.close(); + + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + { + auto const result = hfs.getNFTSerial(nftId); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == serial); + } + + // Should return 0 for zero NFT id + { + auto const result = hfs.getNFTSerial(uint256()); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(result.value() == 0); + } + } + + void + testTrace() + { + testcase("trace"); + using namespace test::jtx; + + { + Env env(*this); + OpenView ov{*env.current()}; + test::StreamSink sink{beast::severities::kTrace}; + beast::Journal const jlog{sink}; + ApplyContext ac = createApplyContext(env, ov, jlog); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + std::string const msg = "test trace"; + std::string data = "abc"; + auto const slice = Slice(data.data(), data.size()); + auto const result = hfs.trace(msg, slice, false); + if (BEAST_EXPECT(result.has_value())) + { + BEAST_EXPECT(result.value() == 0); + auto const messages = sink.messages().str(); + BEAST_EXPECT(messages.find(msg) != std::string::npos); + } + + auto const resultHex = hfs.trace(msg, slice, true); + if (BEAST_EXPECT(resultHex.has_value())) + { + BEAST_EXPECT(resultHex.has_value()); + BEAST_EXPECT(resultHex.value() == 0); + auto const messages = sink.messages().str(); + std::string hex; + hex.reserve(data.size() * 2); + boost::algorithm::hex(data.begin(), data.end(), std::back_inserter(hex)); + BEAST_EXPECT(messages.find(msg) != std::string::npos); + BEAST_EXPECT(messages.find(hex) != std::string::npos); + } + } + + { + // logs disabled (trace < error) + Env env(*this); + OpenView ov{*env.current()}; + test::StreamSink sink{beast::severities::kError}; + beast::Journal const jlog{sink}; + ApplyContext ac = createApplyContext(env, ov, jlog); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + std::string const msg = "test trace"; + std::string data = "abc"; + auto const slice = Slice(data.data(), data.size()); + auto const result = hfs.trace(msg, slice, false); + BEAST_EXPECT(result && *result == 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::severities::kTrace}; + beast::Journal const jlog{sink}; + ApplyContext ac = createApplyContext(env, ov, jlog); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + std::string const msg = "trace number"; + int64_t const num = 123456789; + auto const result = hfs.traceNum(msg, num); + if (BEAST_EXPECT(result.has_value())) + { + BEAST_EXPECT(result.value() == 0); + auto const messages = sink.messages().str(); + BEAST_EXPECT(messages.find(msg) != std::string::npos); + BEAST_EXPECT(messages.find(std::to_string(num)) != std::string::npos); + } + } + + { + // logs disabled + Env env(*this); + OpenView ov{*env.current()}; + test::StreamSink sink{beast::severities::kError}; + beast::Journal const jlog{sink}; + ApplyContext ac = createApplyContext(env, ov, jlog); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + std::string const msg = "trace number"; + int64_t const num = 123456789; + auto const result = hfs.traceNum(msg, num); + BEAST_EXPECT(result && *result == 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::severities::kTrace}; + beast::Journal const jlog{sink}; + ApplyContext ac = createApplyContext(env, ov, jlog); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + std::string const msg = "trace account"; + auto const result = hfs.traceAccount(msg, env.master.id()); + if (BEAST_EXPECT(result.has_value())) + { + BEAST_EXPECT(result.value() == 0); + auto const messages = sink.messages().str(); + BEAST_EXPECT(messages.find(msg) != std::string::npos); + BEAST_EXPECT(messages.find(env.master.human()) != std::string::npos); + } + } + + { + // logs disabled + Env env(*this); + OpenView ov{*env.current()}; + test::StreamSink sink{beast::severities::kError}; + beast::Journal const jlog{sink}; + ApplyContext ac = createApplyContext(env, ov, jlog); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + std::string const msg = "trace account"; + auto const result = hfs.traceAccount(msg, env.master.id()); + BEAST_EXPECT(result && *result == 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::severities::kTrace}; + beast::Journal const jlog{sink}; + ApplyContext ac = createApplyContext(env, ov, jlog); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + std::string const msg = "trace amount"; + STAmount const amount = XRP(12345); + { + auto const result = hfs.traceAmount(msg, amount); + if (BEAST_EXPECT(result.has_value())) + { + BEAST_EXPECT(*result == 0); + auto const messages = sink.messages().str(); + BEAST_EXPECT(messages.find(msg) != std::string::npos); + BEAST_EXPECT(messages.find(amount.getFullText()) != std::string::npos); + } + } + + // IOU amount + Account const alice("alice"); + env.fund(XRP(1000), alice); + env.close(); + STAmount const iouAmount = env.master["USD"](100); + { + auto const result = hfs.traceAmount(msg, iouAmount); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(*result == 0); + } + + // MPT amount + { + auto const mptId = makeMptID(42, env.master.id()); + Asset const mptAsset = Asset(mptId); + STAmount const mptAmount(mptAsset, 123456); + auto const result = hfs.traceAmount(msg, mptAmount); + if (BEAST_EXPECT(result.has_value())) + BEAST_EXPECT(*result == 0); + } + } + + { + // logs disabled + Env env(*this); + OpenView ov{*env.current()}; + test::StreamSink sink{beast::severities::kError}; + beast::Journal const jlog{sink}; + ApplyContext ac = createApplyContext(env, ov, jlog); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + std::string const msg = "trace amount"; + STAmount const amount = XRP(12345); + auto const result = hfs.traceAmount(msg, amount); + BEAST_EXPECT(result && *result == 0); + auto const messages = sink.messages().str(); + BEAST_EXPECT(messages.empty()); + } + } + + // clang-format off + + int const normalExp = 15; + + Bytes const floatIntMin = {0x99, 0x20, 0xc4, 0x9b, 0xa5, 0xe3, 0x53, 0xf8}; // -2^63 + Bytes const floatIntZero = {0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // 0 + Bytes const floatIntMax = {0xd9, 0x20, 0xc4, 0x9b, 0xa5, 0xe3, 0x53, 0xf8}; // 2^63-1 + Bytes const floatUIntMax = {0xd9, 0x46, 0x8d, 0xb8, 0xba, 0xc7, 0x10, 0xcb}; // 2^64-1 + Bytes const floatMaxExp = {0xEC, 0x43, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // 1e(80+15) + Bytes const floatPreMaxExp = {0xEC, 0x03, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // 1e(79+15) + Bytes const floatMinusMaxExp = {0xAC, 0x43, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // -1e(80+15) + Bytes const floatMaxIOU = {0xEC, 0x63, 0x86, 0xF2, 0x6F, 0xC0, 0xFF, 0xFF}; // 1e(81+15)-1 + Bytes const floatMinExp = {0xC0, 0x43, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // 1e-96 + Bytes const float1 = {0xD4, 0x83, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // 1 + Bytes const floatMinus1 = {0x94, 0x83, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // -1 + Bytes const float1More = {0xD4, 0x83, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x01}; // 1.000 000 000 000 001 + Bytes const float2 = {0xD4, 0x87, 0x1A, 0xFD, 0x49, 0x8D, 0x00, 0x00}; // 2 + Bytes const float10 = {0xD4, 0xC3, 0x8D, 0x7E, 0xA4, 0xC6, 0x80, 0x00}; // 10 + Bytes const floatInvalidZero = {0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // INVALID + Bytes const floatPi = {0xD4, 0x8B, 0x29, 0x43, 0x0A, 0x25, 0x6D, 0x21}; // 3.141592653589793 + + std::string const invalid = "invalid_data"; + + // clang-format on + + void + testTraceFloat() + { + testcase("traceFloat"); + using namespace test::jtx; + + { + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + std::string const msg = "trace float"; + + { + auto const result = hfs.traceFloat(msg, makeSlice(invalid)); + BEAST_EXPECT(result && *result == 0); + } + + { + auto const result = hfs.traceFloat(msg, makeSlice(floatMaxExp)); + BEAST_EXPECT(result && *result == 0); + } + } + + { + // logs disabled + Env env(*this); + OpenView ov{*env.current()}; + test::StreamSink sink{beast::severities::kError}; + beast::Journal const jlog{sink}; + ApplyContext ac = createApplyContext(env, ov, jlog); + + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + std::string const msg = "trace float"; + + auto const result = hfs.traceFloat(msg, makeSlice(invalid)); + BEAST_EXPECT(result && *result == 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)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatFromInt(std::numeric_limits::min(), -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatFromInt(std::numeric_limits::min(), 4); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatFromInt(std::numeric_limits::min(), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntMin); + } + + { + auto const result = hfs.floatFromInt(0, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero); + } + + { + auto const result = hfs.floatFromInt(std::numeric_limits::max(), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntMax); + } + } + + void + testFloatFromUint() + { + testcase("floatFromUint"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatFromUint(std::numeric_limits::min(), -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatFromUint(std::numeric_limits::min(), 4); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatFromUint(0, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero); + } + + { + auto const result = hfs.floatFromUint(std::numeric_limits::max(), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatUIntMax); + } + } + + void + testFloatSet() + { + testcase("floatSet"); + using namespace test::jtx; + 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)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatSet(1, 0, -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatSet(1, 0, 4); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatSet(1, wasmMaxExponent + normalExp + 1, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + + { + auto const result = hfs.floatSet(1, wasmMinExponent + normalExp - 1, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero); + } + + { + auto const result = hfs.floatSet(1, wasmMaxExponent + normalExp, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxExp); + } + + { + auto const result = hfs.floatSet(-1, wasmMaxExponent + normalExp, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMinusMaxExp); + } + + { + auto const result = hfs.floatSet(1, wasmMaxExponent + normalExp - 1, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatPreMaxExp); + } + + { + auto const result = hfs.floatSet(STAmount::cMaxValue, wasmMaxExponent, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxIOU); + } + + { + auto const result = hfs.floatSet(1, wasmMinExponent + normalExp, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMinExp); + } + + { + auto const result = hfs.floatSet(10, -1, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == float1); + } + + { + auto const result = hfs.floatSet(1, Number::maxExponent + normalExp + 1, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + } + + void + testFloatCompare() + { + testcase("floatCompare"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatCompare(Slice(), Slice()); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatCompare(makeSlice(floatInvalidZero), Slice()); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatCompare(makeSlice(float1), makeSlice(invalid)); + BEAST_EXPECT(!result && result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto x = floatMaxExp; + // exp = 81 + 97 = 178 + x[1] |= 0x80; + x[1] &= 0xBF; + auto const result = hfs.floatCompare(makeSlice(x), makeSlice(floatMaxExp)); + BEAST_EXPECT(!result && result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatCompare(makeSlice(floatIntMin), makeSlice(floatIntZero)); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == 2); + } + + { + auto const result = hfs.floatCompare(makeSlice(floatIntMax), makeSlice(floatIntZero)); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == 1); + } + + { + auto const result = hfs.floatCompare(makeSlice(float1), makeSlice(float1)); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == 0); + } + } + + void + testFloatAdd() + { + testcase("floatAdd"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatAdd(Slice(), Slice(), -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatAdd(Slice(), Slice(), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatAdd(makeSlice(float1), makeSlice(invalid), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatAdd(makeSlice(floatMaxIOU), makeSlice(floatMaxExp), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + + { + auto const result = hfs.floatAdd(makeSlice(floatIntMin), makeSlice(floatIntZero), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntMin); + } + + { + auto const result = hfs.floatAdd(makeSlice(floatIntMax), makeSlice(floatIntMin), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero); + } + } + + void + testFloatSubtract() + { + testcase("floatSubtract"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatSubtract(Slice(), Slice(), -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatSubtract(Slice(), Slice(), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatSubtract(makeSlice(float1), makeSlice(invalid), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = + hfs.floatSubtract(makeSlice(floatMaxIOU), makeSlice(floatMinusMaxExp), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + + { + auto const result = + hfs.floatSubtract(makeSlice(floatIntMin), makeSlice(floatIntZero), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntMin); + } + + { + auto const result = hfs.floatSubtract(makeSlice(floatIntZero), makeSlice(float1), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMinus1); + } + } + + void + testFloatMultiply() + { + testcase("floatMultiply"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatMultiply(Slice(), Slice(), -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatMultiply(Slice(), Slice(), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatMultiply(makeSlice(float1), makeSlice(invalid), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatMultiply(makeSlice(floatMaxIOU), makeSlice(float1More), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + + { + auto const result = hfs.floatMultiply(makeSlice(float1), makeSlice(float1), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == float1); + } + + { + auto const result = + hfs.floatMultiply(makeSlice(floatIntZero), makeSlice(floatMaxIOU), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero); + } + + { + auto const result = hfs.floatMultiply(makeSlice(float10), makeSlice(floatPreMaxExp), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxExp); + } + } + + void + testFloatDivide() + { + testcase("floatDivide"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatDivide(Slice(), Slice(), -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatDivide(Slice(), Slice(), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatDivide(makeSlice(float1), makeSlice(invalid), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatDivide(makeSlice(float1), makeSlice(floatIntZero), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + + { + auto const y = hfs.floatSet(STAmount::cMaxValue, -normalExp - 1, 0); // 0.9999999... + if (BEAST_EXPECT(y)) + { + auto const result = hfs.floatDivide(makeSlice(floatMaxIOU), makeSlice(*y), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + } + + { + auto const result = hfs.floatDivide(makeSlice(floatIntZero), makeSlice(float1), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero); + } + + { + auto const result = hfs.floatDivide(makeSlice(floatMaxExp), makeSlice(float10), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatPreMaxExp); + } + } + + void + testFloatRoot() + { + testcase("floatRoot"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatRoot(Slice(), 2, -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatRoot(makeSlice(invalid), 3, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatRoot(makeSlice(float1), -2, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatRoot(makeSlice(floatIntZero), 2, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatIntZero); + } + + { + auto const result = hfs.floatRoot(makeSlice(floatMaxIOU), 1, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxIOU); + } + + { + auto const x = hfs.floatSet(100, 0, 0); // 100 + if (BEAST_EXPECT(x)) + { + auto const result = hfs.floatRoot(makeSlice(*x), 2, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == float10); + } + } + + { + auto const x = hfs.floatSet(1000, 0, 0); // 1000 + if (BEAST_EXPECT(x)) + { + auto const result = hfs.floatRoot(makeSlice(*x), 3, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == float10); + } + } + + { + auto const x = hfs.floatSet(1, -2, 0); // 0.01 + auto const y = hfs.floatSet(1, -1, 0); // 0.1 + if (BEAST_EXPECT(x && y)) + { + auto const result = hfs.floatRoot(makeSlice(*x), 2, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == *y); + } + } + } + + void + testFloatPower() + { + testcase("floatPower"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatPower(Slice(), 2, -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatPower(makeSlice(invalid), 3, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatPower(makeSlice(float1), -2, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatPower(makeSlice(floatMaxIOU), 2, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + + { + auto const result = hfs.floatPower(makeSlice(floatMaxIOU), 81, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatPower(makeSlice(floatMaxIOU), 2, 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_COMPUTATION_ERROR); + } + + { + auto const result = hfs.floatPower(makeSlice(floatMaxIOU), 0, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == float1); + } + + { + auto const result = hfs.floatPower(makeSlice(floatMaxIOU), 1, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == floatMaxIOU); + } + + { + auto const x = hfs.floatSet(100, 0, 0); // 100 + if (BEAST_EXPECT(x)) + { + auto const result = hfs.floatPower(makeSlice(float10), 2, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == *x); + } + } + + { + auto const x = hfs.floatSet(1, -1, 0); // 0.1 + auto const y = hfs.floatSet(1, -2, 0); // 0.01 + if (BEAST_EXPECT(x && y)) + { + auto const result = hfs.floatPower(makeSlice(*x), 2, 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == *y); + } + } + } + + void + testFloatLog() + { + testcase("floatLog"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + { + auto const result = hfs.floatLog(Slice(), -1); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatLog(makeSlice(invalid), 0); + BEAST_EXPECT(!result) && + BEAST_EXPECT(result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + // perf test logs + // { + // auto const result = hfs.floatLog(makeSlice(floatPi), 0); + // if (BEAST_EXPECT(result)) + // { + // std::cout << "lg(" << floatToString(makeSlice(floatPi)) + // << ") = " << floatToString(makeSlice(*result)) + // << std::endl; + // } + // } + // { + // auto const result = hfs.floatLog(makeSlice(floatIntMax), 0); + // if (BEAST_EXPECT(result)) + // { + // std::cout << "lg(" << floatToString(makeSlice(floatIntMax)) + // << ") = " << floatToString(makeSlice(*result)) + // << std::endl; + // } + // } + + // { + // auto const result = hfs.floatLog(makeSlice(floatMaxExp), 0); + // if (BEAST_EXPECT(result)) + // { + // std::cout << "lg(" << floatToString(makeSlice(floatMaxExp)) + // << ") = " << floatToString(makeSlice(*result)) + // << std::endl; + // } + // } + + // { + // auto const result = hfs.floatLog(makeSlice(floatMaxIOU), 0); + // if (BEAST_EXPECT(result)) + // { + // std::cout << "lg(" << floatToString(makeSlice(floatMaxIOU)) + // << ") = " << floatToString(makeSlice(*result)) + // << std::endl; + // } + // } + + { + auto const x = hfs.floatSet(9'500'000'000'000'001, -14, 0); // almost 80+15 + if (BEAST_EXPECT(x)) + { + auto const result = hfs.floatLog(makeSlice(floatMaxExp), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == *x); + } + } + + { + auto const x = hfs.floatSet(100, 0, 0); // 100 + if (BEAST_EXPECT(x)) + { + auto const result = hfs.floatLog(makeSlice(*x), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == float2); + } + } + + { + auto const x = hfs.floatSet(1000, 0, 0); // 1000 + auto const y = hfs.floatSet(3, 0, 0); // 0.1 + if (BEAST_EXPECT(x && y)) + { + auto const result = hfs.floatLog(makeSlice(*x), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == *y); + } + } + + { + auto const x = hfs.floatSet(1, -2, 0); // 0.01 + auto const y = hfs.floatSet(-2'000'000'000'000'000ll, -15, 0); // -2 + if (BEAST_EXPECT(x && y)) + { + auto const result = hfs.floatLog(makeSlice(*x), 0); + BEAST_EXPECT(result) && BEAST_EXPECT(*result == *y); + } + } + } + + void + testFloatSpecialCases() + { + testcase("float Xrp+Mpt"); + using namespace test::jtx; + + Env env{*this}; + OpenView ov{*env.current()}; + ApplyContext ac = createApplyContext(env, ov); + auto const dummyEscrow = keylet::escrow(env.master, env.seq(env.master)); + WasmHostFunctionsImpl const hfs(ac, dummyEscrow); + + auto const y = hfs.floatSet(20, 0, 0); + if (!BEAST_EXPECT(y)) + return; + + Bytes x(8); + + // XRP + memset(x.data(), 0, x.size()); + x[0] = 0x40; + x[7] = 10; + + { + auto const result = hfs.floatCompare(makeSlice(x), makeSlice(float10)); + BEAST_EXPECT(!result && result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatAdd(makeSlice(float10), makeSlice(x), 0); + BEAST_EXPECT(!result && result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + // MPT + memset(x.data(), 0, x.size()); + x[0] = 0x60; + x[7] = 10; + + { + auto const result = hfs.floatCompare(makeSlice(x), makeSlice(float10)); + BEAST_EXPECT(!result && result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + { + auto const result = hfs.floatAdd(makeSlice(float10), makeSlice(x), 0); + BEAST_EXPECT(!result && result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + + testcase("float non-canonical"); + + { // non-canonical mantissa 10 000 000 000 000 000 + Bytes x = float1; + *reinterpret_cast(x.data()) = 0x0000C16FF286A3D4ull; + { + auto const result = hfs.floatCompare(makeSlice(x), makeSlice(float1)); + BEAST_EXPECT(!result && result.error() == HostFunctionError::FLOAT_INPUT_MALFORMED); + } + } + } + + void + testFloats() + { + testTraceFloat(); + testFloatFromInt(); + testFloatFromUint(); + testFloatSet(); + testFloatCompare(); + testFloatAdd(); + testFloatSubtract(); + testFloatMultiply(); + testFloatDivide(); + testFloatRoot(); + testFloatPower(); + testFloatLog(); + testFloatSpecialCases(); + } + + void + run() override + { + testGetLedgerSqn(); + testGetParentLedgerTime(); + testGetParentLedgerHash(); + testGetBaseFee(); + testIsAmendmentEnabled(); + testCacheLedgerObj(); + testGetTxField(); + testGetCurrentLedgerObjField(); + testGetLedgerObjField(); + testGetTxNestedField(); + testGetCurrentLedgerObjNestedField(); + testGetLedgerObjNestedField(); + testGetTxArrayLen(); + testGetCurrentLedgerObjArrayLen(); + testGetLedgerObjArrayLen(); + testGetTxNestedArrayLen(); + testGetCurrentLedgerObjNestedArrayLen(); + testGetLedgerObjNestedArrayLen(); + testUpdateData(); + testCheckSignature(); + testComputeSha512HalfHash(); + testKeyletFunctions(); + testGetNFT(); + testGetNFTIssuer(); + testGetNFTTaxon(); + testGetNFTFlags(); + testGetNFTTransferFee(); + testGetNFTSerial(); + testTrace(); + testTraceNum(); + testTraceAccount(); + testTraceAmount(); + testFloats(); + } +}; + +BEAST_DEFINE_TESTSUITE(HostFuncImpl, app, xrpl); + +} // namespace test +} // namespace xrpl diff --git a/src/test/app/PseudoTx_test.cpp b/src/test/app/PseudoTx_test.cpp index 9f65daad182..84f1bce5ede 100644 --- a/src/test/app/PseudoTx_test.cpp +++ b/src/test/app/PseudoTx_test.cpp @@ -43,6 +43,12 @@ struct PseudoTx_test : public beast::unit_test::Suite obj[sfReserveIncrement] = 0; obj[sfReferenceFeeUnits] = 0; } + if (rules.enabled(featureSmartEscrow)) + { + obj[sfExtensionComputeLimit] = 0; + obj[sfExtensionSizeLimit] = 0; + obj[sfGasPrice] = 0; + } }); res.emplace_back(ttAMENDMENT, [&](auto& obj) { @@ -107,7 +113,9 @@ struct PseudoTx_test : public beast::unit_test::Suite FeatureBitset const all{testableAmendments()}; FeatureBitset const xrpFees{featureXRPFees}; + testPrevented(all - featureXRPFees - featureSmartEscrow); testPrevented(all - featureXRPFees); + testPrevented(all - featureSmartEscrow); testPrevented(all); testAllowed(); } diff --git a/src/test/app/TestHostFunctions.h b/src/test/app/TestHostFunctions.h new file mode 100644 index 00000000000..748f11baccb --- /dev/null +++ b/src/test/app/TestHostFunctions.h @@ -0,0 +1,536 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +namespace test { + +struct TestLedgerDataProvider : public HostFunctions +{ + jtx::Env& env_; + void const* rt_ = nullptr; + +public: + TestLedgerDataProvider(jtx::Env& env) : HostFunctions(env.journal), env_(env) + { + } + + virtual void + setRT(void const* rt) override + { + rt_ = rt; + } + + virtual void const* + getRT() const override + { + return rt_; + } + + Expected + getLedgerSqn() const override + { + return env_.current()->seq(); + } +}; + +struct TestHostFunctions : public HostFunctions +{ + test::jtx::Env& env_; + AccountID accountID_; + Bytes data_; + int clock_drift_ = 0; + void const* rt_ = nullptr; + +public: + TestHostFunctions(test::jtx::Env& env, int cd = 0) + : HostFunctions(env.journal), env_(env), clock_drift_(cd) + { + accountID_ = env_.master.id(); + std::string t = "10000"; + data_ = Bytes{t.begin(), t.end()}; + } + + virtual void + setRT(void const* rt) override + { + rt_ = rt; + } + + virtual void const* + getRT() const override + { + return rt_; + } + + Expected + getLedgerSqn() const override + { + return 12345; + } + + Expected + getParentLedgerTime() const override + { + return 67890; + } + + Expected + getParentLedgerHash() const override + { + return env_.current()->header().parentHash; + } + + Expected + getBaseFee() const override + { + return 10; + } + + Expected + isAmendmentEnabled(uint256 const& amendmentId) const override + { + return 1; + } + + Expected + isAmendmentEnabled(std::string_view const& amendmentName) const override + { + return 1; + } + + virtual Expected + cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) override + { + return 1; + } + + Expected + getTxField(SField const& fname) const override + { + if (fname == sfAccount) + return Bytes(accountID_.begin(), accountID_.end()); + else if (fname == sfFee) + { + int64_t x = 235; + uint8_t const* p = reinterpret_cast(&x); + return Bytes{p, p + sizeof(x)}; + } + else if (fname == sfSequence) + { + auto const x = getLedgerSqn(); + if (!x) + return 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(); + } + + Expected + getCurrentLedgerObjField(SField const& fname) const override + { + auto const& sn = fname.getName(); + if (sn == "Destination" || sn == "Account") + return Bytes(accountID_.begin(), accountID_.end()); + else if (sn == "Data") + return data_; + else 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 Unexpected(HostFunctionError::INTERNAL); + } + + Expected + getLedgerObjField(int32_t cacheIdx, SField const& fname) const override + { + if (fname == sfBalance) + { + int64_t x = 10'000; + uint8_t const* p = reinterpret_cast(&x); + return Bytes{p, p + sizeof(x)}; + } + else if (fname == sfAccount) + { + return Bytes(accountID_.begin(), accountID_.end()); + } + return data_; + } + + Expected + getTxNestedField(Slice const& locator) const override + { + if (locator.size() == 4) + { + int32_t const* l = reinterpret_cast(locator.data()); + int32_t const sfield = l[0]; + if (sfield == sfAccount.fieldCode) + { + 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)]); + } + + Expected + getCurrentLedgerObjNestedField(Slice const& locator) const override + { + if (locator.size() == 4) + { + int32_t const* l = reinterpret_cast(locator.data()); + int32_t const sfield = l[0]; + if (sfield == sfAccount.fieldCode) + { + 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)]); + } + + Expected + getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator) const override + { + if (locator.size() == 4) + { + int32_t const* l = reinterpret_cast(locator.data()); + int32_t const sfield = l[0]; + if (sfield == sfAccount.fieldCode) + { + 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)]); + } + + Expected + getTxArrayLen(SField const& fname) const override + { + return 32; + } + + Expected + getCurrentLedgerObjArrayLen(SField const& fname) const override + { + return 32; + } + + Expected + getLedgerObjArrayLen(int32_t cacheIdx, SField const& fname) const override + { + return 32; + } + + Expected + getTxNestedArrayLen(Slice const& locator) const override + { + return 32; + } + + Expected + getCurrentLedgerObjNestedArrayLen(Slice const& locator) const override + { + return 32; + } + + Expected + getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator) const override + { + return 32; + } + + Expected + updateData(Slice const& data) override + { + return data.size(); + } + + Expected + checkSignature(Slice const& message, Slice const& signature, Slice const& pubkey) const override + { + return 1; + } + + Expected + computeSha512HalfHash(Slice const& data) const override + { + return env_.current()->header().parentHash; + } + + Expected + accountKeylet(AccountID const& account) const override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::account(account); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + ammKeylet(Asset const& issue1, Asset const& issue2) const override + { + if (issue1 == issue2) + return Unexpected(HostFunctionError::INVALID_PARAMS); + if (issue1.holds() || issue2.holds()) + return Unexpected(HostFunctionError::INVALID_PARAMS); + auto const keylet = keylet::amm(issue1, issue2); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + checkKeylet(AccountID const& account, std::uint32_t seq) const override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::check(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + credentialKeylet(AccountID const& subject, AccountID const& issuer, Slice const& credentialType) + const override + { + if (!subject || !issuer || credentialType.empty() || + credentialType.size() > maxCredentialTypeLength) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::credential(subject, issuer, credentialType); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + escrowKeylet(AccountID const& account, std::uint32_t seq) const override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::escrow(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + oracleKeylet(AccountID const& account, std::uint32_t documentId) const override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::oracle(account, documentId); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + getNFT(AccountID const& account, uint256 const& nftId) const override + { + if (!account || !nftId) + { + return Unexpected(HostFunctionError::INVALID_PARAMS); + } + + std::string s = "https://ripple.com"; + return Bytes(s.begin(), s.end()); + } + + Expected + getNFTIssuer(uint256 const& nftId) const override + { + return Bytes(accountID_.begin(), accountID_.end()); + } + + Expected + getNFTTaxon(uint256 const& nftId) const override + { + return 4; + } + + Expected + getNFTFlags(uint256 const& nftId) const override + { + return 8; + } + + Expected + getNFTTransferFee(uint256 const& nftId) const override + { + return 10; + } + + Expected + getNFTSerial(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::severities::kTrace)) + return; + auto j = getJournal().trace(); +#endif + j << "WasmTrace: " << msg << " " << dataFn(); + +#ifdef DEBUG_OUTPUT + j << std::endl; +#endif + } + + 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; + } + + Expected + traceNum(std::string_view const& msg, int64_t data) const override + { + log(msg, [data] { return data; }); + return 0; + } + + Expected + traceAccount(std::string_view const& msg, AccountID const& account) const override + { + log(msg, [&account] { return toBase58(account); }); + return 0; + } + + Expected + traceFloat(std::string_view const& msg, Slice const& data) const override + { + log(msg, [&data] { return wasm_float::floatToString(data); }); + return 0; + } + + Expected + traceAmount(std::string_view const& msg, STAmount const& amount) const override + { + log(msg, [&amount] { return amount.getFullText(); }); + return 0; + } + + Expected + floatFromInt(int64_t x, int32_t mode) const override + { + return wasm_float::floatFromIntImpl(x, mode); + } + + Expected + floatFromUint(uint64_t x, int32_t mode) const override + { + return wasm_float::floatFromUintImpl(x, mode); + } + + Expected + floatSet(int64_t mantissa, int32_t exponent, int32_t mode) const override + { + return wasm_float::floatSetImpl(mantissa, exponent, mode); + } + + Expected + floatCompare(Slice const& x, Slice const& y) const override + { + return wasm_float::floatCompareImpl(x, y); + } + + Expected + floatAdd(Slice const& x, Slice const& y, int32_t mode) const override + { + return wasm_float::floatAddImpl(x, y, mode); + } + + Expected + floatSubtract(Slice const& x, Slice const& y, int32_t mode) const override + { + return wasm_float::floatSubtractImpl(x, y, mode); + } + + Expected + floatMultiply(Slice const& x, Slice const& y, int32_t mode) const override + { + return wasm_float::floatMultiplyImpl(x, y, mode); + } + + Expected + floatDivide(Slice const& x, Slice const& y, int32_t mode) const override + { + return wasm_float::floatDivideImpl(x, y, mode); + } + + Expected + floatRoot(Slice const& x, int32_t n, int32_t mode) const override + { + return wasm_float::floatRootImpl(x, n, mode); + } + + Expected + floatPower(Slice const& x, int32_t n, int32_t mode) const override + { + return wasm_float::floatPowerImpl(x, n, mode); + } + + Expected + floatLog(Slice const& x, int32_t mode) const override + { + return wasm_float::floatLogImpl(x, mode); + } +}; + +struct TestHostFunctionsSink : public TestHostFunctions +{ + test::StreamSink sink_; + void const* rt_ = nullptr; + +public: + explicit TestHostFunctionsSink(test::jtx::Env& env, int cd = 0) + : TestHostFunctions(env, cd), sink_(beast::severities::kDebug) + { + j_ = beast::Journal(sink_); + } + + test::StreamSink& + getSink() + { + return sink_; + } +}; + +} // namespace test +} // namespace xrpl diff --git a/src/test/app/Wasm_test.cpp b/src/test/app/Wasm_test.cpp new file mode 100644 index 00000000000..0f2c4c8ad4c --- /dev/null +++ b/src/test/app/Wasm_test.cpp @@ -0,0 +1,1555 @@ +#ifdef _DEBUG +// #define DEBUG_OUTPUT 1 +#endif + +#include + +#include + +#include + +namespace xrpl { +namespace test { + +bool +testGetDataIncrement(); + +using Add_proto = int32_t(int32_t, int32_t); +static wasm_trap_t* +Add(void* env, 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()); +} + +template +unsigned +uleb128(IT& it, T val) +{ + unsigned count = 0; + do + { + std::uint8_t byte = val & 0x7f; + val >>= 7; + if (val) + byte |= 0x80; + *it++ = byte; + ++count; + } while (val != 0); + + return count; +} + +template +std::pair +uleb128(IT&& it) +{ + static_assert(sizeof(*it) == 1, "invalid iterator type"); + std::uint64_t val = 0; + std::uint64_t byte = 0; + unsigned shift = 0; + unsigned count = 0; + + do + { + if (shift > (sizeof(std::uint64_t) * 8) - 7) + return {0, 0}; + byte = *it++; + val |= (byte & 0x7F) << shift; + shift += 7; + ++count; + } while (byte >= 0x80); + + return {val, count}; +} + +std::pair +getSection(Bytes const& module, std::uint8_t n) +{ + static std::uint8_t const hdr[] = {0x00, 0x61, 0x73, 0x6D}; + static std::uint8_t const ver[] = {0x01, 0x00, 0x00, 0x00}; + static std::uint8_t const lastSec = 12; + + // sections: + // 0: "Custom", 1: "Type", 2: "Import", 3: "Function", 4: "Table", 5: "Memory", 6: "Global", + // 7: "Export", 8: "Start", 9: "Element", 10: "Code", 11: "Data", 12: "DataCount" + + if (module.size() < sizeof(hdr) + sizeof(ver) + 2) + return {0, 0}; + if (memcmp(module.data(), hdr, sizeof(hdr)) != 0) + return {0, 0}; + if (memcmp(module.data() + sizeof(hdr), ver, sizeof(ver)) != 0) + return {0, 0}; + + unsigned pos = sizeof(hdr) + sizeof(ver); // sections start + for (; pos < module.size();) + { + auto const start = pos; + std::uint8_t const byte = module[pos++]; + if (byte > lastSec) + return {0, 0}; + + auto [sz, cnt] = uleb128(module.cbegin() + pos); + if (cnt == 0u) + return {0, 0}; + if (pos + cnt + sz > module.size()) + return {0, 0}; + pos += cnt + sz; + + if (byte == n) + return {start, pos}; + } + return {0, 0}; +} + +std::optional +runFinishFunction(std::string const& code) +{ + auto& engine = WasmEngine::instance(); + auto const wasm = hexToBytes(code); + HostFunctions hfs; + auto const re = engine.run(wasm, hfs, 10'000'000, "finish"); + if (re.has_value()) + { + return std::optional(re->result); + } + + return std::nullopt; +} + +struct Wasm_test : public beast::unit_test::suite +{ + void + checkResult( + Expected, TER> 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()) + 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("wasmtime 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", reinterpret_cast(&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, ESCROW_FUNCTION_NAME); + BEAST_EXPECT(!isTesSuccess(re)); + } + } + + void + testWasmLedgerSqn() + { + testcase("Wasm get ledger sequence"); + + auto ledgerSqnWasm = hexToBytes(ledgerSqnWasmHex); + + using namespace test::jtx; + + Env env{*this}; + TestLedgerDataProvider hfs(env); + ImportVec imports; + WASM_IMPORT_FUNC2(imports, getLedgerSqn, "get_ledger_sqn", &hfs, 33); + auto& engine = WasmEngine::instance(); + + auto re = engine.run( + ledgerSqnWasm, hfs, 1'000'000, ESCROW_FUNCTION_NAME, {}, imports, env.journal); + + checkResult(re, 0, 440); + + env.close(); + env.close(); + + // empty module, throwing exception + re = engine.run({}, hfs, 1'000'000, ESCROW_FUNCTION_NAME, {}, imports, env.journal); + BEAST_EXPECT(!re); + env.close(); + } + + void + testImpExp() + { + testcase("Wasm import/export functions"); + + auto impExpWasm = hexToBytes(impExpHex); + + using namespace test::jtx; + + Env env{*this}; + TestLedgerDataProvider hfs(env); + ImportVec imports; + WASM_IMPORT_FUNC2(imports, getLedgerSqn, "get_ledger_sqn", &hfs, 33); + WASM_IMPORT_FUNC2(imports, getParentLedgerHash, "get_parent_ledger_hash", &hfs, 60); + auto& engine = WasmEngine::instance(); + + // Test exp_func1() - should return 1 + auto re = engine.run(impExpWasm, hfs, 1'000'000, "exp_func1", {}, imports, env.journal); + checkResult(re, 1, 30); + + // Test exp_func2(5) - should return 2 * 5 = 10 + re = engine.run( + impExpWasm, hfs, 1'000'000, "exp_func2", wasmParams(5), imports, env.journal); + checkResult(re, 10, 52); + + // Test test_imports() - should call get_ledger_sqn and get_parent_ledger_hash + re = engine.run(impExpWasm, hfs, 1'000'000, "test_imports", {}, imports, env.journal); + // Should return the ledger sequence number (3 by default in test env) + checkResult(re, 3, 294); + + // Test corrupted import/export sections - invert each byte and expect failure + testcase("Wasm import/export section corruption"); + { + // Import section(#2): bytes [26, 79) - 53 bytes + // Export section(#7): bytes [90, 141) - 51 bytes + auto [importStart, importEnd] = getSection(impExpWasm, 2); + auto [exportStart, exportEnd] = getSection(impExpWasm, 7); + + BEAST_EXPECTS(importStart == 26, std::to_string(importStart)); + BEAST_EXPECTS(importEnd == 79, std::to_string(importEnd)); + BEAST_EXPECTS(exportStart == 90, std::to_string(exportStart)); + BEAST_EXPECTS(exportEnd == 141, std::to_string(exportEnd)); + + auto testInv = [&](unsigned i) { + auto corruptedWasm = impExpWasm; + corruptedWasm[i] = ~corruptedWasm[i]; // Invert byte + + // Try to run any function - should fail due to corruption + auto result = engine.run( + corruptedWasm, hfs, 1'000'000, "exp_func1", {}, imports, env.journal); + BEAST_EXPECT(!result); + }; + + // Test each byte in import section + for (unsigned i = importStart; i < importEnd; ++i) + testInv(i); + + // Test each byte in export section + for (unsigned i = exportStart; i < exportEnd; ++i) + testInv(i); + } + + env.close(); + } + + void + testWasmFib() + { + testcase("Wasm fibo"); + + auto const fibWasm = hexToBytes(fibWasmHex); + auto& engine = WasmEngine::instance(); + HostFunctions hfs; + + auto const re = engine.run(fibWasm, hfs, 10'000'000, "fib", wasmParams(10)); + + checkResult(re, 55, 1'137); + } + + void + testHFCost() + { + testcase("wasm test host functions cost"); + + using namespace test::jtx; + + Env env(*this); + { + auto const allHostFuncWasm = hexToBytes(allHostFunctionsWasmHex); + + auto& engine = WasmEngine::instance(); + + TestHostFunctions hfs(env, 0); + auto imp = createWasmImport(hfs); + for (auto& i : imp) + i.second.gas = 0; + + auto re = engine.run( + allHostFuncWasm, hfs, 1'000'000, ESCROW_FUNCTION_NAME, {}, imp, env.journal); + + checkResult(re, 1, 27'080); + + env.close(); + } + + env.close(); + env.close(); + env.close(); + env.close(); + env.close(); + + { + auto const allHostFuncWasm = hexToBytes(allHostFunctionsWasmHex); + + auto& engine = WasmEngine::instance(); + + TestHostFunctions hfs(env, 0); + auto const imp = createWasmImport(hfs); + + auto re = engine.run( + allHostFuncWasm, hfs, 1'000'000, ESCROW_FUNCTION_NAME, {}, imp, env.journal); + + checkResult(re, 1, 65'840); + + env.close(); + } + + // not enough gas + { + auto const allHostFuncWasm = hexToBytes(allHostFunctionsWasmHex); + + auto& engine = WasmEngine::instance(); + + TestHostFunctions hfs(env, 0); + auto const imp = createWasmImport(hfs); + + auto re = + engine.run(allHostFuncWasm, hfs, 200, ESCROW_FUNCTION_NAME, {}, imp, env.journal); + + if (BEAST_EXPECT(!re)) + { + BEAST_EXPECTS( + re.error() == tecFAILED_PROCESSING, std::to_string(TERtoInt(re.error()))); + } + + env.close(); + } + } + + void + testEscrowWasmDN() + { + testcase("escrow wasm devnet test"); + + auto const allHFWasm = hexToBytes(allHostFunctionsWasmHex); + + using namespace test::jtx; + Env env{*this}; + { + TestHostFunctions hfs(env, 0); + auto re = runEscrowWasm(allHFWasm, hfs, 100'000, ESCROW_FUNCTION_NAME, {}); + checkResult(re, 1, 65'840); + } + + { + // Invalid gas limit (0) should be rejected (boundary condition) + TestHostFunctions hfs(env, 0); + auto re = runEscrowWasm(allHFWasm, hfs, -1, ESCROW_FUNCTION_NAME, {}); + BEAST_EXPECT(!re.has_value()); + BEAST_EXPECT(re.error() == temBAD_AMOUNT); + } + + { + // Invalid gas limit (-1) should be rejected + TestHostFunctions hfs(env, 0); + auto re = runEscrowWasm(allHFWasm, hfs, 0, ESCROW_FUNCTION_NAME, {}); + BEAST_EXPECT(!re.has_value()); + BEAST_EXPECT(re.error() == temBAD_AMOUNT); + } + + { + // max() gas + TestHostFunctions hfs(env, 0); + auto re = runEscrowWasm( + allHFWasm, hfs, std::numeric_limits::max(), ESCROW_FUNCTION_NAME, {}); + checkResult(re, 1, 65'840); + } + + { // fail because trying to access nonexistent field + struct FieldNotFoundHostFunctions : public TestHostFunctions + { + explicit FieldNotFoundHostFunctions(Env& env) : TestHostFunctions(env) + { + } + Expected + getTxField(SField const& fname) const override + { + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + } + }; + + FieldNotFoundHostFunctions hfs(env); + auto re = runEscrowWasm(allHFWasm, hfs, 100'000, ESCROW_FUNCTION_NAME, {}); + checkResult(re, -201, 28'965); + } + + { // fail because trying to allocate more than MAX_PAGES memory + struct OversizedFieldHostFunctions : public TestHostFunctions + { + explicit OversizedFieldHostFunctions(Env& env) : TestHostFunctions(env) + { + } + Expected + getTxField(SField const& fname) const override + { + return Bytes((128 + 1) * 64 * 1024, 1); + } + }; + + OversizedFieldHostFunctions hfs(env); + auto re = runEscrowWasm(allHFWasm, hfs, 100'000, ESCROW_FUNCTION_NAME, {}); + checkResult(re, -201, 28'965); + } + + { // fail because recursion too deep + + auto const deepWasm = hexToBytes(deepRecursionHex); + + TestHostFunctionsSink hfs(env); + std::string const funcName("finish"); + auto re = runEscrowWasm(deepWasm, hfs, 1'000'000'000, funcName, {}); + BEAST_EXPECT(!re && re.error()); + // std::cout << "bad case (deep recursion) result " << re.error() + // << std::endl; + + auto const& sink = hfs.getSink(); + auto countSubstr = [](std::string const& str, std::string const& substr) { + std::size_t pos = 0; + int occurrences = 0; + while ((pos = str.find(substr, pos)) != std::string::npos) + { + occurrences++; + pos += substr.length(); + } + return occurrences; + }; + + auto const s = sink.messages().str(); + BEAST_EXPECT(countSubstr(s, "WASMI Error: failure to call func") == 1); + BEAST_EXPECT(countSubstr(s, "exception: failure") > 0); + } + + { // infinite loop + auto const infiniteLoopWasm = hexToBytes(infiniteLoopWasmHex); + std::string const funcName("loop"); + TestHostFunctions hfs(env, 0); + + // infinite loop should be caught and fail + auto const re = runEscrowWasm(infiniteLoopWasm, hfs, 1'000'000, funcName, {}); + if (BEAST_EXPECT(!re.has_value())) + { + BEAST_EXPECT(re.error() == tecFAILED_PROCESSING); + } + } + + { + // expected import not provided + auto const lgrSqnWasm = hexToBytes(ledgerSqnWasmHex); + TestLedgerDataProvider hfs(env); + ImportVec imports; + WASM_IMPORT_FUNC2(imports, getLedgerSqn, "get_ledger_sqn2", &hfs); + + auto& engine = WasmEngine::instance(); + + auto re = engine.run( + lgrSqnWasm, hfs, 1'000'000, ESCROW_FUNCTION_NAME, {}, imports, env.journal); + + BEAST_EXPECT(!re); + } + + { + // bad import format + auto const lgrSqnWasm = hexToBytes(ledgerSqnWasmHex); + TestLedgerDataProvider hfs(env); + ImportVec imports; + WASM_IMPORT_FUNC2(imports, getLedgerSqn, "get_ledger_sqn", &hfs); + imports[0].first = nullptr; + + auto& engine = WasmEngine::instance(); + + auto re = engine.run( + lgrSqnWasm, hfs, 1'000'000, ESCROW_FUNCTION_NAME, {}, imports, env.journal); + + BEAST_EXPECT(!re); + } + + { + // bad function name + auto const lgrSqnWasm = hexToBytes(ledgerSqnWasmHex); + TestLedgerDataProvider hfs(env); + ImportVec imports; + WASM_IMPORT_FUNC2(imports, getLedgerSqn, "get_ledger_sqn", &hfs); + + auto& engine = WasmEngine::instance(); + auto re = engine.run(lgrSqnWasm, hfs, 1'000'000, "func1", {}, imports, env.journal); + + BEAST_EXPECT(!re); + } + } + + void + testFloat() + { + testcase("float point"); + + std::string const funcName("finish"); + + using namespace test::jtx; + + Env env(*this); + { + auto const floatTestWasm = hexToBytes(floatTestsWasmHex); + + TestHostFunctions hfs(env, 0); + auto re = runEscrowWasm(floatTestWasm, hfs, 200'000, funcName, {}); + checkResult(re, 1, 110'699); + env.close(); + } + + { + auto const float0Wasm = hexToBytes(float0Hex); + + TestHostFunctions hfs(env, 0); + auto re = runEscrowWasm(float0Wasm, hfs, 100'000, funcName, {}); + checkResult(re, 1, 4'259); + env.close(); + } + } + + void + testCodecovWasm() + { + testcase("Codecov wasm test"); + + using namespace test::jtx; + + Env env{*this}; + + auto const codecovWasm = hexToBytes(codecovTestsWasmHex); + TestHostFunctions hfs(env, 0); + + auto const allowance = 340'524; + auto re = runEscrowWasm(codecovWasm, hfs, allowance, ESCROW_FUNCTION_NAME, {}); + + checkResult(re, 1, allowance); + } + + void + testDisabledFloat() + { + testcase("disabled float"); + + using namespace test::jtx; + Env env{*this}; + + auto disabledFloatWasm = hexToBytes(disabledFloatHex); + std::string const funcName("finish"); + TestHostFunctions hfs(env, 0); + + { + // f32 set constant, opcode disabled exception + auto const re = runEscrowWasm(disabledFloatWasm, hfs, 1'000'000, funcName, {}); + if (BEAST_EXPECT(!re.has_value())) + { + BEAST_EXPECT(re.error() == tecFAILED_PROCESSING); + } + } + + { + // f32 add, can't create module exception + disabledFloatWasm[0x117] = 0x92; + auto const re = runEscrowWasm(disabledFloatWasm, hfs, 1'000'000, funcName, {}); + if (BEAST_EXPECT(!re.has_value())) + { + BEAST_EXPECT(re.error() == tecFAILED_PROCESSING); + } + } + } + + void + testWasmMemory() + { + testcase("Wasm additional memory limit tests"); + BEAST_EXPECT(runFinishFunction(memoryPointerAtLimitHex).value() == 1); + BEAST_EXPECT(runFinishFunction(memoryPointerOverLimitHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(memoryOffsetOverLimitHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(memoryEndOfWordOverLimitHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(memoryGrow0To1PageHex).value() == 1); + BEAST_EXPECT(runFinishFunction(memoryGrow1To0PageHex).value() == -1); + BEAST_EXPECT(runFinishFunction(memoryLastByteOf8MBHex).value() == 1); + BEAST_EXPECT(runFinishFunction(memoryGrow1MoreThan8MBHex).value() == -1); + BEAST_EXPECT(runFinishFunction(memoryGrow0MoreThan8MBHex).value() == 1); + BEAST_EXPECT(runFinishFunction(memoryInit1MoreThan8MBHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(memoryNegativeAddressHex).has_value() == false); + } + + void + testWasmTable() + { + testcase("Wasm table limit tests"); + BEAST_EXPECT(runFinishFunction(table64ElementsHex).value() == 1); + BEAST_EXPECT(runFinishFunction(table65ElementsHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(table2TablesHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(table0ElementsHex).value() == 1); + BEAST_EXPECT(runFinishFunction(tableUintMaxHex).has_value() == false); + } + + void + testWasmProposal() + { + testcase("Wasm disabled proposal tests"); + BEAST_EXPECT(runFinishFunction(proposalMutableGlobalHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(proposalGcStructNewHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(proposalMultiValueHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(proposalSignExtHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(proposalFloatToIntHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(proposalBulkMemoryHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(proposalRefTypesHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(proposalTailCallHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(proposalExtendedConstHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(proposalMultiMemoryHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(proposalCustomPageSizesHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(proposalMemory64Hex).has_value() == false); + BEAST_EXPECT(runFinishFunction(proposalWideArithmeticHex).has_value() == false); + } + + void + testWasmTrap() + { + testcase("Wasm trap tests"); + BEAST_EXPECT(runFinishFunction(trapDivideBy0Hex).has_value() == false); + BEAST_EXPECT(runFinishFunction(trapIntOverflowHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(trapUnreachableHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(trapNullCallHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(trapFuncSigMismatchHex).has_value() == false); + } + + void + testWasmWasi() + { + testcase("Wasm Wasi tests"); + BEAST_EXPECT(runFinishFunction(wasiGetTimeHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(wasiPrintHex).has_value() == false); + } + + void + testWasmSectionCorruption() + { + testcase("Wasm Section Corruption tests"); + BEAST_EXPECT(runFinishFunction(badMagicNumberHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(badVersionNumberHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(lyingHeaderHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(neverEndingNumberHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(vectorLieHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(sectionOrderingHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(ghostPayloadHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(junkAfterSectionHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(invalidSectionIdHex).has_value() == false); + BEAST_EXPECT(runFinishFunction(localVariableBombHex).has_value() == false); + } + + void + testStartFunctionLoop() + { + testcase("infinite loop in start function"); + + using namespace test::jtx; + Env env(*this); + + auto const startLoopWasm = hexToBytes(startLoopHex); + TestLedgerDataProvider hfs(env); + ImportVec const imports; + + auto& engine = WasmEngine::instance(); + auto checkRes = engine.check(startLoopWasm, hfs, "finish", {}, imports, env.journal); + BEAST_EXPECTS(checkRes == tesSUCCESS, std::to_string(TERtoInt(checkRes))); + + auto re = engine.run( + startLoopWasm, hfs, 1'000'000, ESCROW_FUNCTION_NAME, {}, imports, env.journal); + BEAST_EXPECTS(re.error() == tecFAILED_PROCESSING, std::to_string(TERtoInt(re.error()))); + } + + void + testBadAlign() + { + testcase("Wasm Bad Align"); + + // bad_align.c + auto const badAlignWasm = hexToBytes(badAlignWasmHex); + + using namespace test::jtx; + + Env env{*this}; + TestHostFunctions hfs(env, 0); + 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()))) + { + BEAST_EXPECTS(re->result == 0x684f7941, std::to_string(re->result)); + } + } + + env.close(); + } + + void + testReturnType() + { + using namespace test::jtx; + Env env(*this); + TestHostFunctions hfs(env, 0); + + testcase("Wasm invalid return type"); + + // return int64. + { // (module + // (memory (export "memory") 1) + // (func (export "finish") (result i64) + // i64.const 0x100000000)) + auto const wasmHex = + "0061736d010000000105016000017e030201000503010001" + "071302066d656d6f727902000666696e69736800000a0a01" + "08004280808080100b"; + auto const wasm = hexToBytes(wasmHex); + auto const re = runEscrowWasm(wasm, hfs, 100'000, ESCROW_FUNCTION_NAME, {}); + BEAST_EXPECT(!re); + } + + // return void. wasmi return execution error + { //(module + // (type (;0;) (func)) + // (func (;0;) (type 0) + // return) + // (memory (;0;) 1) + // (export "memory" (memory 0)) + // (export "finish" (func 0))) + auto const wasmHex = + "0061736d01000000010401600000030201000503010001071302066d656d6f" + "727902000666696e69736800000a050103000f0b"; + auto const wasm = hexToBytes(wasmHex); + auto const re = runEscrowWasm(wasm, hfs, 100'000, ESCROW_FUNCTION_NAME, {}); + BEAST_EXPECT(!re); + } + + // return i32, i32. wasmi doesn't create module + { //(module + // (memory (export "memory") 1) + // (func (export "finish") (result i32 i32) + // i32.const 0x10000000 + // i32.const 0x100000FF)) + auto const wasmHex = + "0061736d010000000106016000027f7f030201000503010001071302066d65" + "6d6f727902000666696e69736800000a10010e0041808080800141ff818080" + "010b"; + auto const wasm = hexToBytes(wasmHex); + auto const re = runEscrowWasm(wasm, hfs, 100'000, ESCROW_FUNCTION_NAME, {}); + BEAST_EXPECT(!re); + } + } + + void + testParameterType() + { + using namespace test::jtx; + Env env(*this); + TestHostFunctions hfs(env, 0); + + testcase("Wasm invalid params"); + + // (module + // (memory (export "memory") 1) + // (func $test1 (export "test1") (param i32) (result i32) + // i32.const 1000) + // (func $test2 (export "test2") (param i32 i32) (result i32) + // i32.const 1001)) + auto const wasmHex = + "0061736d01000000010c0260017f017f60027f7f017f03030200010503010001071a03066d656d6f727902" + "00057465737431000005746573743200010a0d02050041e8070b050041e9070b"; + auto const wasm = hexToBytes(wasmHex); + + // good params, module is working properly + { + auto const re = runEscrowWasm(wasm, hfs, 100'000, "test2", wasmParams(2, 10)); + BEAST_EXPECT(re && re->result == 1001 && re->cost == 37); + } + + // no params + { + auto const re = runEscrowWasm(wasm, hfs, 100'000, "test1", {}); + BEAST_EXPECT(!re); + } + + // more params + { + auto const re = runEscrowWasm(wasm, hfs, 100'000, "test1", wasmParams(0, 1)); + BEAST_EXPECT(!re); + } + + // less params + { + auto const re = runEscrowWasm(wasm, hfs, 100'000, "test2", wasmParams(1)); + BEAST_EXPECT(!re); + } + + // invalid type + { + auto const re = + runEscrowWasm(wasm, hfs, 100'000, "test1", wasmParams(std::int64_t(15))); + BEAST_EXPECT(!re); + } + } + + void + testSwapBytes() + { + testcase("Wasm swap bytes"); + + uint64_t const SWAP_DATAU64 = 0x123456789abcdeffull; + uint64_t const REVERSE_SWAP_DATAU64 = 0xffdebc9a78563412ull; + int64_t const SWAP_DATAI64 = 0x123456789abcdeffll; + int64_t const REVERSE_SWAP_DATAI64 = 0xffdebc9a78563412ll; + + uint32_t const SWAP_DATAU32 = 0x12789aff; + uint32_t const REVERSE_SWAP_DATAU32 = 0xff9a7812; + int32_t const SWAP_DATAI32 = 0x12789aff; + int32_t const REVERSE_SWAP_DATAI32 = 0xff9a7812; + + uint16_t const SWAP_DATAU16 = 0x12ff; + uint16_t const REVERSE_SWAP_DATAU16 = 0xff12; + int16_t const SWAP_DATAI16 = 0x12ff; + int16_t const REVERSE_SWAP_DATAI16 = 0xff12; + + uint64_t b1 = SWAP_DATAU64; + int64_t b2 = SWAP_DATAI64; + b1 = adjustWasmEndianessHlp(b1); + b2 = adjustWasmEndianessHlp(b2); + BEAST_EXPECT(b1 == REVERSE_SWAP_DATAU64); + BEAST_EXPECT(b2 == REVERSE_SWAP_DATAI64); + b1 = adjustWasmEndianessHlp(b1); + b2 = adjustWasmEndianessHlp(b2); + BEAST_EXPECT(b1 == SWAP_DATAU64); + BEAST_EXPECT(b2 == SWAP_DATAI64); + + uint32_t b3 = SWAP_DATAU32; + int32_t b4 = SWAP_DATAI32; + b3 = adjustWasmEndianessHlp(b3); + b4 = adjustWasmEndianessHlp(b4); + BEAST_EXPECT(b3 == REVERSE_SWAP_DATAU32); + BEAST_EXPECT(b4 == REVERSE_SWAP_DATAI32); + b3 = adjustWasmEndianessHlp(b3); + b4 = adjustWasmEndianessHlp(b4); + BEAST_EXPECT(b3 == SWAP_DATAU32); + BEAST_EXPECT(b4 == SWAP_DATAI32); + + uint16_t b5 = SWAP_DATAU16; + int16_t b6 = SWAP_DATAI16; + b5 = adjustWasmEndianessHlp(b5); + b6 = adjustWasmEndianessHlp(b6); + BEAST_EXPECT(b5 == REVERSE_SWAP_DATAU16); + BEAST_EXPECT(b6 == REVERSE_SWAP_DATAI16); + b5 = adjustWasmEndianessHlp(b5); + b6 = adjustWasmEndianessHlp(b6); + BEAST_EXPECT(b5 == SWAP_DATAU16); + BEAST_EXPECT(b6 == SWAP_DATAI16); + } + + void + testManyParams() + { + testcase("Wasm Many params"); + + auto const params1k = hexToBytes(thousandParamsHex); + auto const params1k1 = hexToBytes(thousand1ParamsHex); + + using namespace test::jtx; + + Env env{*this}; + TestHostFunctions hfs(env, 0); + auto imports = createWasmImport(hfs); + + // add 1k parameter (max that wasmi support) + std::vector params; + params.reserve(1000); + for (int i = 0; i < 1000; ++i) + params.push_back({.type = WT_I32, .of = {.i32 = 2 * i}}); + + auto& engine = WasmEngine::instance(); + { + auto re = engine.run(params1k, hfs, 1'000'000, "test", params, imports, env.journal); + BEAST_EXPECT(re && re->result == 999000); + } + + // add 1 more parameter, module can't be created now + params.push_back({.type = WT_I32, .of = {.i32 = 2 * 1000}}); + { + auto re = engine.run(params1k1, hfs, 1'000'000, "test", params, imports, env.journal); + BEAST_EXPECT(!re); + } + + // function that create 10k local variables + auto const locals10k = hexToBytes(locals10kHex); + { + auto re = engine.run( + locals10k, hfs, 1'000'000, "test", wasmParams(0, 1), imports, env.journal); + BEAST_EXPECT(re && re->result == 890'489'442); + } + + // module has 5k functions + auto const functions5k = hexToBytes(functions5kHex); + { + auto re = engine.run( + functions5k, hfs, 1'000'000, "test0001", wasmParams(2, 3), imports, env.journal); + BEAST_EXPECT(re && re->result == 5); + } + + env.close(); + } + + void + testOpcodes() + { + using namespace test::jtx; + + unsigned const RESERVED = 64; + std::uint8_t const nop = 0x01; + std::array const codeMarker = { + nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop, nop}; + auto const opcReserved = hexToBytes(opcReservedHex); + + Env env{*this}; + auto& engine = WasmEngine::instance(); + + TestHostFunctions hfs(env, 0); + auto imports = createWasmImport(hfs); + env.close(); + + { + auto run = [&](std::vector const& code, + bool good = false, + int64_t cost = -1, + std::source_location const location = std::source_location::current()) { + auto const lineStr = " (" + std::to_string(location.line()) + ")"; + auto re = + engine.run(code, hfs, 1'000'000, "all_instructions", {}, imports, env.journal); + if (BEAST_EXPECTS(re.has_value() == good, transToken(re.error()) + lineStr) && good) + BEAST_EXPECTS(re->cost == cost, std::to_string(re->cost) + lineStr); + }; + + // 1 byte instruction + auto test = [&](std::uint8_t start, + std::uint8_t finish, + bool good = false, + int64_t cost = -1, + std::source_location const location = std::source_location::current()) { + auto const lineStr = " (" + std::to_string(location.line()) + ")"; + auto code = opcReserved; + auto codeRange = std::ranges::search(code, codeMarker); + if (!BEAST_EXPECTS(!codeRange.empty(), lineStr)) + return; + + auto it = codeRange.begin(); + for (std::uint16_t i = start; i <= finish; ++i) + { + *it = i; + run(code, good, cost, location); + } + }; + + // 2 bytes instruction + auto test2 = [&](std::uint8_t major, + std::uint16_t start, + std::uint16_t finish, + bool good = false, + int64_t cost = -1, + std::source_location const location = + std::source_location::current()) { + auto const lineStr = " (" + std::to_string(location.line()) + ")"; + auto code = opcReserved; + auto codeRange = std::ranges::search(code, codeMarker); + if (!BEAST_EXPECTS(!codeRange.empty(), lineStr)) + return; + + auto it = codeRange.begin(); + *it++ = major; + for (std::uint16_t i = start; i <= finish; ++i) + { + auto it2 = it; + uleb128(it2, i); + run(code, good, cost, location); + } + }; + + // multibytes instructions + auto testMB = [&](std::vector const& codeSnap, + bool good = false, + int64_t cost = -1, + std::source_location const location = + std::source_location::current()) { + auto const lineStr = " (" + std::to_string(location.line()) + ")"; + auto code = opcReserved; + auto codeRange = std::ranges::search(code, codeMarker); + if (!BEAST_EXPECTS(!codeRange.empty(), lineStr)) + return; + + if (!BEAST_EXPECTS(codeSnap.size() < RESERVED, lineStr)) + return; + auto it = codeRange.begin(); + for (auto x : codeSnap) + *it++ = x; + run(code, good, cost, location); + }; + + // normal run + testcase("Wasm reserved opcodes main"); + test(nop, nop, true, 534); + + // reserved main + test(0x06, 0x0A); + test(0x12, 0x19); + test(0x25, 0x27); + test(0xC0, 0xFA); + test(0xFF, 0xFF); + + // reserved gc, string + testcase("Wasm reserved opcodes gc"); + test2(0xFB, 0x00, 0xBF); // not supported by compiler + + // reserved FC + testcase("Wasm reserved opcodes FC"); + test2(0xFC, 0x00, 0x07); // floats, disabled + test2(0xFC, 0x12, 0x1F); + + // reserved SIMD + testcase("Wasm reserved opcodes SIMD"); + test2(0xFD, 0x9A, 0x9A); + test2(0xFD, 0xA2, 0xA2); + test2(0xFD, 0xA5, 0xA6); + test2(0xFD, 0xAF, 0xB0); + test2(0xFD, 0xB2, 0xB4); + test2(0xFD, 0xB8, 0xB8); + test2(0xFD, 0xC2, 0xC2); + test2(0xFD, 0xC5, 0xC6); + test2(0xFD, 0xCF, 0xD0); + test2(0xFD, 0xD2, 0xD4); + test2(0xFD, 0xE2, 0xE2); + test2(0xFD, 0xEE, 0xEE); + test2(0xFD, 0x115, 0x12F); + + testcase("Wasm opcodes THREADS"); + test2(0xFE, 0x00, 0x4F); // not supported by compiler + + // FC mem instructions + testMB({0x41, 0x00, 0x41, 0x00, 0x41, 0x04, 0xFC, 0x08, 0x00, 0x00}); // memory.init + testMB({0xFC, 0x09, 0x00}); // data.drop + testMB({0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0xFC, 0x0A, 0x00, 0x00}); // memory.copy + testMB({0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0xFC, 0x0B, 0x00}); // memory.fill + testMB({0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0xFC, 0x0C, 0x00, 0x00}); // table.init + testMB({0xFC, 0x0D, 0x00}); // elem.drop + testMB({0x41, 0x00, 0x41, 0x00, 0x41, 0x00, 0xFC, 0x0E, 0x00, 0x00}); // table.copy + testMB({0xD2, 0x00, 0x41, 0x00, 0xFC, 0x0F, 0x00, 0x1A}); // table.grow + testMB({0x1A, 0xFC, 0x10, 0x00, 0x1A}); // table.size + testMB({0x41, 0x00, 0xD2, 0x00, 0x41, 0x00, 0xFC, 0x11, 0x00}); // table.fill + + testcase("Wasm opcodes SIMD"); + // clang-format off + + // generated by auggie + // SIMD instructions + testMB({0x41, 0x00, 0xFD, 0x00, 0x04, 0x00, 0x1A}); // v128.load + testMB({0x41, 0x00, 0xFD, 0x01, 0x03, 0x00, 0x1A}); // v128.load8x8_s + testMB({0x41, 0x00, 0xFD, 0x02, 0x03, 0x00, 0x1A}); // v128.load8x8_u + testMB({0x41, 0x00, 0xFD, 0x03, 0x03, 0x00, 0x1A}); // v128.load16x4_s + testMB({0x41, 0x00, 0xFD, 0x04, 0x03, 0x00, 0x1A}); // v128.load16x4_u + testMB({0x41, 0x00, 0xFD, 0x05, 0x03, 0x00, 0x1A}); // v128.load32x2_s + testMB({0x41, 0x00, 0xFD, 0x06, 0x03, 0x00, 0x1A}); // v128.load32x2_u + testMB({0x41, 0x00, 0xFD, 0x07, 0x00, 0x00, 0x1A}); // v128.load8_splat + testMB({0x41, 0x00, 0xFD, 0x08, 0x01, 0x00, 0x1A}); // v128.load16_splat + testMB({0x41, 0x00, 0xFD, 0x09, 0x02, 0x00, 0x1A}); // v128.load32_splat + testMB({0x41, 0x00, 0xFD, 0x0A, 0x03, 0x00, 0x1A}); // v128.load64_splat + testMB({0x41, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0B, 0x04, 0x00}); // v128.store + testMB({0xFD, 0x0C, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, + 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1A}); // v128.const + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0D, 0x00, 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x1A}); // i8x16.shuffle + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0E, 0x1A}); // i8x16.swizzle + testMB({0x41, 0x2A, 0xFD, 0x0F, 0x1A}); // i8x16.splat + testMB({0x41, 0x2A, 0xFD, 0x10, 0x1A}); // i16x8.splat + testMB({0x41, 0x2A, 0xFD, 0x11, 0x1A}); // i32x4.splat + testMB({0x42, 0x2A, 0xFD, 0x12, 0x1A}); // i64x2.splat + + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x15, 0x00, 0x1A}); // i8x16.extract_lane_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x16, 0x00, 0x1A}); // i8x16.extract_lane_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x2A, 0xFD, 0x17, 0x00, 0x1A}); // i8x16.replace_lane + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x18, 0x00, 0x1A}); // i16x8.extract_lane_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x19, 0x00, 0x1A}); // i16x8.extract_lane_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x2A, 0xFD, 0x1A, 0x00, 0x1A}); // i16x8.replace_lane + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x1B, 0x00, 0x1A}); // i32x4.extract_lane + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x2A, 0xFD, 0x1C, 0x00, 0x1A}); // i32x4.replace_lane + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x1D, 0x00, 0x1A}); // i64x2.extract_lane + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x2A, 0xFD, 0x1E, 0x00, 0x1A}); // i64x2.replace_lane + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x1F, 0x00, 0x1A}); // f32x4.extract_lane + testMB( + {0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x80, 0x3F, 0xFD, 0x20, 0x00, 0x1A}); // f32x4.replace_lane + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x21, 0x00, 0x1A}); // f64x2.extract_lane + testMB( + {0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x3F, 0xFD, 0x22, 0x00, 0x1A}); // f64x2.replace_lane + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x23, 0x1A}); // i8x16.eq + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x24, 0x1A}); // i8x16.ne + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x25, 0x1A}); // i8x16.lt_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x26, 0x1A}); // i8x16.lt_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x27, 0x1A}); // i8x16.gt_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x28, 0x1A}); // i8x16.gt_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x29, 0x1A}); // i8x16.le_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x2A, 0x1A}); // i8x16.le_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x2B, 0x1A}); // i8x16.ge_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x2C, 0x1A}); // i8x16.ge_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x2D, 0x1A}); // i16x8.eq + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x2E, 0x1A}); // i16x8.ne + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x2F, 0x1A}); // i16x8.lt_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x30, 0x1A}); // i16x8.lt_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x31, 0x1A}); // i16x8.gt_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x32, 0x1A}); // i16x8.gt_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x33, 0x1A}); // i16x8.le_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x34, 0x1A}); // i16x8.le_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x35, 0x1A}); // i16x8.ge_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x36, 0x1A}); // i16x8.ge_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x37, 0x1A}); // i32x4.eq + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x38, 0x1A}); // i32x4.ne + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x39, 0x1A}); // i32x4.lt_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x3A, 0x1A}); // i32x4.lt_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x3B, 0x1A}); // i32x4.gt_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x3C, 0x1A}); // i32x4.gt_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x3D, 0x1A}); // i32x4.le_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x3E, 0x1A}); // i32x4.le_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x3F, 0x1A}); // i32x4.ge_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x40, 0x1A}); // i32x4.ge_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x4D, 0x1A}); // v128.not + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x4E, 0x1A}); // v128.and + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x4F, 0x1A}); // v128.andnot + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x50, 0x1A}); // v128.or + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x51, 0x1A}); // v128.xor + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x52, 0x1A}); // v128.bitselect + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x53, 0x1A}); // v128.any_true + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x60, 0x1A}); // i8x16.abs + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x61, 0x1A}); // i8x16.neg + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x62, 0x1A}); // i8x16.popcnt + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x63, 0x1A}); // i8x16.all_true + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x64, 0x1A}); // i8x16.bitmask + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x01, 0xFD, 0x6B, 0x1A}); // i8x16.shl + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x01, 0xFD, 0x6C, 0x1A}); // i8x16.shr_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x01, 0xFD, 0x6D, 0x1A}); // i8x16.shr_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x6E, 0x1A}); // i8x16.add + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x6F, 0x1A}); // i8x16.add_sat_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x70, 0x1A}); // i8x16.add_sat_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x71, 0x1A}); // i8x16.sub + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x72, 0x1A}); // i8x16.sub_sat_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x73, 0x1A}); // i8x16.sub_sat_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x76, 0x1A}); // i8x16.min_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x77, 0x1A}); // i8x16.min_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x78, 0x1A}); // i8x16.max_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x79, 0x1A}); // i8x16.max_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x7B, 0x1A}); // i8x16.avgr_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x80, 0x01, 0x1A}); // i16x8.abs + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x81, 0x01, 0x1A}); // i16x8.neg + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x83, 0x01, 0x1A}); // i16x8.all_true + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x84, 0x01, 0x1A}); // i16x8.bitmask + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x01, 0xFD, 0x8B, 0x01, 0x1A}); // i16x8.shl + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x01, 0xFD, 0x8C, 0x01, 0x1A}); // i16x8.shr_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x01, 0xFD, 0x8D, 0x01, 0x1A}); // i16x8.shr_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x8E, 0x01, 0x1A}); // i16x8.add + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x8F, 0x01, 0x1A}); // i16x8.add_sat_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x90, 0x01, 0x1A}); // i16x8.add_sat_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x91, 0x01, 0x1A}); // i16x8.sub + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x92, 0x01, 0x1A}); // i16x8.sub_sat_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x93, 0x01, 0x1A}); // i16x8.sub_sat_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x95, 0x01, 0x1A}); // i16x8.mul + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x96, 0x01, 0x1A}); // i16x8.min_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x97, 0x01, 0x1A}); // i16x8.min_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x98, 0x01, 0x1A}); // i16x8.max_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x99, 0x01, 0x1A}); // i16x8.max_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x9B, 0x01, 0x1A}); // i16x8.avgr_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xA0, 0x01, 0x1A}); // i32x4.abs + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xA1, 0x01, 0x1A}); // i32x4.neg + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xA3, 0x01, 0x1A}); // i32x4.all_true + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xA4, 0x01, 0x1A}); // i32x4.bitmask + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x01, 0xFD, 0xAB, 0x01, 0x1A}); // i32x4.shl + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x01, 0xFD, 0xAC, 0x01, 0x1A}); // i32x4.shr_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x01, 0xFD, 0xAD, 0x01, 0x1A}); // i32x4.shr_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xAE, 0x01, 0x1A}); // i32x4.add + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xB1, 0x01, 0x1A}); // i32x4.sub + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xB5, 0x01, 0x1A}); // i32x4.mul + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xB6, 0x01, 0x1A}); // i32x4.min_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xB7, 0x01, 0x1A}); // i32x4.min_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xB8, 0x01, 0x1A}); // i32x4.max_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xB9, 0x01, 0x1A}); // i32x4.max_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xBA, 0x01, 0x1A}); // i32x4.dot_i16x8_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xC0, 0x01, 0x1A}); // i64x2.abs + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xC1, 0x01, 0x1A}); // i64x2.neg + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xC3, 0x01, 0x1A}); // i64x2.all_true + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xC4, 0x01, 0x1A}); // i64x2.bitmask + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x01, 0xFD, 0xCB, 0x01, 0x1A}); // i64x2.shl + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x01, 0xFD, 0xCC, 0x01, 0x1A}); // i64x2.shr_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x01, 0xFD, 0xCD, 0x01, 0x1A}); // i64x2.shr_u + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xCE, 0x01, 0x1A}); // i64x2.add + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xD1, 0x01, 0x1A}); // i64x2.sub + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xD5, 0x01, 0x1A}); // i64x2.mul + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xD6, 0x01, 0x1A}); // i64x2.eq + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xD7, 0x01, 0x1A}); // i64x2.ne + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xD8, 0x01, 0x1A}); // i64x2.lt_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xD9, 0x01, 0x1A}); // i64x2.gt_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xDA, 0x01, 0x1A}); // i64x2.le_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xDB, 0x01, 0x1A}); // i64x2.ge_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xF8, 0x01, 0x1A}); // i32x4.trunc_sat_f32x4_s + testMB({0xFD, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0xF9, 0x01, 0x1A}); // i32x4.trunc_sat_f32x4_u + + // clang-format on + } + } + + void + run() override + { + using namespace test::jtx; + + testGetDataHelperFunctions(); + testWasmLib(); + testBadWasm(); + testWasmLedgerSqn(); + testImpExp(); + + testWasmFib(); + + testHFCost(); + testEscrowWasmDN(); + testFloat(); + + testCodecovWasm(); + testDisabledFloat(); + + testWasmMemory(); + testWasmTable(); + testWasmProposal(); + testWasmTrap(); + testWasmWasi(); + testWasmSectionCorruption(); + + testStartFunctionLoop(); + testBadAlign(); + testReturnType(); + testSwapBytes(); + testManyParams(); + testParameterType(); + + testOpcodes(); + } +}; + +BEAST_DEFINE_TESTSUITE(Wasm, app, xrpl); + +} // namespace test +} // namespace xrpl diff --git a/src/test/app/wasm_fixtures/.gitignore b/src/test/app/wasm_fixtures/.gitignore new file mode 100644 index 00000000000..08b2e8a2569 --- /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 00000000000..c3cd0e33420 --- /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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +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.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +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.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "xrpl-address-macro" +version = "0.7.1" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#1b27d310dedb4d1279407cf3a40f6858be0e3f14" +dependencies = [ + "bs58", + "quote", + "sha2", + "syn", +] + +[[package]] +name = "xrpl-wasm-stdlib" +version = "0.7.1" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#1b27d310dedb4d1279407cf3a40f6858be0e3f14" +dependencies = [ + "xrpl-address-macro", +] 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 00000000000..82f6ce05a35 --- /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" } + +[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 00000000000..444fcaddfa2 --- /dev/null +++ b/src/test/app/wasm_fixtures/all_host_functions/src/lib.rs @@ -0,0 +1,783 @@ +#![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 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::get_ledger_sqn(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::get_parent_ledger_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::get_parent_ledger_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::get_tx_field( + sfield::Account, + 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::get_tx_field(sfield::Fee, 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::get_tx_field(sfield::Sequence, 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, 0x00]; // Simple locator for first element + let mut nested_buffer = [0u8; 32]; + let nested_result = unsafe { + host::get_tx_nested_field( + 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::get_tx_array_len(sfield::Signers) }; + let _ = trace_num("Signers array length:", signers_len as i64); + + let memos_len = unsafe { host::get_tx_array_len(sfield::Memos) }; + 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::get_tx_nested_array_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::get_current_ledger_obj_field( + sfield::Balance, + 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::get_current_ledger_obj_field( + sfield::Account, + 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, 0x00]; // Simple locator + let mut current_nested_buffer = [0u8; 32]; + let current_nested_result = unsafe { + host::get_current_ledger_obj_nested_field( + 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::get_current_ledger_obj_array_len(sfield::Signers) }; + 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::get_current_ledger_obj_nested_array_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::account_keylet( + 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: account_keylet failed for caching test:", + keylet_result as i64, + ); + return -401; // Keylet generation failed for caching test + } + + let cache_result = + unsafe { host::cache_ledger_obj(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::get_ledger_obj_field( + 1, + sfield::Balance, + 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, 0x00]; + let nested_result = unsafe { + host::get_ledger_obj_nested_field( + 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::get_ledger_obj_array_len(1, sfield::Signers) }; + 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::get_ledger_obj_nested_array_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::get_ledger_obj_field( + slot, + sfield::Balance, + 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, 0x00]; + let mut cached_nested_buffer = [0u8; 32]; + let cached_nested_result = unsafe { + host::get_ledger_obj_nested_field( + 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::get_ledger_obj_array_len(slot, sfield::Signers) }; + 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::get_ledger_obj_nested_array_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: account_keylet() - Generate keylet for account + let mut account_keylet_buffer = [0u8; 32]; + let account_keylet_result = unsafe { + host::account_keylet( + account_id.0.as_ptr(), + account_id.0.len(), + account_keylet_buffer.as_mut_ptr(), + account_keylet_buffer.len(), + ) + }; + + if account_keylet_result != 32 { + let _ = trace_num( + "ERROR: account_keylet failed:", + account_keylet_result as i64, + ); + return -501; // Account keylet generation failed + } + let _ = trace_data("Account keylet:", &account_keylet_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_keylet( + 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_keylet( + 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_keylet( + 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::compute_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::get_nft( + 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::update_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 00000000000..ae759c2b09a --- /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.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[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.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +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.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +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.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "xrpl-address-macro" +version = "0.7.1" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=fix-keylets#591160f2dc794b7894e7402dc0c956fca746eedb" +dependencies = [ + "bs58", + "quote", + "sha2", + "syn", +] + +[[package]] +name = "xrpl-wasm-stdlib" +version = "0.7.1" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git?branch=fix-keylets#591160f2dc794b7894e7402dc0c956fca746eedb" +dependencies = [ + "xrpl-address-macro", +] 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 00000000000..f3e36c1646b --- /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 = "fix-keylets" } + +[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 00000000000..e3d9ab1b190 --- /dev/null +++ b/src/test/app/wasm_fixtures/all_keylets/src/lib.rs @@ -0,0 +1,181 @@ +#![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::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::types::account_id::AccountID; +use xrpl_std::core::types::currency::Currency; +use xrpl_std::core::types::issue::{IouIssue, Issue, XrpIssue}; +use xrpl_std::core::types::keylets; +use xrpl_std::core::types::mpt_id::MptId; +use xrpl_std::core::types::uint::Hash256; +use xrpl_std::host; +use xrpl_std::host::trace::{trace, trace_account, trace_data, trace_num, DataRepr}; +use xrpl_std::sfield; + +#[unsafe(no_mangle)] +pub fn object_exists( + keylet_result: Result, + keylet_type: &str, + field: i32, +) -> Result { + match keylet_result { + Ok(keylet) => { + let _ = trace_data(keylet_type, &keylet, DataRepr::AsHex); + + let slot = unsafe { host::cache_ledger_obj(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.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, 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); + } + } + } + + Ok(true) + } + Err(error) => { + let _ = trace_num("Error getting keylet: ", error.into()); + Err(error) + } + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn finish() -> i32 { + let _ = trace("$$$$$ STARTING WASM EXECUTION $$$$$"); + + let escrow: CurrentEscrow = get_current_escrow(); + + let account = escrow.get_account().unwrap_or_panic(); + let _ = trace_account("Account:", &account); + + let destination = escrow.get_destination().unwrap_or_panic(); + let _ = trace_account("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 account_keylet = keylets::account_keylet(&account); + check_object_exists!(account_keylet, "Account", sfield::Account); + + let currency_code: &[u8; 3] = b"USD"; + let currency: Currency = Currency::from(*currency_code); + let line_keylet = keylets::line_keylet(&account, &destination, ¤cy); + check_object_exists!(line_keylet, "Trustline", sfield::Generic); + seq += 1; + + let asset1 = Issue::XRP(XrpIssue {}); + let asset2 = Issue::IOU(IouIssue::new(destination, currency)); + check_object_exists!( + keylets::amm_keylet(&asset1, &asset2), + "AMM", + sfield::Account + ); + + let check_keylet = keylets::check_keylet(&account, seq); + check_object_exists!(check_keylet, "Check", sfield::Account); + seq += 1; + + let cred_type: &[u8] = b"termsandconditions"; + let credential_keylet = keylets::credential_keylet(&account, &account, cred_type); + check_object_exists!(credential_keylet, "Credential", sfield::Subject); + seq += 1; + + let delegate_keylet = keylets::delegate_keylet(&account, &destination); + check_object_exists!(delegate_keylet, "Delegate", sfield::Account); + seq += 1; + + let deposit_preauth_keylet = keylets::deposit_preauth_keylet(&account, &destination); + check_object_exists!(deposit_preauth_keylet, "DepositPreauth", sfield::Account); + seq += 1; + + let did_keylet = keylets::did_keylet(&account); + check_object_exists!(did_keylet, "DID", sfield::Account); + seq += 1; + + let escrow_keylet = keylets::escrow_keylet(&account, seq); + check_object_exists!(escrow_keylet, "Escrow", sfield::Account); + seq += 1; + + let mpt_issuance_keylet = keylets::mpt_issuance_keylet(&account, seq); + let mpt_id = MptId::new(seq.try_into().unwrap(), account); + check_object_exists!(mpt_issuance_keylet, "MPTIssuance", sfield::Issuer); + seq += 1; + + let mptoken_keylet = keylets::mptoken_keylet(&mpt_id, &destination); + check_object_exists!(mptoken_keylet, "MPToken", sfield::Account); + + let nft_offer_keylet = keylets::nft_offer_keylet(&destination, 6); + check_object_exists!(nft_offer_keylet, "NFTokenOffer", sfield::Owner); + + let offer_keylet = keylets::offer_keylet(&account, seq); + check_object_exists!(offer_keylet, "Offer", sfield::Account); + seq += 1; + + let paychan_keylet = keylets::paychan_keylet(&account, &destination, seq); + check_object_exists!(paychan_keylet, "PayChannel", sfield::Account); + seq += 1; + + let pd_keylet = keylets::permissioned_domain_keylet(&account, seq); + check_object_exists!(pd_keylet, "PermissionedDomain", sfield::Owner); + seq += 1; + + let signers_keylet = keylets::signers_keylet(&account); + check_object_exists!(signers_keylet, "SignerList", sfield::Generic); + seq += 1; + + seq += 1; // ticket sequence number is one greater + let ticket_keylet = keylets::ticket_keylet(&account, seq); + check_object_exists!(ticket_keylet, "Ticket", sfield::Account); + seq += 1; + + let vault_keylet = keylets::vault_keylet(&account, seq); + check_object_exists!(vault_keylet, "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 00000000000..cc291c70f13 --- /dev/null +++ b/src/test/app/wasm_fixtures/bad_align.c @@ -0,0 +1,43 @@ +#include + +int32_t float_from_uint(uint8_t const *, int32_t, uint8_t *, int32_t, int32_t); +int32_t check_keylet(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_keylet with misaligned uint32 at &e_data2[1] to hit line 72 in + // HostFuncWrapper.cpp + int32_t result = + check_keylet(&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 00000000000..c2996b48cb4 --- /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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +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.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +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.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "xrpl-address-macro" +version = "0.7.1" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#1b27d310dedb4d1279407cf3a40f6858be0e3f14" +dependencies = [ + "bs58", + "quote", + "sha2", + "syn", +] + +[[package]] +name = "xrpl-wasm-stdlib" +version = "0.7.1" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#1b27d310dedb4d1279407cf3a40f6858be0e3f14" +dependencies = [ + "xrpl-address-macro", +] 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 00000000000..5031bcb5665 --- /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" } 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 00000000000..c99a0047d5e --- /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 get_parent_ledger_hash(out_buff_ptr: i32, out_buff_len: i32) -> i32; + + pub fn cache_ledger_obj(keylet_ptr: i32, keylet_len: i32, cache_num: i32) -> i32; + + pub fn get_tx_nested_array_len(locator_ptr: i32, locator_len: i32) -> i32; + + pub fn account_keylet( + account_ptr: i32, + account_len: i32, + out_buff_ptr: *mut u8, + out_buff_len: usize, + ) -> i32; + + pub fn line_keylet( + 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 00000000000..918bbb5f099 --- /dev/null +++ b/src/test/app/wasm_fixtures/codecov_tests/src/lib.rs @@ -0,0 +1,1813 @@ +#![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::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::keylets; +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 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::get_ledger_sqn(ptr, len) }, + 4, + "get_ledger_sqn", + ); + }); + with_buffer::<4, _, _>(|ptr, len| { + check_result( + unsafe { host::get_parent_ledger_time(ptr, len) }, + 4, + "get_parent_ledger_time", + ); + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { host::get_parent_ledger_hash(ptr, len) }, + 32, + "get_parent_ledger_hash", + ); + }); + with_buffer::<4, _, _>(|ptr, len| { + check_result(unsafe { host::get_base_fee(ptr, len) }, 4, "get_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::account_keylet(&account).unwrap_or_panic(); // account_keylet under the hood + check_result( + unsafe { host::cache_ledger_obj(keylet.as_ptr(), keylet.len(), 0) }, + 1, + "cache_ledger_obj", + ); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { host::get_current_ledger_obj_field(sfield::Account, ptr, len) }, + 20, + "get_current_ledger_obj_field", + ); + }); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { host::get_ledger_obj_field(1, sfield::Account, ptr, len) }, + 20, + "get_ledger_obj_field", + ); + }); + let mut locator = Locator::new(); + locator.pack(sfield::Account); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { host::get_tx_nested_field(locator.as_ptr(), locator.len(), ptr, len) }, + 20, + "get_tx_nested_field", + ); + }); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { + host::get_current_ledger_obj_nested_field(locator.as_ptr(), locator.len(), ptr, len) + }, + 20, + "get_current_ledger_obj_nested_field", + ); + }); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { + host::get_ledger_obj_nested_field(1, locator.as_ptr(), locator.len(), ptr, len) + }, + 20, + "get_ledger_obj_nested_field", + ); + }); + check_result( + unsafe { host::get_tx_array_len(sfield::Memos) }, + 32, + "get_tx_array_len", + ); + check_result( + unsafe { host::get_current_ledger_obj_array_len(sfield::Memos) }, + 32, + "get_current_ledger_obj_array_len", + ); + check_result( + unsafe { host::get_ledger_obj_array_len(1, sfield::Memos) }, + 32, + "get_ledger_obj_array_len", + ); + check_result( + unsafe { host::get_tx_nested_array_len(locator.as_ptr(), locator.len()) }, + 32, + "get_tx_nested_array_len", + ); + check_result( + unsafe { host::get_current_ledger_obj_nested_array_len(locator.as_ptr(), locator.len()) }, + 32, + "get_current_ledger_obj_nested_array_len", + ); + check_result( + unsafe { host::get_ledger_obj_nested_array_len(1, locator.as_ptr(), locator.len()) }, + 32, + "get_ledger_obj_nested_array_len", + ); + check_result( + unsafe { host::update_data(account.0.as_ptr(), account.0.len()) }, + 20, + "update_data", + ); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { host::compute_sha512_half(locator.as_ptr(), locator.len(), ptr, len) }, + 32, + "compute_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::get_nft( + account.0.as_ptr(), + account.0.len(), + nft_id.as_ptr(), + nft_id.len(), + ptr, + len, + ) + }, + 18, + "get_nft", + ) + }); + with_buffer::<20, _, _>(|ptr, len| { + check_result( + unsafe { host::get_nft_issuer(nft_id.as_ptr(), nft_id.len(), ptr, len) }, + 20, + "get_nft_issuer", + ) + }); + with_buffer::<4, _, _>(|ptr, len| { + check_result( + unsafe { host::get_nft_taxon(nft_id.as_ptr(), nft_id.len(), ptr, len) }, + 4, + "get_nft_taxon", + ) + }); + check_result( + unsafe { host::get_nft_flags(nft_id.as_ptr(), nft_id.len()) }, + 8, + "get_nft_flags", + ); + check_result( + unsafe { host::get_nft_transfer_fee(nft_id.as_ptr(), nft_id.len()) }, + 10, + "get_nft_transfer_fee", + ); + with_buffer::<4, _, _>(|ptr, len| { + check_result( + unsafe { host::get_nft_serial(nft_id.as_ptr(), nft_id.len(), ptr, len) }, + 4, + "get_nft_serial", + ) + }); + let message = "testing trace"; + check_result( + unsafe { + host::trace_account( + message.as_ptr(), + message.len(), + account.0.as_ptr(), + account.0.len(), + ) + }, + 0, + "trace_account", + ); + let amount = &[0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F]; // 95 drops of XRP + check_result( + unsafe { + host::trace_amount( + message.as_ptr(), + message.len(), + amount.as_ptr(), + amount.len(), + ) + }, + 0, + "trace_amount", + ); + let amount = &[0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; // 0 drops of XRP + check_result( + unsafe { + host::trace_amount( + message.as_ptr(), + message.len(), + amount.as_ptr(), + amount.len(), + ) + }, + 0, + "trace_amount_zero", + ); + + // ######################################## + // Step #2: Test set_data edge cases + // ######################################## + check_result( + unsafe { host_bindings_loose::get_parent_ledger_hash(-1, 4) }, + error_codes::INVALID_PARAMS, + "get_parent_ledger_hash_neg_ptr", + ); + with_buffer::<4, _, _>(|ptr, _len| { + check_result( + unsafe { host_bindings_loose::get_parent_ledger_hash(ptr as i32, -1) }, + error_codes::INVALID_PARAMS, + "get_parent_ledger_hash_neg_len", + ) + }); + with_buffer::<3, _, _>(|ptr, len| { + check_result( + unsafe { host_bindings_loose::get_parent_ledger_hash(ptr as i32, len as i32) }, + error_codes::BUFFER_TOO_SMALL, + "get_parent_ledger_hash_buf_too_small", + ) + }); + with_buffer::<4, _, _>(|ptr, _len| { + check_result( + unsafe { host_bindings_loose::get_parent_ledger_hash(ptr as i32, 1_000_000_000) }, + error_codes::POINTER_OUT_OF_BOUNDS, + "get_parent_ledger_hash_len_too_long", + ) + }); + + // ######################################## + // Step #3: Test getData[Type] edge cases + // ######################################## + + // SField + check_result( + unsafe { host::get_tx_array_len(2) }, // not a valid SField value + error_codes::INVALID_FIELD, + "get_tx_array_len_invalid_sfield", + ); + + // Slice + check_result( + unsafe { host_bindings_loose::get_tx_nested_array_len(-1, locator.len() as i32) }, + error_codes::INVALID_PARAMS, + "get_tx_nested_array_len_neg_ptr", + ); + check_result( + unsafe { host_bindings_loose::get_tx_nested_array_len(locator.as_ptr() as i32, -1) }, + error_codes::INVALID_PARAMS, + "get_tx_nested_array_len_neg_len", + ); + let long_len = DEFAULT_BLOB_SIZE + 1; + check_result( + unsafe { + host_bindings_loose::get_tx_nested_array_len(locator.as_ptr() as i32, long_len as i32) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "get_tx_nested_array_len_too_long", + ); + check_result( + unsafe { + host_bindings_loose::get_tx_nested_array_len( + locator.as_ptr() as i32 + 1_000_000_000, + locator.len() as i32, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "get_tx_nested_array_len_ptr_oob", + ); + + // uint32 + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::check_keylet( + 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_keylet_oob_len_u32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::check_keylet( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "check_keylet_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_ledger_obj( + locator.as_ptr() as i32 + 1_000_000_000, + locator.len() as i32, + 1, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "cache_ledger_obj_ptr_oob", + ); + check_result( + unsafe { + host_bindings_loose::cache_ledger_obj(locator.as_ptr() as i32, locator.len() as i32, 1) + }, + error_codes::INVALID_PARAMS, + "cache_ledger_obj_wrong_len", + ); + + // AccountID + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host_bindings_loose::account_keylet( + locator.as_ptr() as i32 + 1_000_000_000, + locator.len() as i32, + ptr, + len, + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "account_keylet_len_oob", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host_bindings_loose::account_keylet( + locator.as_ptr() as i32, + locator.len() as i32, + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "account_keylet_wrong_len", + ) + }); + + // Currency + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host_bindings_loose::line_keylet( + 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, + "line_keylet_len_oob_currency", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host_bindings_loose::line_keylet( + 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, + "line_keylet_wrong_len_currency", + ) + }); + + // Issue + let asset1_bytes = Issue::XRP(XrpIssue {}).as_bytes(); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_keylet( + 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_keylet_len_oob_asset2", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_keylet( + asset1_bytes.as_ptr(), + asset1_bytes.len(), + locator.as_ptr(), + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "amm_keylet_len_wrong_len_asset2", + ) + }); + let currency: &[u8] = b"USD00000000000000000"; // 20 bytes + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_keylet( + asset1_bytes.as_ptr(), + asset1_bytes.len(), + currency.as_ptr(), + currency.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "amm_keylet_len_wrong_non_xrp_currency_len", + ) + }); + let xrp_issue: &[u8] = &[0; 40]; // 40 bytes + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_keylet( + xrp_issue.as_ptr(), + xrp_issue.len(), + asset1_bytes.as_ptr(), + asset1_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "amm_keylet_len_wrong_xrp_currency_len", + ) + }); + let mptid = MptId::new(1, account); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_keylet( + mptid.as_ptr(), + mptid.len(), + asset1_bytes.as_ptr(), + asset1_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "amm_keylet_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::get_tx_field(2, ptr, len) }, + error_codes::INVALID_FIELD, + "get_tx_field_invalid_sfield", + ); + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::get_current_ledger_obj_field(2, ptr, len) }, + error_codes::INVALID_FIELD, + "get_current_ledger_obj_field_invalid_sfield", + ); + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::get_ledger_obj_field(1, 2, ptr, len) }, + error_codes::INVALID_FIELD, + "get_ledger_obj_field_invalid_sfield", + ); + }); + check_result( + unsafe { host::get_tx_array_len(2) }, + error_codes::INVALID_FIELD, + "get_tx_array_len_invalid_sfield", + ); + check_result( + unsafe { host::get_current_ledger_obj_array_len(2) }, + error_codes::INVALID_FIELD, + "get_current_ledger_obj_array_len_invalid_sfield", + ); + check_result( + unsafe { host::get_ledger_obj_array_len(1, 2) }, + error_codes::INVALID_FIELD, + "get_ledger_obj_array_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::get_tx_nested_field(locator.as_ptr(), long_len, ptr, len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "get_tx_nested_field_too_big_slice", + ); + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::get_current_ledger_obj_nested_field(locator.as_ptr(), long_len, ptr, len) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "get_current_ledger_obj_nested_field_too_big_slice", + ); + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::get_ledger_obj_nested_field(1, locator.as_ptr(), long_len, ptr, len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "get_ledger_obj_nested_field_too_big_slice", + ); + }); + check_result( + unsafe { host::get_tx_nested_array_len(locator.as_ptr(), long_len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "get_tx_nested_array_len_too_big_slice", + ); + check_result( + unsafe { host::get_current_ledger_obj_nested_array_len(locator.as_ptr(), long_len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "get_current_ledger_obj_nested_array_len_too_big_slice", + ); + check_result( + unsafe { host::get_ledger_obj_nested_array_len(1, locator.as_ptr(), long_len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "get_ledger_obj_nested_array_len_too_big_slice", + ); + let too_big_data_len = XRPL_CONTRACT_DATA_SIZE + 1; + check_result( + unsafe { host::update_data(locator.as_ptr(), too_big_data_len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "update_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::compute_sha512_half(locator.as_ptr(), long_len, ptr, len) }, + error_codes::DATA_FIELD_TOO_LARGE, + "compute_sha512_half_too_big_slice", + ); + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::amm_keylet( + asset1_bytes.as_ptr(), + long_len, + asset1_bytes.as_ptr(), + asset1_bytes.len(), + ptr, + len, + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "amm_keylet_too_big_slice", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::credential_keylet( + 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_keylet_too_big_slice", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::mptoken_keylet( + mptid.as_ptr(), + long_len, + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "mptoken_keylet_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_opaque_float( + message.as_ptr(), + message.len(), + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_opaque_float_oob_slice", + ); + check_result( + unsafe { + host::trace_amount( + message.as_ptr(), + message.len(), + locator.as_ptr().wrapping_add(1_000_000_000), + locator.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_amount_oob_slice", + ); + check_result( + unsafe { + host::float_compare( + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + float.as_ptr(), + float.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_compare_oob_slice1", + ); + check_result( + unsafe { + host::float_compare( + float.as_ptr(), + float.len(), + float.as_ptr().wrapping_add(1_000_000_000), + float.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "float_compare_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_subtract( + 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_subtract_oob_slice1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_subtract( + 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_subtract_oob_slice2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_multiply( + 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_multiply_oob_slice1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_multiply( + 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_multiply_oob_slice2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_divide( + 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_divide_oob_slice1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_divide( + 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_divide_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", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::float_log( + 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_log_oob_slice", + ) + }); + + // invalid UInt32 + + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::escrow_keylet( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "escrow_keylet_wrong_size_uint32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::mpt_issuance_keylet( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "mpt_issuance_keylet_wrong_size_uint32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::nft_offer_keylet( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "nft_offer_keylet_wrong_size_uint32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::offer_keylet( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "offer_keylet_wrong_size_uint32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::oracle_keylet( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "oracle_keylet_wrong_size_uint32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::paychan_keylet( + 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_keylet_wrong_size_uint32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::permissioned_domain_keylet( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "permissioned_domain_keylet_wrong_size_uint32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::ticket_keylet( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "ticket_keylet_wrong_size_uint32", + ) + }); + with_buffer::<32, _, _>(|ptr, len| { + check_result( + unsafe { + host::vault_keylet( + account.0.as_ptr(), + account.0.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "vault_keylet_wrong_size_uint32", + ) + }); + + // invalid UInt256 + + check_result( + unsafe { host::cache_ledger_obj(locator.as_ptr(), locator.len(), 0) }, + error_codes::INVALID_PARAMS, + "cache_ledger_obj_wrong_size_uint256", + ); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::get_nft( + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "get_nft_wrong_size_uint256", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::get_nft_issuer(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "get_nft_issuer_wrong_size_uint256", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::get_nft_taxon(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "get_nft_taxon_wrong_size_uint256", + ) + }); + check_result( + unsafe { host::get_nft_flags(locator.as_ptr(), locator.len()) }, + error_codes::INVALID_PARAMS, + "get_nft_flags_wrong_size_uint256", + ); + check_result( + unsafe { host::get_nft_transfer_fee(locator.as_ptr(), locator.len()) }, + error_codes::INVALID_PARAMS, + "get_nft_transfer_fee_wrong_size_uint256", + ); + with_buffer::<4, _, _>(|ptr, len| { + check_result( + unsafe { host::get_nft_serial(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "get_nft_serial_wrong_size_uint256", + ) + }); + + // invalid AccountID + + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::account_keylet(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "account_keylet_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_keylet( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "check_keylet_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::credential_keylet( + 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_keylet_wrong_size_account_id1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::credential_keylet( + 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_keylet_wrong_size_account_id2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::delegate_keylet( + locator.as_ptr(), // invalid AccountID size + locator.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "delegate_keylet_wrong_size_account_id1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::delegate_keylet( + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), // invalid AccountID size + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "delegate_keylet_wrong_size_account_id2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::deposit_preauth_keylet( + locator.as_ptr(), // invalid AccountID size + locator.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "deposit_preauth_keylet_wrong_size_account_id1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::deposit_preauth_keylet( + account.0.as_ptr(), + account.0.len(), + locator.as_ptr(), // invalid AccountID size + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "deposit_preauth_keylet_wrong_size_account_id2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::did_keylet(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "did_keylet_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::escrow_keylet( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "escrow_keylet_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::line_keylet( + 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, + "line_keylet_wrong_size_account_id1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::line_keylet( + 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, + "line_keylet_wrong_size_account_id2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::mpt_issuance_keylet( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "mpt_issuance_keylet_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::mptoken_keylet( + mptid.as_ptr(), + mptid.len(), + locator.as_ptr(), + locator.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "mptoken_keylet_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::nft_offer_keylet( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "nft_offer_keylet_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::offer_keylet( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "offer_keylet_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::oracle_keylet( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "oracle_keylet_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::paychan_keylet( + 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_keylet_wrong_size_account_id1", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::paychan_keylet( + 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_keylet_wrong_size_account_id2", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::permissioned_domain_keylet( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "permissioned_domain_keylet_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { host::signers_keylet(locator.as_ptr(), locator.len(), ptr, len) }, + error_codes::INVALID_PARAMS, + "signers_keylet_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::ticket_keylet( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "ticket_keylet_wrong_size_account_id", + ) + }); + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::vault_keylet( + locator.as_ptr(), + locator.len(), + seq_bytes.as_ptr(), + seq_bytes.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "vault_keylet_wrong_size_account_id", + ) + }); + let uint256: &[u8] = b"00000000000000000000000000000001"; + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::get_nft( + locator.as_ptr(), + locator.len(), + uint256.as_ptr(), + uint256.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "get_nft_wrong_size_account_id", + ) + }); + check_result( + unsafe { + host::trace_account( + message.as_ptr(), + message.len(), + locator.as_ptr(), + locator.len(), + ) + }, + error_codes::INVALID_PARAMS, + "trace_account_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_opaque_float( + message.as_ptr().wrapping_add(1_000_000_000), + message.len(), + float.as_ptr(), + float.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_opaque_float_oob_string", + ); + check_result( + unsafe { + host::trace_account( + 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_account_oob_string", + ); + check_result( + unsafe { + host::trace_amount( + message.as_ptr().wrapping_add(1_000_000_000), + message.len(), + amount.as_ptr(), + amount.len(), + ) + }, + error_codes::POINTER_OUT_OF_BOUNDS, + "trace_amount_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_opaque_float(message.as_ptr(), long_len, float.as_ptr(), float.len()) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "trace_opaque_float_too_long", + ); + check_result( + unsafe { + host::trace_account( + message.as_ptr(), + long_len, + account.0.as_ptr(), + account.0.len(), + ) + }, + error_codes::DATA_FIELD_TOO_LARGE, + "trace_account_too_long", + ); + check_result( + unsafe { host::trace_amount(message.as_ptr(), long_len, amount.as_ptr(), amount.len()) }, + error_codes::DATA_FIELD_TOO_LARGE, + "trace_amount_too_long", + ); + + // trace amount errors + + check_result( + unsafe { + host::trace_amount( + message.as_ptr(), + message.len(), + locator.as_ptr(), + locator.len(), + ) + }, + error_codes::INVALID_PARAMS, + "trace_amount_wrong_length", + ); + + // other misc errors + + with_buffer::<2, _, _>(|ptr, len| { + check_result( + unsafe { + host::mptoken_keylet( + locator.as_ptr(), + locator.len(), + account.0.as_ptr(), + account.0.len(), + ptr, + len, + ) + }, + error_codes::INVALID_PARAMS, + "mptoken_keylet_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_account( + empty.as_ptr(), + empty.len(), + account.0.as_ptr(), + account.0.len(), + ) + }, + 0, + "trace_account_check_desync", + ); + + 1 // <-- If we get here, finish the escrow. +} diff --git a/src/test/app/wasm_fixtures/contract_data/Cargo.lock b/src/test/app/wasm_fixtures/contract_data/Cargo.lock new file mode 100644 index 00000000000..5b87de721f9 --- /dev/null +++ b/src/test/app/wasm_fixtures/contract_data/Cargo.lock @@ -0,0 +1,15 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "contract_data" +version = "0.0.1" +dependencies = [ + "xrpl-wasm-std", +] + +[[package]] +name = "xrpl-wasm-std" +version = "0.5.1-devnet5" +source = "git+https://github.com/Transia-RnD/craft.git?branch=dangell%2Fsmart-contracts#3c8191ae9832ea25f7d8f3e5eeb33b65181d31b5" diff --git a/src/test/app/wasm_fixtures/contract_data/Cargo.toml b/src/test/app/wasm_fixtures/contract_data/Cargo.toml new file mode 100644 index 00000000000..5638081ca00 --- /dev/null +++ b/src/test/app/wasm_fixtures/contract_data/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "contract_data" +version = "0.0.1" +edition = "2024" + +# 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-wasm-std = { git = "https://github.com/Transia-RnD/craft.git", branch = "dangell/smart-contracts", package = "xrpl-wasm-std" } + +[profile.dev] +panic = "abort" diff --git a/src/test/app/wasm_fixtures/contract_data/src/lib.rs b/src/test/app/wasm_fixtures/contract_data/src/lib.rs new file mode 100644 index 00000000000..965c50c06c0 --- /dev/null +++ b/src/test/app/wasm_fixtures/contract_data/src/lib.rs @@ -0,0 +1,529 @@ +#![cfg_attr(target_arch = "wasm32", no_std)] + +#[cfg(not(target_arch = "wasm32"))] +extern crate std; + +use xrpl_wasm_std::core::data::codec::{ + get_array_element, get_data, get_nested_array_element, get_nested_data, set_array_element, + set_data, set_nested_array_element, set_nested_data, +}; +use xrpl_wasm_std::core::types::account_id::AccountID; +use xrpl_wasm_std::host::trace::{trace, trace_num}; + +// Different accounts for different test patterns +const ACCOUNT: [u8; 20] = [ + 0xAE, 0x12, 0x3A, 0x85, 0x56, 0xF3, 0xCF, 0x91, 0x15, 0x47, 0x11, 0x37, 0x6A, 0xFB, 0x0F, 0x89, + 0x4F, 0x83, 0x2B, 0x3D, +]; + +// ============================================================================ +// TEST 1: Simple Object - Only top-level key-value pairs +// Creates: { "value_u8": 42, "value_u16": 1234, "count": 3, ... } +// ============================================================================ + +#[unsafe(no_mangle)] +pub extern "C" fn object_simple_create() -> i32 { + let _ = trace("=== TEST 1: Simple Object Create ==="); + let account = AccountID(ACCOUNT); + + // Test u8 + let _ = trace("Testing u8..."); + if let Err(e) = set_data::(&account, "value_u8", 42) { + return e; + } + if let Some(val) = get_data::(&account, "value_u8") { + let _ = trace_num("Read back u8:", val.into()); + } else { + let _ = trace("Failed to read back u8"); + return -1; + } + + // Test u16 + let _ = trace("Testing u16..."); + if let Err(e) = set_data::(&account, "value_u16", 1234) { + return e; + } + if let Some(val) = get_data::(&account, "value_u16") { + let _ = trace_num("Read back u16:", val.into()); + } else { + let _ = trace("Failed to read back u16"); + return -1; + } + + // Test u32 + let _ = trace("Testing u32..."); + if let Err(e) = set_data::(&account, "count", 3) { + return e; + } + if let Err(e) = set_data::(&account, "total", 12) { + return e; + } + if let Some(count_val) = get_data::(&account, "count") { + let _ = trace_num("Read back count:", count_val.into()); + } else { + let _ = trace("Failed to read back count"); + return -1; + } + + // Test u64 + let _ = trace("Testing u64..."); + if let Err(e) = set_data::(&account, "value_u64", 9876543210) { + return e; + } + if let Some(val) = get_data::(&account, "value_u64") { + let _ = trace_num("Read back u64:", val as i64); + } else { + let _ = trace("Failed to read back u64"); + return -1; + } + + // Test AccountID + let _ = trace("Testing AccountID..."); + const DESTINATION: [u8; 20] = [ + 0x05, 0x96, 0x91, 0x5C, 0xFD, 0xEE, 0xE3, 0xA6, 0x95, 0xB3, 0xEF, 0xD6, 0xBD, 0xA9, 0xAC, + 0x78, 0x8A, 0x36, 0x8B, 0x7B, + ]; + let destination = AccountID(DESTINATION); + if let Err(e) = set_data(&account, "destination", destination) { + return e; + } + if let Some(_dest) = get_data::(&account, "destination") { + let _ = trace("Read back AccountID successfully"); + } else { + let _ = trace("Failed to read back AccountID"); + return -1; + } + + // Test reading non-existent key + let _ = trace("Testing non-existent key..."); + if let Some(_) = get_data::(&account, "nonexistent") { + let _ = trace("ERROR: Should not have found nonexistent key"); + return -1; + } else { + let _ = trace("Correctly returned None for nonexistent key"); + } + + let _ = trace("Simple object create tests passed!"); + 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn object_simple_update() -> i32 { + let _ = trace("=== TEST 1: Simple Object Update ==="); + let account = AccountID(ACCOUNT); + + // Update u8 + let _ = trace("Updating u8 to 99..."); + if let Err(e) = set_data::(&account, "value_u8", 99) { + return e; + } + if let Some(val) = get_data::(&account, "value_u8") { + let _ = trace_num("Read back updated u8:", val.into()); + } else { + let _ = trace("Failed to read back u8"); + return -1; + } + + // Update u32 + let _ = trace("Updating count to 4..."); + if let Err(e) = set_data::(&account, "count", 4) { + return e; + } + if let Some(count_val) = get_data::(&account, "count") { + let _ = trace_num("Read back updated count:", count_val.into()); + } else { + let _ = trace("Failed to read back count"); + return -1; + } + + // Add new field + let _ = trace("Adding new field 'status'..."); + if let Err(e) = set_data::(&account, "status", 100) { + return e; + } + if let Some(val) = get_data::(&account, "status") { + let _ = trace_num("Read back new status:", val.into()); + } else { + let _ = trace("Failed to read back status"); + return -1; + } + + let _ = trace("Simple object update tests passed!"); + 0 +} + +// ============================================================================ +// TEST 2: Nested Object - Objects containing objects (depth 1) +// Creates: { "stats": {"score": 9999, "level": 5}, "key": {"subkey": 12} } +// ============================================================================ + +#[unsafe(no_mangle)] +pub extern "C" fn object_nested_create() -> i32 { + let _ = trace("=== TEST 2: Nested Object Create ==="); + let account = AccountID(ACCOUNT); + + // Test nested u8 + let _ = trace("Testing nested u8..."); + if let Err(e) = set_nested_data::(&account, "key", "subkey", 12) { + return e; + } + if let Some(nested_val) = get_nested_data::(&account, "key", "subkey") { + let _ = trace_num("Read back nested value:", nested_val.into()); + } else { + let _ = trace("Failed to read back nested value"); + return -1; + } + + // Test nested u32 + let _ = trace("Testing nested u32..."); + if let Err(e) = set_nested_data::(&account, "stats", "score", 9999) { + return e; + } + if let Some(val) = get_nested_data::(&account, "stats", "score") { + let _ = trace_num("Read back nested u32:", val.into()); + } else { + let _ = trace("Failed to read back nested u32"); + return -1; + } + + // Test multiple fields in same nested object + let _ = trace("Adding multiple fields to nested object..."); + if let Err(e) = set_nested_data::(&account, "stats", "level", 5) { + return e; + } + if let Err(e) = set_nested_data::(&account, "stats", "coins", 1000) { + return e; + } + if let Some(val) = get_nested_data::(&account, "stats", "level") { + let _ = trace_num("Read back stats.level:", val.into()); + } else { + let _ = trace("Failed to read back stats.level"); + return -1; + } + + // Test nested u64 + let _ = trace("Testing nested u64..."); + if let Err(e) = set_nested_data::(&account, "data", "timestamp", 1234567890) { + return e; + } + if let Some(val) = get_nested_data::(&account, "data", "timestamp") { + let _ = trace_num("Read back nested u64:", val as i64); + } else { + let _ = trace("Failed to read back nested u64"); + return -1; + } + + let _ = trace("Nested object create tests passed!"); + 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn object_nested_update() -> i32 { + let _ = trace("=== TEST 2: Nested Object Update ==="); + let account = AccountID(ACCOUNT); + + // Update nested value + let _ = trace("Updating nested score to 12345..."); + if let Err(e) = set_nested_data::(&account, "stats", "score", 12345) { + return e; + } + if let Some(val) = get_nested_data::(&account, "stats", "score") { + let _ = trace_num("Read back updated nested score:", val.into()); + } else { + let _ = trace("Failed to read back nested score"); + return -1; + } + + // Update another nested field + let _ = trace("Updating nested level to 10..."); + if let Err(e) = set_nested_data::(&account, "stats", "level", 10) { + return e; + } + if let Some(val) = get_nested_data::(&account, "stats", "level") { + let _ = trace_num("Read back updated level:", val.into()); + } else { + let _ = trace("Failed to read back level"); + return -1; + } + + // Add new nested field + let _ = trace("Adding new nested field..."); + if let Err(e) = set_nested_data::(&account, "config", "timeout", 30) { + return e; + } + if let Some(val) = get_nested_data::(&account, "config", "timeout") { + let _ = trace_num("Read back new config.timeout:", val.into()); + } else { + let _ = trace("Failed to read back config.timeout"); + return -1; + } + + let _ = trace("Nested object update tests passed!"); + 0 +} + +// ============================================================================ +// TEST 3: Object with Arrays - Objects containing arrays of simple values +// Creates: { "items": [10, 20, 30], "values": [100, 200] } +// Note: This uses set_array_element which creates an object with array fields +// ============================================================================ + +#[unsafe(no_mangle)] +pub extern "C" fn object_with_arrays_create() -> i32 { + let _ = trace("=== TEST 3: Object with Arrays Create ==="); + let account = AccountID(ACCOUNT); + + // Test u8 array + let _ = trace("Testing u8 array..."); + if let Err(e) = set_array_element::(&account, "array_u8", 0, 10) { + return e; + } + if let Err(e) = set_array_element::(&account, "array_u8", 1, 20) { + return e; + } + if let Err(e) = set_array_element::(&account, "array_u8", 2, 30) { + return e; + } + if let Some(val) = get_array_element::(&account, "array_u8", 0) { + let _ = trace_num("Read array_u8[0]:", val.into()); + } else { + let _ = trace("Failed to read array_u8[0]"); + return -1; + } + if let Some(val) = get_array_element::(&account, "array_u8", 1) { + let _ = trace_num("Read array_u8[1]:", val.into()); + } else { + let _ = trace("Failed to read array_u8[1]"); + return -1; + } + + // Test u16 array + let _ = trace("Testing u16 array..."); + if let Err(e) = set_array_element::(&account, "array_u16", 0, 100) { + return e; + } + if let Err(e) = set_array_element::(&account, "array_u16", 1, 200) { + return e; + } + if let Some(val) = get_array_element::(&account, "array_u16", 0) { + let _ = trace_num("Read array_u16[0]:", val.into()); + } else { + let _ = trace("Failed to read array_u16[0]"); + return -1; + } + + // Test u32 array + let _ = trace("Testing u32 array..."); + if let Err(e) = set_array_element::(&account, "array_u32", 0, 1000) { + return e; + } + if let Err(e) = set_array_element::(&account, "array_u32", 1, 2000) { + return e; + } + if let Some(val) = get_array_element::(&account, "array_u32", 0) { + let _ = trace_num("Read array_u32[0]:", val.into()); + } else { + let _ = trace("Failed to read array_u32[0]"); + return -1; + } + + // Test u64 array + let _ = trace("Testing u64 array..."); + if let Err(e) = set_array_element::(&account, "array_u64", 0, 10000) { + return e; + } + if let Err(e) = set_array_element::(&account, "array_u64", 1, 20000) { + return e; + } + if let Some(val) = get_array_element::(&account, "array_u64", 0) { + let _ = trace_num("Read array_u64[0]:", val as i64); + } else { + let _ = trace("Failed to read array_u64[0]"); + return -1; + } + + let _ = trace("Object with arrays create tests passed!"); + 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn object_with_arrays_update() -> i32 { + let _ = trace("=== TEST 3: Object with Arrays Update ==="); + let account = AccountID(ACCOUNT); + + // Update array element + let _ = trace("Updating array_u32[0] to 7777..."); + if let Err(e) = set_array_element::(&account, "array_u32", 0, 7777) { + return e; + } + if let Some(val) = get_array_element::(&account, "array_u32", 0) { + let _ = trace_num("Read back updated array_u32[0]:", val.into()); + } else { + let _ = trace("Failed to read back array_u32[0]"); + return -1; + } + + // Add new array element + let _ = trace("Adding new array element array_u16[2]..."); + if let Err(e) = set_array_element::(&account, "array_u16", 2, 300) { + return e; + } + if let Some(val) = get_array_element::(&account, "array_u16", 2) { + let _ = trace_num("Read back new array_u16[2]:", val.into()); + } else { + let _ = trace("Failed to read back array_u16[2]"); + return -1; + } + + // Add element with gap (should auto-fill with nulls) + let _ = trace("Adding array_u8[5] (skipping indices 3-4)..."); + if let Err(e) = set_array_element::(&account, "array_u8", 5, 50) { + return e; + } + if let Some(val) = get_array_element::(&account, "array_u8", 5) { + let _ = trace_num("Read back array_u8[5]:", val.into()); + } else { + let _ = trace("Failed to read back array_u8[5]"); + return -1; + } + + let _ = trace("Object with arrays update tests passed!"); + 0 +} + +// ============================================================================ +// TEST 4: Object with Nested Arrays - Objects containing arrays of objects +// Creates: { "nested_array": [{"field1": 55, "field2": 66}, {"field1": 77}] } +// This is the most complex structure allowed (depth 1) +// DA: I wouldn't use this. If you are doing this, consider redesigning your data model +// ============================================================================ + +#[unsafe(no_mangle)] +pub extern "C" fn object_with_nested_arrays_create() -> i32 { + let _ = trace("=== TEST 4: Object with Nested Arrays Create ==="); + let account = AccountID(ACCOUNT); + + // Test nested u8 array with multiple fields + let _ = trace("Testing nested u8 array..."); + if let Err(e) = set_nested_array_element::(&account, "nested_array", 0, "field1", 55) { + return e; + } + if let Err(e) = set_nested_array_element::(&account, "nested_array", 0, "field2", 66) { + return e; + } + if let Err(e) = set_nested_array_element::(&account, "nested_array", 1, "field1", 77) { + return e; + } + + if let Some(val) = get_nested_array_element::(&account, "nested_array", 0, "field1") { + let _ = trace_num("Read nested_array[0].field1:", val.into()); + } else { + let _ = trace("Failed to read nested_array[0].field1"); + return -1; + } + if let Some(val) = get_nested_array_element::(&account, "nested_array", 0, "field2") { + let _ = trace_num("Read nested_array[0].field2:", val.into()); + } else { + let _ = trace("Failed to read nested_array[0].field2"); + return -1; + } + if let Some(val) = get_nested_array_element::(&account, "nested_array", 1, "field1") { + let _ = trace_num("Read nested_array[1].field1:", val.into()); + } else { + let _ = trace("Failed to read nested_array[1].field1"); + return -1; + } + + // Test nested u32 array + let _ = trace("Testing nested u32 array..."); + if let Err(e) = set_nested_array_element::(&account, "nested_array_u32", 0, "value", 5555) + { + return e; + } + if let Err(e) = set_nested_array_element::(&account, "nested_array_u32", 1, "value", 6666) + { + return e; + } + if let Some(val) = get_nested_array_element::(&account, "nested_array_u32", 0, "value") { + let _ = trace_num("Read nested_array_u32[0].value:", val.into()); + } else { + let _ = trace("Failed to read nested_array_u32[0].value"); + return -1; + } + + // Test nested u64 array + let _ = trace("Testing nested u64 array..."); + if let Err(e) = set_nested_array_element::(&account, "items", 0, "id", 99999) { + return e; + } + if let Err(e) = set_nested_array_element::(&account, "items", 0, "price", 123456) { + return e; + } + if let Some(val) = get_nested_array_element::(&account, "items", 0, "id") { + let _ = trace_num("Read items[0].id:", val as i64); + } else { + let _ = trace("Failed to read items[0].id"); + return -1; + } + + let _ = trace("Object with nested arrays create tests passed!"); + 0 +} + +#[unsafe(no_mangle)] +pub extern "C" fn object_with_nested_arrays_update() -> i32 { + let _ = trace("=== TEST 4: Object with Nested Arrays Update ==="); + let account = AccountID(ACCOUNT); + + // Update nested array element + let _ = trace("Updating nested_array[0].field1 to 88..."); + if let Err(e) = set_nested_array_element::(&account, "nested_array", 0, "field1", 88) { + return e; + } + if let Some(val) = get_nested_array_element::(&account, "nested_array", 0, "field1") { + let _ = trace_num("Read back updated nested_array[0].field1:", val.into()); + } else { + let _ = trace("Failed to read back nested_array[0].field1"); + return -1; + } + + // Add new field to existing array element + let _ = trace("Adding field3 to nested_array[0]..."); + if let Err(e) = set_nested_array_element::(&account, "nested_array", 0, "field3", 111) { + return e; + } + if let Some(val) = get_nested_array_element::(&account, "nested_array", 0, "field3") { + let _ = trace_num("Read back nested_array[0].field3:", val.into()); + } else { + let _ = trace("Failed to read back nested_array[0].field3"); + return -1; + } + + // Add new array element + let _ = trace("Adding nested_array[2]..."); + if let Err(e) = set_nested_array_element::(&account, "nested_array", 2, "field1", 99) { + return e; + } + if let Some(val) = get_nested_array_element::(&account, "nested_array", 2, "field1") { + let _ = trace_num("Read back nested_array[2].field1:", val.into()); + } else { + let _ = trace("Failed to read back nested_array[2].field1"); + return -1; + } + + // Update u32 nested array + let _ = trace("Updating nested_array_u32[1].value to 8888..."); + if let Err(e) = set_nested_array_element::(&account, "nested_array_u32", 1, "value", 8888) + { + return e; + } + if let Some(val) = get_nested_array_element::(&account, "nested_array_u32", 1, "value") { + let _ = trace_num("Read back updated nested_array_u32[1].value:", val.into()); + } else { + let _ = trace("Failed to read back nested_array_u32[1].value"); + return -1; + } + + let _ = trace("Object with nested arrays update tests passed!"); + 0 +} diff --git a/src/test/app/wasm_fixtures/copyFixtures.py b/src/test/app/wasm_fixtures/copyFixtures.py new file mode 100644 index 00000000000..e5c666c05bf --- /dev/null +++ b/src/test/app/wasm_fixtures/copyFixtures.py @@ -0,0 +1,135 @@ +# cspell: disable +import os +import sys +import subprocess +import re + +OPT = "-Oz" + + +def update_fixture(project_name, wasm): + fixture_name = ( + re.sub(r"_([a-z])", lambda m: m.group(1).upper(), project_name) + "WasmHex" + ) + print(f"Updating fixture: {fixture_name}") + + cpp_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "fixtures.cpp")) + h_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "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\n extern 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 process_rust(project_name): + project_path = os.path.abspath( + os.path.join(os.path.dirname(__file__), project_name) + ) + wasm_location = f"target/wasm32v1-none/release/{project_name}.wasm" + build_cmd = ( + f"(cd {project_path} " + f"&& cargo build --target wasm32v1-none --release " + f"&& wasm-opt {wasm_location} {OPT} -o {wasm_location}" + ")" + ) + try: + result = subprocess.run( + build_cmd, shell=True, check=True, capture_output=True, text=True + ) + print(f"stdout: {result.stdout}") + if result.stderr: + print(f"stderr: {result.stderr}") + print(f"WASM file for {project_name} has been built and optimized.") + except subprocess.CalledProcessError as e: + print(f"exec error: {e}") + sys.exit(1) + + src_path = os.path.abspath( + os.path.join( + os.path.dirname(__file__), + f"{project_name}/target/wasm32v1-none/release/{project_name}.wasm", + ) + ) + with open(src_path, "rb") as f: + data = f.read() + wasm = data.hex() + update_fixture(project_name, wasm) + + +def process_c(project_name): + project_path = os.path.abspath( + os.path.join(os.path.dirname(__file__), f"{project_name}.c") + ) + wasm_path = os.path.abspath( + os.path.join(os.path.dirname(__file__), f"{project_name}.wasm") + ) + build_cmd = ( + f"$CC --sysroot=$SYSROOT " + f"-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 " + f"-o {wasm_path} {project_path}" + f"&& wasm-opt {wasm_path} {OPT} -o {wasm_path}" + ) + try: + result = subprocess.run( + build_cmd, shell=True, check=True, capture_output=True, text=True + ) + print(f"stdout: {result.stdout}") + if result.stderr: + print(f"stderr: {result.stderr}") + print( + f"WASM file for {project_name} has been built with WASI support using clang." + ) + except subprocess.CalledProcessError as e: + print(f"exec error: {e}") + sys.exit(1) + + with open(wasm_path, "rb") as f: + data = f.read() + wasm = data.hex() + update_fixture(project_name, wasm) + + +if __name__ == "__main__": + if len(sys.argv) > 2: + print("Usage: python copyFixtures.py []") + sys.exit(1) + if len(sys.argv) == 2: + if os.path.isdir(os.path.join(os.path.dirname(__file__), sys.argv[1])): + process_rust(sys.argv[1]) + else: + process_c(sys.argv[1]) + print("Fixture has been processed.") + else: + dirs = [ + d + for d in os.listdir(os.path.dirname(__file__)) + if os.path.isdir(os.path.join(os.path.dirname(__file__), d)) + ] + c_files = [f for f in os.listdir(os.path.dirname(__file__)) if f.endswith(".c")] + for d in dirs: + process_rust(d) + for c in c_files: + process_c(c[:-2]) + print("All fixtures have been processed.") diff --git a/src/test/app/wasm_fixtures/disableFloat.wat b/src/test/app/wasm_fixtures/disableFloat.wat new file mode 100644 index 00000000000..035a849e30d --- /dev/null +++ b/src/test/app/wasm_fixtures/disableFloat.wat @@ -0,0 +1,34 @@ +(module + (type (;0;) (func)) + (type (;1;) (func (result i32))) + (func (;0;) (type 0)) + (func (;1;) (type 1) (result i32) + f32.const -2048 + f32.const 2050 + f32.sub + drop + i32.const 1) + (memory (;0;) 2) + (global (;0;) i32 (i32.const 1024)) + (global (;1;) i32 (i32.const 1024)) + (global (;2;) i32 (i32.const 2048)) + (global (;3;) i32 (i32.const 2048)) + (global (;4;) i32 (i32.const 67584)) + (global (;5;) i32 (i32.const 1024)) + (global (;6;) i32 (i32.const 67584)) + (global (;7;) i32 (i32.const 131072)) + (global (;8;) i32 (i32.const 0)) + (global (;9;) i32 (i32.const 1)) + (export "memory" (memory 0)) + (export "__wasm_call_ctors" (func 0)) + (export "finish" (func 1)) + (export "buf" (global 0)) + (export "__dso_handle" (global 1)) + (export "__data_end" (global 2)) + (export "__stack_low" (global 3)) + (export "__stack_high" (global 4)) + (export "__global_base" (global 5)) + (export "__heap_base" (global 6)) + (export "__heap_end" (global 7)) + (export "__memory_base" (global 8)) + (export "__table_base" (global 9))) diff --git a/src/test/app/wasm_fixtures/emit_txn/Cargo.toml b/src/test/app/wasm_fixtures/emit_txn/Cargo.toml new file mode 100644 index 00000000000..6ae66a59140 --- /dev/null +++ b/src/test/app/wasm_fixtures/emit_txn/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "emit_txn" +version = "0.0.1" +edition = "2024" + +# 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-wasm-std = { git = "https://github.com/Transia-RnD/craft.git", branch = "dangell/smart-contracts", package = "xrpl-wasm-std" } + +[profile.dev] +panic = "abort" diff --git a/src/test/app/wasm_fixtures/emit_txn/src/lib.rs b/src/test/app/wasm_fixtures/emit_txn/src/lib.rs new file mode 100644 index 00000000000..a50df3e7834 --- /dev/null +++ b/src/test/app/wasm_fixtures/emit_txn/src/lib.rs @@ -0,0 +1,218 @@ +#![cfg_attr(target_arch = "wasm32", no_std)] + +#[cfg(not(target_arch = "wasm32"))] +extern crate std; + +use xrpl_wasm_std::core::current_tx::contract_call::{ContractCall, get_current_contract_call}; +use xrpl_wasm_std::core::current_tx::traits::TransactionCommonFields; +use xrpl_wasm_std::core::submit::inner_objects::build_memo; +use xrpl_wasm_std::core::transaction_types::TT_PAYMENT; +use xrpl_wasm_std::core::types::account_id::AccountID; +use xrpl_wasm_std::host::{add_txn_field, build_txn, emit_built_txn}; +use xrpl_wasm_std::sfield; + +// ============================================================================ +// Constants +// ============================================================================ + +/// Custom error code for transaction failures +const CUSTOM_ERROR_CODE: i32 = -18; + +/// XRPL encoding markers +mod markers { + pub const ARRAY_END: u8 = 0xF1; + pub const OBJECT_END: u8 = 0xE1; +} + +/// Buffer sizes +mod buffer_sizes { + pub const MEMO_BUFFER: usize = 256; + pub const MEMOS_ARRAY: usize = 1024; + pub const DESTINATION: usize = 21; +} + +// ============================================================================ +// Helper Functions +// ============================================================================ + +/// Builds a complete memos array from individual memo buffers +/// +/// # Arguments +/// * `buffer` - Output buffer for the complete memos array +/// * `memo_buffers` - Slice of memo data and their lengths +/// +/// # Returns +/// Total length of the memos array including the end marker +fn build_memos_array( + buffer: &mut [u8; buffer_sizes::MEMOS_ARRAY], + memo_buffers: &[(&[u8], usize)] +) -> usize { + let mut position = 0; + + // Copy each memo into the array + for (memo_data, memo_length) in memo_buffers { + buffer[position..position + memo_length].copy_from_slice(&memo_data[..*memo_length]); + position += memo_length; + } + + // Terminate the array + buffer[position] = markers::ARRAY_END; + position + 1 +} + +/// Adds the amount field to the transaction +/// +/// # Arguments +/// * `txn_index` - Transaction builder index +/// * `amount_drops` - Amount in drops (192 in this example) +/// +/// # Returns +/// Result code from add_txn_field +unsafe fn add_amount_field(txn_index: i32) -> i32 { + // 192 drops encoded as XRPL Amount + const AMOUNT_BYTES: [u8; 8] = [ + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 + ]; + + add_txn_field( + txn_index, + sfield::Amount, + AMOUNT_BYTES.as_ptr(), + AMOUNT_BYTES.len() + ) +} + +/// Adds the destination field to the transaction +/// +/// # Arguments +/// * `txn_index` - Transaction builder index +/// * `destination` - Destination account ID +/// +/// # Returns +/// Result code from add_txn_field +unsafe fn add_destination_field(txn_index: i32, destination: &AccountID) -> i32 { + let mut dest_buffer = [0u8; buffer_sizes::DESTINATION]; + dest_buffer[0] = 0x14; // Length prefix for 20-byte account + dest_buffer[1..21].copy_from_slice(&destination.0); + + add_txn_field( + txn_index, + sfield::Destination, + dest_buffer.as_ptr(), + dest_buffer.len() + ) +} + +/// Adds the memos field to the transaction +/// +/// # Arguments +/// * `txn_index` - Transaction builder index +/// +/// # Returns +/// Result code from add_txn_field +unsafe fn add_memos_field(txn_index: i32) -> i32 { + use core::mem::MaybeUninit; + + // Uninitialized backing buffer (no zeroing => no memory.fill) + let mut memos_uninit: MaybeUninit<[u8; buffer_sizes::MEMOS_ARRAY]> = MaybeUninit::uninit(); + let base = memos_uninit.as_mut_ptr() as *mut u8; + + // Helper: get a 256-byte window at current position + #[inline(always)] + unsafe fn at<'a>(base: *mut u8, pos: usize) -> &'a mut [u8; buffer_sizes::MEMO_BUFFER] { + &mut *(base.add(pos) as *mut [u8; buffer_sizes::MEMO_BUFFER]) + } + + let mut pos = 0usize; + + // Write each Memo directly into the big buffer + let len1 = build_memo( + at(base, pos), + Some(b"invoice"), + Some(b"INV-2024-001"), + Some(b"text/plain") + ); + pos += len1; + + let len2 = build_memo( + at(base, pos), + Some(b"note"), + Some(b"Payment for consulting services"), + Some(b"text/plain") + ); + pos += len2; + + let len3 = build_memo( + at(base, pos), + None, + Some(b"Additional reference: Project Alpha"), + None + ); + pos += len3; + + // Terminate the array + *base.add(pos) = markers::ARRAY_END; + pos += 1; + + add_txn_field( + txn_index, + sfield::Memos, + base, + pos + ) +} + +// ============================================================================ +// Main Entry Point +// ============================================================================ + +/// Main hook function that builds and emits a payment transaction with memos +/// +/// This function: +/// 1. Retrieves the current contract call context +/// 2. Builds a payment transaction +/// 3. Adds amount, destination, and memos fields +/// 4. Emits the completed transaction +/// +/// # Returns +/// - 0 on success +/// - Negative error code on failure +#[unsafe(no_mangle)] +pub extern "C" fn emit() -> i32 { + // Get contract context + let contract_call: ContractCall = get_current_contract_call(); + let account = contract_call.get_account().unwrap(); + + // Initialize payment transaction + let txn_index = 0; + let build_result = unsafe { build_txn(TT_PAYMENT) }; + if build_result < 0 { + return CUSTOM_ERROR_CODE; + } + + // Build transaction fields + unsafe { + // Add amount field + if add_amount_field(txn_index) < 0 { + return CUSTOM_ERROR_CODE; + } + + // Add destination field + if add_destination_field(txn_index, &account) < 0 { + return CUSTOM_ERROR_CODE; + } + + // Add memos field + if add_memos_field(txn_index) < 0 { + return CUSTOM_ERROR_CODE; + } + + // Emit the completed transaction + let emission_result = emit_built_txn(txn_index); + if emission_result < 0 { + return emission_result; + } + } + + 0 // Success +} diff --git a/src/test/app/wasm_fixtures/events/Cargo.toml b/src/test/app/wasm_fixtures/events/Cargo.toml new file mode 100644 index 00000000000..ce249ac9b04 --- /dev/null +++ b/src/test/app/wasm_fixtures/events/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "events" +version = "0.0.1" +edition = "2024" + +# 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-wasm-std = { git = "https://github.com/Transia-RnD/craft.git", branch = "dangell/smart-contracts", package = "xrpl-wasm-std" } + +[profile.dev] +panic = "abort" diff --git a/src/test/app/wasm_fixtures/events/src/lib.rs b/src/test/app/wasm_fixtures/events/src/lib.rs new file mode 100644 index 00000000000..7cc8d0537c6 --- /dev/null +++ b/src/test/app/wasm_fixtures/events/src/lib.rs @@ -0,0 +1,99 @@ +#![cfg_attr(target_arch = "wasm32", no_std)] + +#[cfg(not(target_arch = "wasm32"))] +extern crate std; + +use xrpl_wasm_std::core::types::account_id::AccountID; +use xrpl_wasm_std::core::event::codec_v2::{ + EventBuffer, event_add_u8, event_add_u16, event_add_u32, event_add_u64, + event_add_u128, event_add_u160, event_add_u192, event_add_u256, event_add_amount, event_add_account, + event_add_currency, event_add_str +}; + +#[unsafe(no_mangle)] +pub extern "C" fn events() -> i32 { + let mut buf = EventBuffer::new(); + + // STI_AMOUNT + const AMOUNT: [u8; 8] = [ + 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0 + ]; + if event_add_amount(&mut buf, "amount", &AMOUNT).is_err() { + return -1; + } + + // STI_CURRENCY + const CURRENCY: [u8; 20] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x55, 0x53, 0x44, 0x00, + 0x00, 0x00, 0x00, 0x00 + ]; + if event_add_currency(&mut buf, "currency", &CURRENCY).is_err() { + return -1; + } + + // STI_ACCOUNT + const ACCOUNT: [u8; 20] = [ + 0x59, 0x69, 0x15, 0xCF, 0xDE, 0xEE, 0x3A, 0x69, + 0x5B, 0x3E, 0xFD, 0x6B, 0xDA, 0x9A, 0xC7, 0x88, + 0xA3, 0x68, 0xB7, 0xB + ]; + let account = AccountID(ACCOUNT); + if event_add_account(&mut buf, "destination", &account.0).is_err() { + return -1; + } + + // STI_UINT128 + if event_add_u128(&mut buf, "uint128", &[0u8; 16]).is_err() { + return -1; + } + + // STI_UINT16 + if event_add_u16(&mut buf, "uint16", 16).is_err() { + return -1; + } + + // STI_UINT160 + if event_add_u160(&mut buf, "uint160", &[0u8; 20]).is_err() { + return -1; + } + + // STI_UINT192 + if event_add_u192(&mut buf, "uint192", &[0u8; 24]).is_err() { + return -1; + } + + // STI_UINT256 + if event_add_u256(&mut buf, "uint256", &[0u8; 32]).is_err() { + return -1; + } + + // STI_UINT32 + if event_add_u32(&mut buf, "uint32", 32).is_err() { + return -1; + } + + // STI_UINT64 + if event_add_u64(&mut buf, "uint64", 64).is_err() { + return -1; + } + + // STI_UINT8 + if event_add_u8(&mut buf, "uint8", 8).is_err() { + return -1; + } + + // STI_VL + if event_add_str(&mut buf, "vl", "Hello, World!").is_err() { + return -1; + } + + // STI_ISSUE (XRP) + // STI_ISSUE (IOU) + // STI_ISSUE (MPT) + + if buf.emit("event1").is_err() { + return -1; + } + 0 +} diff --git a/src/test/app/wasm_fixtures/fib.c b/src/test/app/wasm_fixtures/fib.c new file mode 100644 index 00000000000..e45cc4fe6cc --- /dev/null +++ b/src/test/app/wasm_fixtures/fib.c @@ -0,0 +1,11 @@ +// typedef long long mint; +typedef int mint; + +mint fib(mint n) +{ + if (!n) + return 0; + if (n <= 2) + return 1; + return fib(n - 1) + fib(n - 2); +} diff --git a/src/test/app/wasm_fixtures/fixture_functions_5k.cpp b/src/test/app/wasm_fixtures/fixture_functions_5k.cpp new file mode 100644 index 00000000000..6ade5fea144 --- /dev/null +++ b/src/test/app/wasm_fixtures/fixture_functions_5k.cpp @@ -0,0 +1,2238 @@ +// TODO: consider moving these to separate files (and figure out the build) + +#include + +extern std::string const functions5kHex = + "0061736d0100000001070160027f7f017f038a27882700000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000000000000007e2d303882708" + "7465737430303030000008746573743030303100010874657374303030320002087465737430303033000308746573" + "7430303034000408746573743030303500050874657374303030360006087465737430303037000708746573743030" + "303800080874657374303030390009087465737430303130000a087465737430303131000b08746573743030313200" + "0c087465737430303133000d087465737430303134000e087465737430303135000f08746573743030313600100874" + "6573743030313700110874657374303031380012087465737430303139001308746573743030323000140874657374" + "3030323100150874657374303032320016087465737430303233001708746573743030323400180874657374303032" + "350019087465737430303236001a087465737430303237001b087465737430303238001c087465737430303239001d" + "087465737430303330001e087465737430303331001f08746573743030333200200874657374303033330021087465" + "7374303033340022087465737430303335002308746573743030333600240874657374303033370025087465737430" + "3033380026087465737430303339002708746573743030343000280874657374303034310029087465737430303432" + "002a087465737430303433002b087465737430303434002c087465737430303435002d087465737430303436002e08" + "7465737430303437002f08746573743030343800300874657374303034390031087465737430303530003208746573" + "7430303531003308746573743030353200340874657374303035330035087465737430303534003608746573743030" + "3535003708746573743030353600380874657374303035370039087465737430303538003a08746573743030353900" + "3b087465737430303630003c087465737430303631003d087465737430303632003e087465737430303633003f0874" + "6573743030363400400874657374303036350041087465737430303636004208746573743030363700430874657374" + "3030363800440874657374303036390045087465737430303730004608746573743030373100470874657374303037" + "3200480874657374303037330049087465737430303734004a087465737430303735004b087465737430303736004c" + "087465737430303737004d087465737430303738004e087465737430303739004f0874657374303038300050087465" + "7374303038310051087465737430303832005208746573743030383300530874657374303038340054087465737430" + "3038350055087465737430303836005608746573743030383700570874657374303038380058087465737430303839" + "0059087465737430303930005a087465737430303931005b087465737430303932005c087465737430303933005d08" + "7465737430303934005e087465737430303935005f0874657374303039360060087465737430303937006108746573" + "7430303938006208746573743030393900630874657374303130300064087465737430313031006508746573743031" + "3032006608746573743031303300670874657374303130340068087465737430313035006908746573743031303600" + "6a087465737430313037006b087465737430313038006c087465737430313039006d087465737430313130006e0874" + "65737430313131006f0874657374303131320070087465737430313133007108746573743031313400720874657374" + "3031313500730874657374303131360074087465737430313137007508746573743031313800760874657374303131" + "39007708746573743031323000780874657374303132310079087465737430313232007a087465737430313233007b" + "087465737430313234007c087465737430313235007d087465737430313236007e087465737430313237007f087465" + "7374303132380080010874657374303132390081010874657374303133300082010874657374303133310083010874" + "6573743031333200840108746573743031333300850108746573743031333400860108746573743031333500870108" + "7465737430313336008801087465737430313337008901087465737430313338008a01087465737430313339008b01" + "087465737430313430008c01087465737430313431008d01087465737430313432008e01087465737430313433008f" + "0108746573743031343400900108746573743031343500910108746573743031343600920108746573743031343700" + "9301087465737430313438009401087465737430313439009501087465737430313530009601087465737430313531" + "009701087465737430313532009801087465737430313533009901087465737430313534009a010874657374303135" + "35009b01087465737430313536009c01087465737430313537009d01087465737430313538009e0108746573743031" + "3539009f0108746573743031363000a00108746573743031363100a10108746573743031363200a201087465737430" + "31363300a30108746573743031363400a40108746573743031363500a50108746573743031363600a6010874657374" + "3031363700a70108746573743031363800a80108746573743031363900a90108746573743031373000aa0108746573" + "743031373100ab0108746573743031373200ac0108746573743031373300ad0108746573743031373400ae01087465" + "73743031373500af0108746573743031373600b00108746573743031373700b10108746573743031373800b2010874" + "6573743031373900b30108746573743031383000b40108746573743031383100b50108746573743031383200b60108" + "746573743031383300b70108746573743031383400b80108746573743031383500b90108746573743031383600ba01" + "08746573743031383700bb0108746573743031383800bc0108746573743031383900bd0108746573743031393000be" + "0108746573743031393100bf0108746573743031393200c00108746573743031393300c10108746573743031393400" + "c20108746573743031393500c30108746573743031393600c40108746573743031393700c501087465737430313938" + "00c60108746573743031393900c70108746573743032303000c80108746573743032303100c9010874657374303230" + "3200ca0108746573743032303300cb0108746573743032303400cc0108746573743032303500cd0108746573743032" + "303600ce0108746573743032303700cf0108746573743032303800d00108746573743032303900d101087465737430" + "32313000d20108746573743032313100d30108746573743032313200d40108746573743032313300d5010874657374" + "3032313400d60108746573743032313500d70108746573743032313600d80108746573743032313700d90108746573" + "743032313800da0108746573743032313900db0108746573743032323000dc0108746573743032323100dd01087465" + "73743032323200de0108746573743032323300df0108746573743032323400e00108746573743032323500e1010874" + "6573743032323600e20108746573743032323700e30108746573743032323800e40108746573743032323900e50108" + "746573743032333000e60108746573743032333100e70108746573743032333200e80108746573743032333300e901" + "08746573743032333400ea0108746573743032333500eb0108746573743032333600ec0108746573743032333700ed" + "0108746573743032333800ee0108746573743032333900ef0108746573743032343000f00108746573743032343100" + "f10108746573743032343200f20108746573743032343300f30108746573743032343400f401087465737430323435" + "00f50108746573743032343600f60108746573743032343700f70108746573743032343800f8010874657374303234" + "3900f90108746573743032353000fa0108746573743032353100fb0108746573743032353200fc0108746573743032" + "353300fd0108746573743032353400fe0108746573743032353500ff01087465737430323536008002087465737430" + "3235370081020874657374303235380082020874657374303235390083020874657374303236300084020874657374" + "3032363100850208746573743032363200860208746573743032363300870208746573743032363400880208746573" + "7430323635008902087465737430323636008a02087465737430323637008b02087465737430323638008c02087465" + "737430323639008d02087465737430323730008e02087465737430323731008f020874657374303237320090020874" + "6573743032373300910208746573743032373400920208746573743032373500930208746573743032373600940208" + "7465737430323737009502087465737430323738009602087465737430323739009702087465737430323830009802" + "087465737430323831009902087465737430323832009a02087465737430323833009b02087465737430323834009c" + "02087465737430323835009d02087465737430323836009e02087465737430323837009f0208746573743032383800" + "a00208746573743032383900a10208746573743032393000a20208746573743032393100a302087465737430323932" + "00a40208746573743032393300a50208746573743032393400a60208746573743032393500a7020874657374303239" + "3600a80208746573743032393700a90208746573743032393800aa0208746573743032393900ab0208746573743033" + "303000ac0208746573743033303100ad0208746573743033303200ae0208746573743033303300af02087465737430" + "33303400b00208746573743033303500b10208746573743033303600b20208746573743033303700b3020874657374" + "3033303800b40208746573743033303900b50208746573743033313000b60208746573743033313100b70208746573" + "743033313200b80208746573743033313300b90208746573743033313400ba0208746573743033313500bb02087465" + "73743033313600bc0208746573743033313700bd0208746573743033313800be0208746573743033313900bf020874" + "6573743033323000c00208746573743033323100c10208746573743033323200c20208746573743033323300c30208" + "746573743033323400c40208746573743033323500c50208746573743033323600c60208746573743033323700c702" + "08746573743033323800c80208746573743033323900c90208746573743033333000ca0208746573743033333100cb" + "0208746573743033333200cc0208746573743033333300cd0208746573743033333400ce0208746573743033333500" + "cf0208746573743033333600d00208746573743033333700d10208746573743033333800d202087465737430333339" + "00d30208746573743033343000d40208746573743033343100d50208746573743033343200d6020874657374303334" + "3300d70208746573743033343400d80208746573743033343500d90208746573743033343600da0208746573743033" + "343700db0208746573743033343800dc0208746573743033343900dd0208746573743033353000de02087465737430" + "33353100df0208746573743033353200e00208746573743033353300e10208746573743033353400e2020874657374" + "3033353500e30208746573743033353600e40208746573743033353700e50208746573743033353800e60208746573" + "743033353900e70208746573743033363000e80208746573743033363100e90208746573743033363200ea02087465" + "73743033363300eb0208746573743033363400ec0208746573743033363500ed0208746573743033363600ee020874" + "6573743033363700ef0208746573743033363800f00208746573743033363900f10208746573743033373000f20208" + "746573743033373100f30208746573743033373200f40208746573743033373300f50208746573743033373400f602" + "08746573743033373500f70208746573743033373600f80208746573743033373700f90208746573743033373800fa" + "0208746573743033373900fb0208746573743033383000fc0208746573743033383100fd0208746573743033383200" + "fe0208746573743033383300ff02087465737430333834008003087465737430333835008103087465737430333836" + "0082030874657374303338370083030874657374303338380084030874657374303338390085030874657374303339" + "3000860308746573743033393100870308746573743033393200880308746573743033393300890308746573743033" + "3934008a03087465737430333935008b03087465737430333936008c03087465737430333937008d03087465737430" + "333938008e03087465737430333939008f030874657374303430300090030874657374303430310091030874657374" + "3034303200920308746573743034303300930308746573743034303400940308746573743034303500950308746573" + "7430343036009603087465737430343037009703087465737430343038009803087465737430343039009903087465" + "737430343130009a03087465737430343131009b03087465737430343132009c03087465737430343133009d030874" + "65737430343134009e03087465737430343135009f0308746573743034313600a00308746573743034313700a10308" + "746573743034313800a20308746573743034313900a30308746573743034323000a40308746573743034323100a503" + "08746573743034323200a60308746573743034323300a70308746573743034323400a80308746573743034323500a9" + "0308746573743034323600aa0308746573743034323700ab0308746573743034323800ac0308746573743034323900" + "ad0308746573743034333000ae0308746573743034333100af0308746573743034333200b003087465737430343333" + "00b10308746573743034333400b20308746573743034333500b30308746573743034333600b4030874657374303433" + "3700b50308746573743034333800b60308746573743034333900b70308746573743034343000b80308746573743034" + "343100b90308746573743034343200ba0308746573743034343300bb0308746573743034343400bc03087465737430" + "34343500bd0308746573743034343600be0308746573743034343700bf0308746573743034343800c0030874657374" + "3034343900c10308746573743034353000c20308746573743034353100c30308746573743034353200c40308746573" + "743034353300c50308746573743034353400c60308746573743034353500c70308746573743034353600c803087465" + "73743034353700c90308746573743034353800ca0308746573743034353900cb0308746573743034363000cc030874" + "6573743034363100cd0308746573743034363200ce0308746573743034363300cf0308746573743034363400d00308" + "746573743034363500d10308746573743034363600d20308746573743034363700d30308746573743034363800d403" + "08746573743034363900d50308746573743034373000d60308746573743034373100d70308746573743034373200d8" + "0308746573743034373300d90308746573743034373400da0308746573743034373500db0308746573743034373600" + "dc0308746573743034373700dd0308746573743034373800de0308746573743034373900df03087465737430343830" + "00e00308746573743034383100e10308746573743034383200e20308746573743034383300e3030874657374303438" + "3400e40308746573743034383500e50308746573743034383600e60308746573743034383700e70308746573743034" + "383800e80308746573743034383900e90308746573743034393000ea0308746573743034393100eb03087465737430" + "34393200ec0308746573743034393300ed0308746573743034393400ee0308746573743034393500ef030874657374" + "3034393600f00308746573743034393700f10308746573743034393800f20308746573743034393900f30308746573" + "743035303000f40308746573743035303100f50308746573743035303200f60308746573743035303300f703087465" + "73743035303400f80308746573743035303500f90308746573743035303600fa0308746573743035303700fb030874" + "6573743035303800fc0308746573743035303900fd0308746573743035313000fe0308746573743035313100ff0308" + "7465737430353132008004087465737430353133008104087465737430353134008204087465737430353135008304" + "0874657374303531360084040874657374303531370085040874657374303531380086040874657374303531390087" + "04087465737430353230008804087465737430353231008904087465737430353232008a0408746573743035323300" + "8b04087465737430353234008c04087465737430353235008d04087465737430353236008e04087465737430353237" + "008f040874657374303532380090040874657374303532390091040874657374303533300092040874657374303533" + "3100930408746573743035333200940408746573743035333300950408746573743035333400960408746573743035" + "3335009704087465737430353336009804087465737430353337009904087465737430353338009a04087465737430" + "353339009b04087465737430353430009c04087465737430353431009d04087465737430353432009e040874657374" + "30353433009f0408746573743035343400a00408746573743035343500a10408746573743035343600a20408746573" + "743035343700a30408746573743035343800a40408746573743035343900a50408746573743035353000a604087465" + "73743035353100a70408746573743035353200a80408746573743035353300a90408746573743035353400aa040874" + "6573743035353500ab0408746573743035353600ac0408746573743035353700ad0408746573743035353800ae0408" + "746573743035353900af0408746573743035363000b00408746573743035363100b10408746573743035363200b204" + "08746573743035363300b30408746573743035363400b40408746573743035363500b50408746573743035363600b6" + "0408746573743035363700b70408746573743035363800b80408746573743035363900b90408746573743035373000" + "ba0408746573743035373100bb0408746573743035373200bc0408746573743035373300bd04087465737430353734" + "00be0408746573743035373500bf0408746573743035373600c00408746573743035373700c1040874657374303537" + "3800c20408746573743035373900c30408746573743035383000c40408746573743035383100c50408746573743035" + "383200c60408746573743035383300c70408746573743035383400c80408746573743035383500c904087465737430" + "35383600ca0408746573743035383700cb0408746573743035383800cc0408746573743035383900cd040874657374" + "3035393000ce0408746573743035393100cf0408746573743035393200d00408746573743035393300d10408746573" + "743035393400d20408746573743035393500d30408746573743035393600d40408746573743035393700d504087465" + "73743035393800d60408746573743035393900d70408746573743036303000d80408746573743036303100d9040874" + "6573743036303200da0408746573743036303300db0408746573743036303400dc0408746573743036303500dd0408" + "746573743036303600de0408746573743036303700df0408746573743036303800e00408746573743036303900e104" + "08746573743036313000e20408746573743036313100e30408746573743036313200e40408746573743036313300e5" + "0408746573743036313400e60408746573743036313500e70408746573743036313600e80408746573743036313700" + "e90408746573743036313800ea0408746573743036313900eb0408746573743036323000ec04087465737430363231" + "00ed0408746573743036323200ee0408746573743036323300ef0408746573743036323400f0040874657374303632" + "3500f10408746573743036323600f20408746573743036323700f30408746573743036323800f40408746573743036" + "323900f50408746573743036333000f60408746573743036333100f70408746573743036333200f804087465737430" + "36333300f90408746573743036333400fa0408746573743036333500fb0408746573743036333600fc040874657374" + "3036333700fd0408746573743036333800fe0408746573743036333900ff0408746573743036343000800508746573" + "7430363431008105087465737430363432008205087465737430363433008305087465737430363434008405087465" + "7374303634350085050874657374303634360086050874657374303634370087050874657374303634380088050874" + "65737430363439008905087465737430363530008a05087465737430363531008b05087465737430363532008c0508" + "7465737430363533008d05087465737430363534008e05087465737430363535008f05087465737430363536009005" + "0874657374303635370091050874657374303635380092050874657374303635390093050874657374303636300094" + "0508746573743036363100950508746573743036363200960508746573743036363300970508746573743036363400" + "9805087465737430363635009905087465737430363636009a05087465737430363637009b05087465737430363638" + "009c05087465737430363639009d05087465737430363730009e05087465737430363731009f050874657374303637" + "3200a00508746573743036373300a10508746573743036373400a20508746573743036373500a30508746573743036" + "373600a40508746573743036373700a50508746573743036373800a60508746573743036373900a705087465737430" + "36383000a80508746573743036383100a90508746573743036383200aa0508746573743036383300ab050874657374" + "3036383400ac0508746573743036383500ad0508746573743036383600ae0508746573743036383700af0508746573" + "743036383800b00508746573743036383900b10508746573743036393000b20508746573743036393100b305087465" + "73743036393200b40508746573743036393300b50508746573743036393400b60508746573743036393500b7050874" + "6573743036393600b80508746573743036393700b90508746573743036393800ba0508746573743036393900bb0508" + "746573743037303000bc0508746573743037303100bd0508746573743037303200be0508746573743037303300bf05" + "08746573743037303400c00508746573743037303500c10508746573743037303600c20508746573743037303700c3" + "0508746573743037303800c40508746573743037303900c50508746573743037313000c60508746573743037313100" + "c70508746573743037313200c80508746573743037313300c90508746573743037313400ca05087465737430373135" + "00cb0508746573743037313600cc0508746573743037313700cd0508746573743037313800ce050874657374303731" + "3900cf0508746573743037323000d00508746573743037323100d10508746573743037323200d20508746573743037" + "323300d30508746573743037323400d40508746573743037323500d50508746573743037323600d605087465737430" + "37323700d70508746573743037323800d80508746573743037323900d90508746573743037333000da050874657374" + "3037333100db0508746573743037333200dc0508746573743037333300dd0508746573743037333400de0508746573" + "743037333500df0508746573743037333600e00508746573743037333700e10508746573743037333800e205087465" + "73743037333900e30508746573743037343000e40508746573743037343100e50508746573743037343200e6050874" + "6573743037343300e70508746573743037343400e80508746573743037343500e90508746573743037343600ea0508" + "746573743037343700eb0508746573743037343800ec0508746573743037343900ed0508746573743037353000ee05" + "08746573743037353100ef0508746573743037353200f00508746573743037353300f10508746573743037353400f2" + "0508746573743037353500f30508746573743037353600f40508746573743037353700f50508746573743037353800" + "f60508746573743037353900f70508746573743037363000f80508746573743037363100f905087465737430373632" + "00fa0508746573743037363300fb0508746573743037363400fc0508746573743037363500fd050874657374303736" + "3600fe0508746573743037363700ff0508746573743037363800800608746573743037363900810608746573743037" + "3730008206087465737430373731008306087465737430373732008406087465737430373733008506087465737430" + "3737340086060874657374303737350087060874657374303737360088060874657374303737370089060874657374" + "30373738008a06087465737430373739008b06087465737430373830008c06087465737430373831008d0608746573" + "7430373832008e06087465737430373833008f06087465737430373834009006087465737430373835009106087465" + "7374303738360092060874657374303738370093060874657374303738380094060874657374303738390095060874" + "6573743037393000960608746573743037393100970608746573743037393200980608746573743037393300990608" + "7465737430373934009a06087465737430373935009b06087465737430373936009c06087465737430373937009d06" + "087465737430373938009e06087465737430373939009f0608746573743038303000a00608746573743038303100a1" + "0608746573743038303200a20608746573743038303300a30608746573743038303400a40608746573743038303500" + "a50608746573743038303600a60608746573743038303700a70608746573743038303800a806087465737430383039" + "00a90608746573743038313000aa0608746573743038313100ab0608746573743038313200ac060874657374303831" + "3300ad0608746573743038313400ae0608746573743038313500af0608746573743038313600b00608746573743038" + "313700b10608746573743038313800b20608746573743038313900b30608746573743038323000b406087465737430" + "38323100b50608746573743038323200b60608746573743038323300b70608746573743038323400b8060874657374" + "3038323500b90608746573743038323600ba0608746573743038323700bb0608746573743038323800bc0608746573" + "743038323900bd0608746573743038333000be0608746573743038333100bf0608746573743038333200c006087465" + "73743038333300c10608746573743038333400c20608746573743038333500c30608746573743038333600c4060874" + "6573743038333700c50608746573743038333800c60608746573743038333900c70608746573743038343000c80608" + "746573743038343100c90608746573743038343200ca0608746573743038343300cb0608746573743038343400cc06" + "08746573743038343500cd0608746573743038343600ce0608746573743038343700cf0608746573743038343800d0" + "0608746573743038343900d10608746573743038353000d20608746573743038353100d30608746573743038353200" + "d40608746573743038353300d50608746573743038353400d60608746573743038353500d706087465737430383536" + "00d80608746573743038353700d90608746573743038353800da0608746573743038353900db060874657374303836" + "3000dc0608746573743038363100dd0608746573743038363200de0608746573743038363300df0608746573743038" + "363400e00608746573743038363500e10608746573743038363600e20608746573743038363700e306087465737430" + "38363800e40608746573743038363900e50608746573743038373000e60608746573743038373100e7060874657374" + "3038373200e80608746573743038373300e90608746573743038373400ea0608746573743038373500eb0608746573" + "743038373600ec0608746573743038373700ed0608746573743038373800ee0608746573743038373900ef06087465" + "73743038383000f00608746573743038383100f10608746573743038383200f20608746573743038383300f3060874" + "6573743038383400f40608746573743038383500f50608746573743038383600f60608746573743038383700f70608" + "746573743038383800f80608746573743038383900f90608746573743038393000fa0608746573743038393100fb06" + "08746573743038393200fc0608746573743038393300fd0608746573743038393400fe0608746573743038393500ff" + "0608746573743038393600800708746573743038393700810708746573743038393800820708746573743038393900" + "8307087465737430393030008407087465737430393031008507087465737430393032008607087465737430393033" + "008707087465737430393034008807087465737430393035008907087465737430393036008a070874657374303930" + "37008b07087465737430393038008c07087465737430393039008d07087465737430393130008e0708746573743039" + "3131008f07087465737430393132009007087465737430393133009107087465737430393134009207087465737430" + "3931350093070874657374303931360094070874657374303931370095070874657374303931380096070874657374" + "30393139009707087465737430393230009807087465737430393231009907087465737430393232009a0708746573" + "7430393233009b07087465737430393234009c07087465737430393235009d07087465737430393236009e07087465" + "737430393237009f0708746573743039323800a00708746573743039323900a10708746573743039333000a2070874" + "6573743039333100a30708746573743039333200a40708746573743039333300a50708746573743039333400a60708" + "746573743039333500a70708746573743039333600a80708746573743039333700a90708746573743039333800aa07" + "08746573743039333900ab0708746573743039343000ac0708746573743039343100ad0708746573743039343200ae" + "0708746573743039343300af0708746573743039343400b00708746573743039343500b10708746573743039343600" + "b20708746573743039343700b30708746573743039343800b40708746573743039343900b507087465737430393530" + "00b60708746573743039353100b70708746573743039353200b80708746573743039353300b9070874657374303935" + "3400ba0708746573743039353500bb0708746573743039353600bc0708746573743039353700bd0708746573743039" + "353800be0708746573743039353900bf0708746573743039363000c00708746573743039363100c107087465737430" + "39363200c20708746573743039363300c30708746573743039363400c40708746573743039363500c5070874657374" + "3039363600c60708746573743039363700c70708746573743039363800c80708746573743039363900c90708746573" + "743039373000ca0708746573743039373100cb0708746573743039373200cc0708746573743039373300cd07087465" + "73743039373400ce0708746573743039373500cf0708746573743039373600d00708746573743039373700d1070874" + "6573743039373800d20708746573743039373900d30708746573743039383000d40708746573743039383100d50708" + "746573743039383200d60708746573743039383300d70708746573743039383400d80708746573743039383500d907" + "08746573743039383600da0708746573743039383700db0708746573743039383800dc0708746573743039383900dd" + "0708746573743039393000de0708746573743039393100df0708746573743039393200e00708746573743039393300" + "e10708746573743039393400e20708746573743039393500e30708746573743039393600e407087465737430393937" + "00e50708746573743039393800e60708746573743039393900e70708746573743130303000e8070874657374313030" + "3100e90708746573743130303200ea0708746573743130303300eb0708746573743130303400ec0708746573743130" + "303500ed0708746573743130303600ee0708746573743130303700ef0708746573743130303800f007087465737431" + "30303900f10708746573743130313000f20708746573743130313100f30708746573743130313200f4070874657374" + "3130313300f50708746573743130313400f60708746573743130313500f70708746573743130313600f80708746573" + "743130313700f90708746573743130313800fa0708746573743130313900fb0708746573743130323000fc07087465" + "73743130323100fd0708746573743130323200fe0708746573743130323300ff070874657374313032340080080874" + "6573743130323500810808746573743130323600820808746573743130323700830808746573743130323800840808" + "7465737431303239008508087465737431303330008608087465737431303331008708087465737431303332008808" + "087465737431303333008908087465737431303334008a08087465737431303335008b08087465737431303336008c" + "08087465737431303337008d08087465737431303338008e08087465737431303339008f0808746573743130343000" + "9008087465737431303431009108087465737431303432009208087465737431303433009308087465737431303434" + "0094080874657374313034350095080874657374313034360096080874657374313034370097080874657374313034" + "38009808087465737431303439009908087465737431303530009a08087465737431303531009b0808746573743130" + "3532009c08087465737431303533009d08087465737431303534009e08087465737431303535009f08087465737431" + "30353600a00808746573743130353700a10808746573743130353800a20808746573743130353900a3080874657374" + "3130363000a40808746573743130363100a50808746573743130363200a60808746573743130363300a70808746573" + "743130363400a80808746573743130363500a90808746573743130363600aa0808746573743130363700ab08087465" + "73743130363800ac0808746573743130363900ad0808746573743130373000ae0808746573743130373100af080874" + "6573743130373200b00808746573743130373300b10808746573743130373400b20808746573743130373500b30808" + "746573743130373600b40808746573743130373700b50808746573743130373800b60808746573743130373900b708" + "08746573743130383000b80808746573743130383100b90808746573743130383200ba0808746573743130383300bb" + "0808746573743130383400bc0808746573743130383500bd0808746573743130383600be0808746573743130383700" + "bf0808746573743130383800c00808746573743130383900c10808746573743130393000c208087465737431303931" + "00c30808746573743130393200c40808746573743130393300c50808746573743130393400c6080874657374313039" + "3500c70808746573743130393600c80808746573743130393700c90808746573743130393800ca0808746573743130" + "393900cb0808746573743131303000cc0808746573743131303100cd0808746573743131303200ce08087465737431" + "31303300cf0808746573743131303400d00808746573743131303500d10808746573743131303600d2080874657374" + "3131303700d30808746573743131303800d40808746573743131303900d50808746573743131313000d60808746573" + "743131313100d70808746573743131313200d80808746573743131313300d90808746573743131313400da08087465" + "73743131313500db0808746573743131313600dc0808746573743131313700dd0808746573743131313800de080874" + "6573743131313900df0808746573743131323000e00808746573743131323100e10808746573743131323200e20808" + "746573743131323300e30808746573743131323400e40808746573743131323500e50808746573743131323600e608" + "08746573743131323700e70808746573743131323800e80808746573743131323900e90808746573743131333000ea" + "0808746573743131333100eb0808746573743131333200ec0808746573743131333300ed0808746573743131333400" + "ee0808746573743131333500ef0808746573743131333600f00808746573743131333700f108087465737431313338" + "00f20808746573743131333900f30808746573743131343000f40808746573743131343100f5080874657374313134" + "3200f60808746573743131343300f70808746573743131343400f80808746573743131343500f90808746573743131" + "343600fa0808746573743131343700fb0808746573743131343800fc0808746573743131343900fd08087465737431" + "31353000fe0808746573743131353100ff080874657374313135320080090874657374313135330081090874657374" + "3131353400820908746573743131353500830908746573743131353600840908746573743131353700850908746573" + "7431313538008609087465737431313539008709087465737431313630008809087465737431313631008909087465" + "737431313632008a09087465737431313633008b09087465737431313634008c09087465737431313635008d090874" + "65737431313636008e09087465737431313637008f0908746573743131363800900908746573743131363900910908" + "7465737431313730009209087465737431313731009309087465737431313732009409087465737431313733009509" + "0874657374313137340096090874657374313137350097090874657374313137360098090874657374313137370099" + "09087465737431313738009a09087465737431313739009b09087465737431313830009c0908746573743131383100" + "9d09087465737431313832009e09087465737431313833009f0908746573743131383400a009087465737431313835" + "00a10908746573743131383600a20908746573743131383700a30908746573743131383800a4090874657374313138" + "3900a50908746573743131393000a60908746573743131393100a70908746573743131393200a80908746573743131" + "393300a90908746573743131393400aa0908746573743131393500ab0908746573743131393600ac09087465737431" + "31393700ad0908746573743131393800ae0908746573743131393900af0908746573743132303000b0090874657374" + "3132303100b10908746573743132303200b20908746573743132303300b30908746573743132303400b40908746573" + "743132303500b50908746573743132303600b60908746573743132303700b70908746573743132303800b809087465" + "73743132303900b90908746573743132313000ba0908746573743132313100bb0908746573743132313200bc090874" + "6573743132313300bd0908746573743132313400be0908746573743132313500bf0908746573743132313600c00908" + "746573743132313700c10908746573743132313800c20908746573743132313900c30908746573743132323000c409" + "08746573743132323100c50908746573743132323200c60908746573743132323300c70908746573743132323400c8" + "0908746573743132323500c90908746573743132323600ca0908746573743132323700cb0908746573743132323800" + "cc0908746573743132323900cd0908746573743132333000ce0908746573743132333100cf09087465737431323332" + "00d00908746573743132333300d10908746573743132333400d20908746573743132333500d3090874657374313233" + "3600d40908746573743132333700d50908746573743132333800d60908746573743132333900d70908746573743132" + "343000d80908746573743132343100d90908746573743132343200da0908746573743132343300db09087465737431" + "32343400dc0908746573743132343500dd0908746573743132343600de0908746573743132343700df090874657374" + "3132343800e00908746573743132343900e10908746573743132353000e20908746573743132353100e30908746573" + "743132353200e40908746573743132353300e50908746573743132353400e60908746573743132353500e709087465" + "73743132353600e80908746573743132353700e90908746573743132353800ea0908746573743132353900eb090874" + "6573743132363000ec0908746573743132363100ed0908746573743132363200ee0908746573743132363300ef0908" + "746573743132363400f00908746573743132363500f10908746573743132363600f20908746573743132363700f309" + "08746573743132363800f40908746573743132363900f50908746573743132373000f60908746573743132373100f7" + "0908746573743132373200f80908746573743132373300f90908746573743132373400fa0908746573743132373500" + "fb0908746573743132373600fc0908746573743132373700fd0908746573743132373800fe09087465737431323739" + "00ff0908746573743132383000800a08746573743132383100810a08746573743132383200820a0874657374313238" + "3300830a08746573743132383400840a08746573743132383500850a08746573743132383600860a08746573743132" + "383700870a08746573743132383800880a08746573743132383900890a087465737431323930008a0a087465737431" + "323931008b0a087465737431323932008c0a087465737431323933008d0a087465737431323934008e0a0874657374" + "31323935008f0a08746573743132393600900a08746573743132393700910a08746573743132393800920a08746573" + "743132393900930a08746573743133303000940a08746573743133303100950a08746573743133303200960a087465" + "73743133303300970a08746573743133303400980a08746573743133303500990a087465737431333036009a0a0874" + "65737431333037009b0a087465737431333038009c0a087465737431333039009d0a087465737431333130009e0a08" + "7465737431333131009f0a08746573743133313200a00a08746573743133313300a10a08746573743133313400a20a" + "08746573743133313500a30a08746573743133313600a40a08746573743133313700a50a08746573743133313800a6" + "0a08746573743133313900a70a08746573743133323000a80a08746573743133323100a90a08746573743133323200" + "aa0a08746573743133323300ab0a08746573743133323400ac0a08746573743133323500ad0a087465737431333236" + "00ae0a08746573743133323700af0a08746573743133323800b00a08746573743133323900b10a0874657374313333" + "3000b20a08746573743133333100b30a08746573743133333200b40a08746573743133333300b50a08746573743133" + "333400b60a08746573743133333500b70a08746573743133333600b80a08746573743133333700b90a087465737431" + "33333800ba0a08746573743133333900bb0a08746573743133343000bc0a08746573743133343100bd0a0874657374" + "3133343200be0a08746573743133343300bf0a08746573743133343400c00a08746573743133343500c10a08746573" + "743133343600c20a08746573743133343700c30a08746573743133343800c40a08746573743133343900c50a087465" + "73743133353000c60a08746573743133353100c70a08746573743133353200c80a08746573743133353300c90a0874" + "6573743133353400ca0a08746573743133353500cb0a08746573743133353600cc0a08746573743133353700cd0a08" + "746573743133353800ce0a08746573743133353900cf0a08746573743133363000d00a08746573743133363100d10a" + "08746573743133363200d20a08746573743133363300d30a08746573743133363400d40a08746573743133363500d5" + "0a08746573743133363600d60a08746573743133363700d70a08746573743133363800d80a08746573743133363900" + "d90a08746573743133373000da0a08746573743133373100db0a08746573743133373200dc0a087465737431333733" + "00dd0a08746573743133373400de0a08746573743133373500df0a08746573743133373600e00a0874657374313337" + "3700e10a08746573743133373800e20a08746573743133373900e30a08746573743133383000e40a08746573743133" + "383100e50a08746573743133383200e60a08746573743133383300e70a08746573743133383400e80a087465737431" + "33383500e90a08746573743133383600ea0a08746573743133383700eb0a08746573743133383800ec0a0874657374" + "3133383900ed0a08746573743133393000ee0a08746573743133393100ef0a08746573743133393200f00a08746573" + "743133393300f10a08746573743133393400f20a08746573743133393500f30a08746573743133393600f40a087465" + "73743133393700f50a08746573743133393800f60a08746573743133393900f70a08746573743134303000f80a0874" + "6573743134303100f90a08746573743134303200fa0a08746573743134303300fb0a08746573743134303400fc0a08" + "746573743134303500fd0a08746573743134303600fe0a08746573743134303700ff0a08746573743134303800800b" + "08746573743134303900810b08746573743134313000820b08746573743134313100830b0874657374313431320084" + "0b08746573743134313300850b08746573743134313400860b08746573743134313500870b08746573743134313600" + "880b08746573743134313700890b087465737431343138008a0b087465737431343139008b0b087465737431343230" + "008c0b087465737431343231008d0b087465737431343232008e0b087465737431343233008f0b0874657374313432" + "3400900b08746573743134323500910b08746573743134323600920b08746573743134323700930b08746573743134" + "323800940b08746573743134323900950b08746573743134333000960b08746573743134333100970b087465737431" + "34333200980b08746573743134333300990b087465737431343334009a0b087465737431343335009b0b0874657374" + "31343336009c0b087465737431343337009d0b087465737431343338009e0b087465737431343339009f0b08746573" + "743134343000a00b08746573743134343100a10b08746573743134343200a20b08746573743134343300a30b087465" + "73743134343400a40b08746573743134343500a50b08746573743134343600a60b08746573743134343700a70b0874" + "6573743134343800a80b08746573743134343900a90b08746573743134353000aa0b08746573743134353100ab0b08" + "746573743134353200ac0b08746573743134353300ad0b08746573743134353400ae0b08746573743134353500af0b" + "08746573743134353600b00b08746573743134353700b10b08746573743134353800b20b08746573743134353900b3" + "0b08746573743134363000b40b08746573743134363100b50b08746573743134363200b60b08746573743134363300" + "b70b08746573743134363400b80b08746573743134363500b90b08746573743134363600ba0b087465737431343637" + "00bb0b08746573743134363800bc0b08746573743134363900bd0b08746573743134373000be0b0874657374313437" + "3100bf0b08746573743134373200c00b08746573743134373300c10b08746573743134373400c20b08746573743134" + "373500c30b08746573743134373600c40b08746573743134373700c50b08746573743134373800c60b087465737431" + "34373900c70b08746573743134383000c80b08746573743134383100c90b08746573743134383200ca0b0874657374" + "3134383300cb0b08746573743134383400cc0b08746573743134383500cd0b08746573743134383600ce0b08746573" + "743134383700cf0b08746573743134383800d00b08746573743134383900d10b08746573743134393000d20b087465" + "73743134393100d30b08746573743134393200d40b08746573743134393300d50b08746573743134393400d60b0874" + "6573743134393500d70b08746573743134393600d80b08746573743134393700d90b08746573743134393800da0b08" + "746573743134393900db0b08746573743135303000dc0b08746573743135303100dd0b08746573743135303200de0b" + "08746573743135303300df0b08746573743135303400e00b08746573743135303500e10b08746573743135303600e2" + "0b08746573743135303700e30b08746573743135303800e40b08746573743135303900e50b08746573743135313000" + "e60b08746573743135313100e70b08746573743135313200e80b08746573743135313300e90b087465737431353134" + "00ea0b08746573743135313500eb0b08746573743135313600ec0b08746573743135313700ed0b0874657374313531" + "3800ee0b08746573743135313900ef0b08746573743135323000f00b08746573743135323100f10b08746573743135" + "323200f20b08746573743135323300f30b08746573743135323400f40b08746573743135323500f50b087465737431" + "35323600f60b08746573743135323700f70b08746573743135323800f80b08746573743135323900f90b0874657374" + "3135333000fa0b08746573743135333100fb0b08746573743135333200fc0b08746573743135333300fd0b08746573" + "743135333400fe0b08746573743135333500ff0b08746573743135333600800c08746573743135333700810c087465" + "73743135333800820c08746573743135333900830c08746573743135343000840c08746573743135343100850c0874" + "6573743135343200860c08746573743135343300870c08746573743135343400880c08746573743135343500890c08" + "7465737431353436008a0c087465737431353437008b0c087465737431353438008c0c087465737431353439008d0c" + "087465737431353530008e0c087465737431353531008f0c08746573743135353200900c0874657374313535330091" + "0c08746573743135353400920c08746573743135353500930c08746573743135353600940c08746573743135353700" + "950c08746573743135353800960c08746573743135353900970c08746573743135363000980c087465737431353631" + "00990c087465737431353632009a0c087465737431353633009b0c087465737431353634009c0c0874657374313536" + "35009d0c087465737431353636009e0c087465737431353637009f0c08746573743135363800a00c08746573743135" + "363900a10c08746573743135373000a20c08746573743135373100a30c08746573743135373200a40c087465737431" + "35373300a50c08746573743135373400a60c08746573743135373500a70c08746573743135373600a80c0874657374" + "3135373700a90c08746573743135373800aa0c08746573743135373900ab0c08746573743135383000ac0c08746573" + "743135383100ad0c08746573743135383200ae0c08746573743135383300af0c08746573743135383400b00c087465" + "73743135383500b10c08746573743135383600b20c08746573743135383700b30c08746573743135383800b40c0874" + "6573743135383900b50c08746573743135393000b60c08746573743135393100b70c08746573743135393200b80c08" + "746573743135393300b90c08746573743135393400ba0c08746573743135393500bb0c08746573743135393600bc0c" + "08746573743135393700bd0c08746573743135393800be0c08746573743135393900bf0c08746573743136303000c0" + "0c08746573743136303100c10c08746573743136303200c20c08746573743136303300c30c08746573743136303400" + "c40c08746573743136303500c50c08746573743136303600c60c08746573743136303700c70c087465737431363038" + "00c80c08746573743136303900c90c08746573743136313000ca0c08746573743136313100cb0c0874657374313631" + "3200cc0c08746573743136313300cd0c08746573743136313400ce0c08746573743136313500cf0c08746573743136" + "313600d00c08746573743136313700d10c08746573743136313800d20c08746573743136313900d30c087465737431" + "36323000d40c08746573743136323100d50c08746573743136323200d60c08746573743136323300d70c0874657374" + "3136323400d80c08746573743136323500d90c08746573743136323600da0c08746573743136323700db0c08746573" + "743136323800dc0c08746573743136323900dd0c08746573743136333000de0c08746573743136333100df0c087465" + "73743136333200e00c08746573743136333300e10c08746573743136333400e20c08746573743136333500e30c0874" + "6573743136333600e40c08746573743136333700e50c08746573743136333800e60c08746573743136333900e70c08" + "746573743136343000e80c08746573743136343100e90c08746573743136343200ea0c08746573743136343300eb0c" + "08746573743136343400ec0c08746573743136343500ed0c08746573743136343600ee0c08746573743136343700ef" + "0c08746573743136343800f00c08746573743136343900f10c08746573743136353000f20c08746573743136353100" + "f30c08746573743136353200f40c08746573743136353300f50c08746573743136353400f60c087465737431363535" + "00f70c08746573743136353600f80c08746573743136353700f90c08746573743136353800fa0c0874657374313635" + "3900fb0c08746573743136363000fc0c08746573743136363100fd0c08746573743136363200fe0c08746573743136" + "363300ff0c08746573743136363400800d08746573743136363500810d08746573743136363600820d087465737431" + "36363700830d08746573743136363800840d08746573743136363900850d08746573743136373000860d0874657374" + "3136373100870d08746573743136373200880d08746573743136373300890d087465737431363734008a0d08746573" + "7431363735008b0d087465737431363736008c0d087465737431363737008d0d087465737431363738008e0d087465" + "737431363739008f0d08746573743136383000900d08746573743136383100910d08746573743136383200920d0874" + "6573743136383300930d08746573743136383400940d08746573743136383500950d08746573743136383600960d08" + "746573743136383700970d08746573743136383800980d08746573743136383900990d087465737431363930009a0d" + "087465737431363931009b0d087465737431363932009c0d087465737431363933009d0d087465737431363934009e" + "0d087465737431363935009f0d08746573743136393600a00d08746573743136393700a10d08746573743136393800" + "a20d08746573743136393900a30d08746573743137303000a40d08746573743137303100a50d087465737431373032" + "00a60d08746573743137303300a70d08746573743137303400a80d08746573743137303500a90d0874657374313730" + "3600aa0d08746573743137303700ab0d08746573743137303800ac0d08746573743137303900ad0d08746573743137" + "313000ae0d08746573743137313100af0d08746573743137313200b00d08746573743137313300b10d087465737431" + "37313400b20d08746573743137313500b30d08746573743137313600b40d08746573743137313700b50d0874657374" + "3137313800b60d08746573743137313900b70d08746573743137323000b80d08746573743137323100b90d08746573" + "743137323200ba0d08746573743137323300bb0d08746573743137323400bc0d08746573743137323500bd0d087465" + "73743137323600be0d08746573743137323700bf0d08746573743137323800c00d08746573743137323900c10d0874" + "6573743137333000c20d08746573743137333100c30d08746573743137333200c40d08746573743137333300c50d08" + "746573743137333400c60d08746573743137333500c70d08746573743137333600c80d08746573743137333700c90d" + "08746573743137333800ca0d08746573743137333900cb0d08746573743137343000cc0d08746573743137343100cd" + "0d08746573743137343200ce0d08746573743137343300cf0d08746573743137343400d00d08746573743137343500" + "d10d08746573743137343600d20d08746573743137343700d30d08746573743137343800d40d087465737431373439" + "00d50d08746573743137353000d60d08746573743137353100d70d08746573743137353200d80d0874657374313735" + "3300d90d08746573743137353400da0d08746573743137353500db0d08746573743137353600dc0d08746573743137" + "353700dd0d08746573743137353800de0d08746573743137353900df0d08746573743137363000e00d087465737431" + "37363100e10d08746573743137363200e20d08746573743137363300e30d08746573743137363400e40d0874657374" + "3137363500e50d08746573743137363600e60d08746573743137363700e70d08746573743137363800e80d08746573" + "743137363900e90d08746573743137373000ea0d08746573743137373100eb0d08746573743137373200ec0d087465" + "73743137373300ed0d08746573743137373400ee0d08746573743137373500ef0d08746573743137373600f00d0874" + "6573743137373700f10d08746573743137373800f20d08746573743137373900f30d08746573743137383000f40d08" + "746573743137383100f50d08746573743137383200f60d08746573743137383300f70d08746573743137383400f80d" + "08746573743137383500f90d08746573743137383600fa0d08746573743137383700fb0d08746573743137383800fc" + "0d08746573743137383900fd0d08746573743137393000fe0d08746573743137393100ff0d08746573743137393200" + "800e08746573743137393300810e08746573743137393400820e08746573743137393500830e087465737431373936" + "00840e08746573743137393700850e08746573743137393800860e08746573743137393900870e0874657374313830" + "3000880e08746573743138303100890e087465737431383032008a0e087465737431383033008b0e08746573743138" + "3034008c0e087465737431383035008d0e087465737431383036008e0e087465737431383037008f0e087465737431" + "38303800900e08746573743138303900910e08746573743138313000920e08746573743138313100930e0874657374" + "3138313200940e08746573743138313300950e08746573743138313400960e08746573743138313500970e08746573" + "743138313600980e08746573743138313700990e087465737431383138009a0e087465737431383139009b0e087465" + "737431383230009c0e087465737431383231009d0e087465737431383232009e0e087465737431383233009f0e0874" + "6573743138323400a00e08746573743138323500a10e08746573743138323600a20e08746573743138323700a30e08" + "746573743138323800a40e08746573743138323900a50e08746573743138333000a60e08746573743138333100a70e" + "08746573743138333200a80e08746573743138333300a90e08746573743138333400aa0e08746573743138333500ab" + "0e08746573743138333600ac0e08746573743138333700ad0e08746573743138333800ae0e08746573743138333900" + "af0e08746573743138343000b00e08746573743138343100b10e08746573743138343200b20e087465737431383433" + "00b30e08746573743138343400b40e08746573743138343500b50e08746573743138343600b60e0874657374313834" + "3700b70e08746573743138343800b80e08746573743138343900b90e08746573743138353000ba0e08746573743138" + "353100bb0e08746573743138353200bc0e08746573743138353300bd0e08746573743138353400be0e087465737431" + "38353500bf0e08746573743138353600c00e08746573743138353700c10e08746573743138353800c20e0874657374" + "3138353900c30e08746573743138363000c40e08746573743138363100c50e08746573743138363200c60e08746573" + "743138363300c70e08746573743138363400c80e08746573743138363500c90e08746573743138363600ca0e087465" + "73743138363700cb0e08746573743138363800cc0e08746573743138363900cd0e08746573743138373000ce0e0874" + "6573743138373100cf0e08746573743138373200d00e08746573743138373300d10e08746573743138373400d20e08" + "746573743138373500d30e08746573743138373600d40e08746573743138373700d50e08746573743138373800d60e" + "08746573743138373900d70e08746573743138383000d80e08746573743138383100d90e08746573743138383200da" + "0e08746573743138383300db0e08746573743138383400dc0e08746573743138383500dd0e08746573743138383600" + "de0e08746573743138383700df0e08746573743138383800e00e08746573743138383900e10e087465737431383930" + "00e20e08746573743138393100e30e08746573743138393200e40e08746573743138393300e50e0874657374313839" + "3400e60e08746573743138393500e70e08746573743138393600e80e08746573743138393700e90e08746573743138" + "393800ea0e08746573743138393900eb0e08746573743139303000ec0e08746573743139303100ed0e087465737431" + "39303200ee0e08746573743139303300ef0e08746573743139303400f00e08746573743139303500f10e0874657374" + "3139303600f20e08746573743139303700f30e08746573743139303800f40e08746573743139303900f50e08746573" + "743139313000f60e08746573743139313100f70e08746573743139313200f80e08746573743139313300f90e087465" + "73743139313400fa0e08746573743139313500fb0e08746573743139313600fc0e08746573743139313700fd0e0874" + "6573743139313800fe0e08746573743139313900ff0e08746573743139323000800f08746573743139323100810f08" + "746573743139323200820f08746573743139323300830f08746573743139323400840f08746573743139323500850f" + "08746573743139323600860f08746573743139323700870f08746573743139323800880f0874657374313932390089" + "0f087465737431393330008a0f087465737431393331008b0f087465737431393332008c0f08746573743139333300" + "8d0f087465737431393334008e0f087465737431393335008f0f08746573743139333600900f087465737431393337" + "00910f08746573743139333800920f08746573743139333900930f08746573743139343000940f0874657374313934" + "3100950f08746573743139343200960f08746573743139343300970f08746573743139343400980f08746573743139" + "343500990f087465737431393436009a0f087465737431393437009b0f087465737431393438009c0f087465737431" + "393439009d0f087465737431393530009e0f087465737431393531009f0f08746573743139353200a00f0874657374" + "3139353300a10f08746573743139353400a20f08746573743139353500a30f08746573743139353600a40f08746573" + "743139353700a50f08746573743139353800a60f08746573743139353900a70f08746573743139363000a80f087465" + "73743139363100a90f08746573743139363200aa0f08746573743139363300ab0f08746573743139363400ac0f0874" + "6573743139363500ad0f08746573743139363600ae0f08746573743139363700af0f08746573743139363800b00f08" + "746573743139363900b10f08746573743139373000b20f08746573743139373100b30f08746573743139373200b40f" + "08746573743139373300b50f08746573743139373400b60f08746573743139373500b70f08746573743139373600b8" + "0f08746573743139373700b90f08746573743139373800ba0f08746573743139373900bb0f08746573743139383000" + "bc0f08746573743139383100bd0f08746573743139383200be0f08746573743139383300bf0f087465737431393834" + "00c00f08746573743139383500c10f08746573743139383600c20f08746573743139383700c30f0874657374313938" + "3800c40f08746573743139383900c50f08746573743139393000c60f08746573743139393100c70f08746573743139" + "393200c80f08746573743139393300c90f08746573743139393400ca0f08746573743139393500cb0f087465737431" + "39393600cc0f08746573743139393700cd0f08746573743139393800ce0f08746573743139393900cf0f0874657374" + "3230303000d00f08746573743230303100d10f08746573743230303200d20f08746573743230303300d30f08746573" + "743230303400d40f08746573743230303500d50f08746573743230303600d60f08746573743230303700d70f087465" + "73743230303800d80f08746573743230303900d90f08746573743230313000da0f08746573743230313100db0f0874" + "6573743230313200dc0f08746573743230313300dd0f08746573743230313400de0f08746573743230313500df0f08" + "746573743230313600e00f08746573743230313700e10f08746573743230313800e20f08746573743230313900e30f" + "08746573743230323000e40f08746573743230323100e50f08746573743230323200e60f08746573743230323300e7" + "0f08746573743230323400e80f08746573743230323500e90f08746573743230323600ea0f08746573743230323700" + "eb0f08746573743230323800ec0f08746573743230323900ed0f08746573743230333000ee0f087465737432303331" + "00ef0f08746573743230333200f00f08746573743230333300f10f08746573743230333400f20f0874657374323033" + "3500f30f08746573743230333600f40f08746573743230333700f50f08746573743230333800f60f08746573743230" + "333900f70f08746573743230343000f80f08746573743230343100f90f08746573743230343200fa0f087465737432" + "30343300fb0f08746573743230343400fc0f08746573743230343500fd0f08746573743230343600fe0f0874657374" + "3230343700ff0f08746573743230343800801008746573743230343900811008746573743230353000821008746573" + "7432303531008310087465737432303532008410087465737432303533008510087465737432303534008610087465" + "737432303535008710087465737432303536008810087465737432303537008910087465737432303538008a100874" + "65737432303539008b10087465737432303630008c10087465737432303631008d10087465737432303632008e1008" + "7465737432303633008f10087465737432303634009010087465737432303635009110087465737432303636009210" + "0874657374323036370093100874657374323036380094100874657374323036390095100874657374323037300096" + "1008746573743230373100971008746573743230373200981008746573743230373300991008746573743230373400" + "9a10087465737432303735009b10087465737432303736009c10087465737432303737009d10087465737432303738" + "009e10087465737432303739009f1008746573743230383000a01008746573743230383100a1100874657374323038" + "3200a21008746573743230383300a31008746573743230383400a41008746573743230383500a51008746573743230" + "383600a61008746573743230383700a71008746573743230383800a81008746573743230383900a910087465737432" + "30393000aa1008746573743230393100ab1008746573743230393200ac1008746573743230393300ad100874657374" + "3230393400ae1008746573743230393500af1008746573743230393600b01008746573743230393700b11008746573" + "743230393800b21008746573743230393900b31008746573743231303000b41008746573743231303100b510087465" + "73743231303200b61008746573743231303300b71008746573743231303400b81008746573743231303500b9100874" + "6573743231303600ba1008746573743231303700bb1008746573743231303800bc1008746573743231303900bd1008" + "746573743231313000be1008746573743231313100bf1008746573743231313200c01008746573743231313300c110" + "08746573743231313400c21008746573743231313500c31008746573743231313600c41008746573743231313700c5" + "1008746573743231313800c61008746573743231313900c71008746573743231323000c81008746573743231323100" + "c91008746573743231323200ca1008746573743231323300cb1008746573743231323400cc10087465737432313235" + "00cd1008746573743231323600ce1008746573743231323700cf1008746573743231323800d0100874657374323132" + "3900d11008746573743231333000d21008746573743231333100d31008746573743231333200d41008746573743231" + "333300d51008746573743231333400d61008746573743231333500d71008746573743231333600d810087465737432" + "31333700d91008746573743231333800da1008746573743231333900db1008746573743231343000dc100874657374" + "3231343100dd1008746573743231343200de1008746573743231343300df1008746573743231343400e01008746573" + "743231343500e11008746573743231343600e21008746573743231343700e31008746573743231343800e410087465" + "73743231343900e51008746573743231353000e61008746573743231353100e71008746573743231353200e8100874" + "6573743231353300e91008746573743231353400ea1008746573743231353500eb1008746573743231353600ec1008" + "746573743231353700ed1008746573743231353800ee1008746573743231353900ef1008746573743231363000f010" + "08746573743231363100f11008746573743231363200f21008746573743231363300f31008746573743231363400f4" + "1008746573743231363500f51008746573743231363600f61008746573743231363700f71008746573743231363800" + "f81008746573743231363900f91008746573743231373000fa1008746573743231373100fb10087465737432313732" + "00fc1008746573743231373300fd1008746573743231373400fe1008746573743231373500ff100874657374323137" + "3600801108746573743231373700811108746573743231373800821108746573743231373900831108746573743231" + "3830008411087465737432313831008511087465737432313832008611087465737432313833008711087465737432" + "313834008811087465737432313835008911087465737432313836008a11087465737432313837008b110874657374" + "32313838008c11087465737432313839008d11087465737432313930008e11087465737432313931008f1108746573" + "7432313932009011087465737432313933009111087465737432313934009211087465737432313935009311087465" + "7374323139360094110874657374323139370095110874657374323139380096110874657374323139390097110874" + "65737432323030009811087465737432323031009911087465737432323032009a11087465737432323033009b1108" + "7465737432323034009c11087465737432323035009d11087465737432323036009e11087465737432323037009f11" + "08746573743232303800a01108746573743232303900a11108746573743232313000a21108746573743232313100a3" + "1108746573743232313200a41108746573743232313300a51108746573743232313400a61108746573743232313500" + "a71108746573743232313600a81108746573743232313700a91108746573743232313800aa11087465737432323139" + "00ab1108746573743232323000ac1108746573743232323100ad1108746573743232323200ae110874657374323232" + "3300af1108746573743232323400b01108746573743232323500b11108746573743232323600b21108746573743232" + "323700b31108746573743232323800b41108746573743232323900b51108746573743232333000b611087465737432" + "32333100b71108746573743232333200b81108746573743232333300b91108746573743232333400ba110874657374" + "3232333500bb1108746573743232333600bc1108746573743232333700bd1108746573743232333800be1108746573" + "743232333900bf1108746573743232343000c01108746573743232343100c11108746573743232343200c211087465" + "73743232343300c31108746573743232343400c41108746573743232343500c51108746573743232343600c6110874" + "6573743232343700c71108746573743232343800c81108746573743232343900c91108746573743232353000ca1108" + "746573743232353100cb1108746573743232353200cc1108746573743232353300cd1108746573743232353400ce11" + "08746573743232353500cf1108746573743232353600d01108746573743232353700d11108746573743232353800d2" + "1108746573743232353900d31108746573743232363000d41108746573743232363100d51108746573743232363200" + "d61108746573743232363300d71108746573743232363400d81108746573743232363500d911087465737432323636" + "00da1108746573743232363700db1108746573743232363800dc1108746573743232363900dd110874657374323237" + "3000de1108746573743232373100df1108746573743232373200e01108746573743232373300e11108746573743232" + "373400e21108746573743232373500e31108746573743232373600e41108746573743232373700e511087465737432" + "32373800e61108746573743232373900e71108746573743232383000e81108746573743232383100e9110874657374" + "3232383200ea1108746573743232383300eb1108746573743232383400ec1108746573743232383500ed1108746573" + "743232383600ee1108746573743232383700ef1108746573743232383800f01108746573743232383900f111087465" + "73743232393000f21108746573743232393100f31108746573743232393200f41108746573743232393300f5110874" + "6573743232393400f61108746573743232393500f71108746573743232393600f81108746573743232393700f91108" + "746573743232393800fa1108746573743232393900fb1108746573743233303000fc1108746573743233303100fd11" + "08746573743233303200fe1108746573743233303300ff110874657374323330340080120874657374323330350081" + "1208746573743233303600821208746573743233303700831208746573743233303800841208746573743233303900" + "8512087465737432333130008612087465737432333131008712087465737432333132008812087465737432333133" + "008912087465737432333134008a12087465737432333135008b12087465737432333136008c120874657374323331" + "37008d12087465737432333138008e12087465737432333139008f1208746573743233323000901208746573743233" + "3231009112087465737432333232009212087465737432333233009312087465737432333234009412087465737432" + "3332350095120874657374323332360096120874657374323332370097120874657374323332380098120874657374" + "32333239009912087465737432333330009a12087465737432333331009b12087465737432333332009c1208746573" + "7432333333009d12087465737432333334009e12087465737432333335009f1208746573743233333600a012087465" + "73743233333700a11208746573743233333800a21208746573743233333900a31208746573743233343000a4120874" + "6573743233343100a51208746573743233343200a61208746573743233343300a71208746573743233343400a81208" + "746573743233343500a91208746573743233343600aa1208746573743233343700ab1208746573743233343800ac12" + "08746573743233343900ad1208746573743233353000ae1208746573743233353100af1208746573743233353200b0" + "1208746573743233353300b11208746573743233353400b21208746573743233353500b31208746573743233353600" + "b41208746573743233353700b51208746573743233353800b61208746573743233353900b712087465737432333630" + "00b81208746573743233363100b91208746573743233363200ba1208746573743233363300bb120874657374323336" + "3400bc1208746573743233363500bd1208746573743233363600be1208746573743233363700bf1208746573743233" + "363800c01208746573743233363900c11208746573743233373000c21208746573743233373100c312087465737432" + "33373200c41208746573743233373300c51208746573743233373400c61208746573743233373500c7120874657374" + "3233373600c81208746573743233373700c91208746573743233373800ca1208746573743233373900cb1208746573" + "743233383000cc1208746573743233383100cd1208746573743233383200ce1208746573743233383300cf12087465" + "73743233383400d01208746573743233383500d11208746573743233383600d21208746573743233383700d3120874" + "6573743233383800d41208746573743233383900d51208746573743233393000d61208746573743233393100d71208" + "746573743233393200d81208746573743233393300d91208746573743233393400da1208746573743233393500db12" + "08746573743233393600dc1208746573743233393700dd1208746573743233393800de1208746573743233393900df" + "1208746573743234303000e01208746573743234303100e11208746573743234303200e21208746573743234303300" + "e31208746573743234303400e41208746573743234303500e51208746573743234303600e612087465737432343037" + "00e71208746573743234303800e81208746573743234303900e91208746573743234313000ea120874657374323431" + "3100eb1208746573743234313200ec1208746573743234313300ed1208746573743234313400ee1208746573743234" + "313500ef1208746573743234313600f01208746573743234313700f11208746573743234313800f212087465737432" + "34313900f31208746573743234323000f41208746573743234323100f51208746573743234323200f6120874657374" + "3234323300f71208746573743234323400f81208746573743234323500f91208746573743234323600fa1208746573" + "743234323700fb1208746573743234323800fc1208746573743234323900fd1208746573743234333000fe12087465" + "73743234333100ff120874657374323433320080130874657374323433330081130874657374323433340082130874" + "6573743234333500831308746573743234333600841308746573743234333700851308746573743234333800861308" + "7465737432343339008713087465737432343430008813087465737432343431008913087465737432343432008a13" + "087465737432343433008b13087465737432343434008c13087465737432343435008d13087465737432343436008e" + "13087465737432343437008f1308746573743234343800901308746573743234343900911308746573743234353000" + "9213087465737432343531009313087465737432343532009413087465737432343533009513087465737432343534" + "0096130874657374323435350097130874657374323435360098130874657374323435370099130874657374323435" + "38009a13087465737432343539009b13087465737432343630009c13087465737432343631009d1308746573743234" + "3632009e13087465737432343633009f1308746573743234363400a01308746573743234363500a113087465737432" + "34363600a21308746573743234363700a31308746573743234363800a41308746573743234363900a5130874657374" + "3234373000a61308746573743234373100a71308746573743234373200a81308746573743234373300a91308746573" + "743234373400aa1308746573743234373500ab1308746573743234373600ac1308746573743234373700ad13087465" + "73743234373800ae1308746573743234373900af1308746573743234383000b01308746573743234383100b1130874" + "6573743234383200b21308746573743234383300b31308746573743234383400b41308746573743234383500b51308" + "746573743234383600b61308746573743234383700b71308746573743234383800b81308746573743234383900b913" + "08746573743234393000ba1308746573743234393100bb1308746573743234393200bc1308746573743234393300bd" + "1308746573743234393400be1308746573743234393500bf1308746573743234393600c01308746573743234393700" + "c11308746573743234393800c21308746573743234393900c31308746573743235303000c413087465737432353031" + "00c51308746573743235303200c61308746573743235303300c71308746573743235303400c8130874657374323530" + "3500c91308746573743235303600ca1308746573743235303700cb1308746573743235303800cc1308746573743235" + "303900cd1308746573743235313000ce1308746573743235313100cf1308746573743235313200d013087465737432" + "35313300d11308746573743235313400d21308746573743235313500d31308746573743235313600d4130874657374" + "3235313700d51308746573743235313800d61308746573743235313900d71308746573743235323000d81308746573" + "743235323100d91308746573743235323200da1308746573743235323300db1308746573743235323400dc13087465" + "73743235323500dd1308746573743235323600de1308746573743235323700df1308746573743235323800e0130874" + "6573743235323900e11308746573743235333000e21308746573743235333100e31308746573743235333200e41308" + "746573743235333300e51308746573743235333400e61308746573743235333500e71308746573743235333600e813" + "08746573743235333700e91308746573743235333800ea1308746573743235333900eb1308746573743235343000ec" + "1308746573743235343100ed1308746573743235343200ee1308746573743235343300ef1308746573743235343400" + "f01308746573743235343500f11308746573743235343600f21308746573743235343700f313087465737432353438" + "00f41308746573743235343900f51308746573743235353000f61308746573743235353100f7130874657374323535" + "3200f81308746573743235353300f91308746573743235353400fa1308746573743235353500fb1308746573743235" + "353600fc1308746573743235353700fd1308746573743235353800fe1308746573743235353900ff13087465737432" + "3536300080140874657374323536310081140874657374323536320082140874657374323536330083140874657374" + "3235363400841408746573743235363500851408746573743235363600861408746573743235363700871408746573" + "7432353638008814087465737432353639008914087465737432353730008a14087465737432353731008b14087465" + "737432353732008c14087465737432353733008d14087465737432353734008e14087465737432353735008f140874" + "6573743235373600901408746573743235373700911408746573743235373800921408746573743235373900931408" + "7465737432353830009414087465737432353831009514087465737432353832009614087465737432353833009714" + "087465737432353834009814087465737432353835009914087465737432353836009a14087465737432353837009b" + "14087465737432353838009c14087465737432353839009d14087465737432353930009e1408746573743235393100" + "9f1408746573743235393200a01408746573743235393300a11408746573743235393400a214087465737432353935" + "00a31408746573743235393600a41408746573743235393700a51408746573743235393800a6140874657374323539" + "3900a71408746573743236303000a81408746573743236303100a91408746573743236303200aa1408746573743236" + "303300ab1408746573743236303400ac1408746573743236303500ad1408746573743236303600ae14087465737432" + "36303700af1408746573743236303800b01408746573743236303900b11408746573743236313000b2140874657374" + "3236313100b31408746573743236313200b41408746573743236313300b51408746573743236313400b61408746573" + "743236313500b71408746573743236313600b81408746573743236313700b91408746573743236313800ba14087465" + "73743236313900bb1408746573743236323000bc1408746573743236323100bd1408746573743236323200be140874" + "6573743236323300bf1408746573743236323400c01408746573743236323500c11408746573743236323600c21408" + "746573743236323700c31408746573743236323800c41408746573743236323900c51408746573743236333000c614" + "08746573743236333100c71408746573743236333200c81408746573743236333300c91408746573743236333400ca" + "1408746573743236333500cb1408746573743236333600cc1408746573743236333700cd1408746573743236333800" + "ce1408746573743236333900cf1408746573743236343000d01408746573743236343100d114087465737432363432" + "00d21408746573743236343300d31408746573743236343400d41408746573743236343500d5140874657374323634" + "3600d61408746573743236343700d71408746573743236343800d81408746573743236343900d91408746573743236" + "353000da1408746573743236353100db1408746573743236353200dc1408746573743236353300dd14087465737432" + "36353400de1408746573743236353500df1408746573743236353600e01408746573743236353700e1140874657374" + "3236353800e21408746573743236353900e31408746573743236363000e41408746573743236363100e51408746573" + "743236363200e61408746573743236363300e71408746573743236363400e81408746573743236363500e914087465" + "73743236363600ea1408746573743236363700eb1408746573743236363800ec1408746573743236363900ed140874" + "6573743236373000ee1408746573743236373100ef1408746573743236373200f01408746573743236373300f11408" + "746573743236373400f21408746573743236373500f31408746573743236373600f41408746573743236373700f514" + "08746573743236373800f61408746573743236373900f71408746573743236383000f81408746573743236383100f9" + "1408746573743236383200fa1408746573743236383300fb1408746573743236383400fc1408746573743236383500" + "fd1408746573743236383600fe1408746573743236383700ff14087465737432363838008015087465737432363839" + "0081150874657374323639300082150874657374323639310083150874657374323639320084150874657374323639" + "3300851508746573743236393400861508746573743236393500871508746573743236393600881508746573743236" + "3937008915087465737432363938008a15087465737432363939008b15087465737432373030008c15087465737432" + "373031008d15087465737432373032008e15087465737432373033008f150874657374323730340090150874657374" + "3237303500911508746573743237303600921508746573743237303700931508746573743237303800941508746573" + "7432373039009515087465737432373130009615087465737432373131009715087465737432373132009815087465" + "737432373133009915087465737432373134009a15087465737432373135009b15087465737432373136009c150874" + "65737432373137009d15087465737432373138009e15087465737432373139009f1508746573743237323000a01508" + "746573743237323100a11508746573743237323200a21508746573743237323300a31508746573743237323400a415" + "08746573743237323500a51508746573743237323600a61508746573743237323700a71508746573743237323800a8" + "1508746573743237323900a91508746573743237333000aa1508746573743237333100ab1508746573743237333200" + "ac1508746573743237333300ad1508746573743237333400ae1508746573743237333500af15087465737432373336" + "00b01508746573743237333700b11508746573743237333800b21508746573743237333900b3150874657374323734" + "3000b41508746573743237343100b51508746573743237343200b61508746573743237343300b71508746573743237" + "343400b81508746573743237343500b91508746573743237343600ba1508746573743237343700bb15087465737432" + "37343800bc1508746573743237343900bd1508746573743237353000be1508746573743237353100bf150874657374" + "3237353200c01508746573743237353300c11508746573743237353400c21508746573743237353500c31508746573" + "743237353600c41508746573743237353700c51508746573743237353800c61508746573743237353900c715087465" + "73743237363000c81508746573743237363100c91508746573743237363200ca1508746573743237363300cb150874" + "6573743237363400cc1508746573743237363500cd1508746573743237363600ce1508746573743237363700cf1508" + "746573743237363800d01508746573743237363900d11508746573743237373000d21508746573743237373100d315" + "08746573743237373200d41508746573743237373300d51508746573743237373400d61508746573743237373500d7" + "1508746573743237373600d81508746573743237373700d91508746573743237373800da1508746573743237373900" + "db1508746573743237383000dc1508746573743237383100dd1508746573743237383200de15087465737432373833" + "00df1508746573743237383400e01508746573743237383500e11508746573743237383600e2150874657374323738" + "3700e31508746573743237383800e41508746573743237383900e51508746573743237393000e61508746573743237" + "393100e71508746573743237393200e81508746573743237393300e91508746573743237393400ea15087465737432" + "37393500eb1508746573743237393600ec1508746573743237393700ed1508746573743237393800ee150874657374" + "3237393900ef1508746573743238303000f01508746573743238303100f11508746573743238303200f21508746573" + "743238303300f31508746573743238303400f41508746573743238303500f51508746573743238303600f615087465" + "73743238303700f71508746573743238303800f81508746573743238303900f91508746573743238313000fa150874" + "6573743238313100fb1508746573743238313200fc1508746573743238313300fd1508746573743238313400fe1508" + "746573743238313500ff15087465737432383136008016087465737432383137008116087465737432383138008216" + "0874657374323831390083160874657374323832300084160874657374323832310085160874657374323832320086" + "1608746573743238323300871608746573743238323400881608746573743238323500891608746573743238323600" + "8a16087465737432383237008b16087465737432383238008c16087465737432383239008d16087465737432383330" + "008e16087465737432383331008f160874657374323833320090160874657374323833330091160874657374323833" + "3400921608746573743238333500931608746573743238333600941608746573743238333700951608746573743238" + "3338009616087465737432383339009716087465737432383430009816087465737432383431009916087465737432" + "383432009a16087465737432383433009b16087465737432383434009c16087465737432383435009d160874657374" + "32383436009e16087465737432383437009f1608746573743238343800a01608746573743238343900a11608746573" + "743238353000a21608746573743238353100a31608746573743238353200a41608746573743238353300a516087465" + "73743238353400a61608746573743238353500a71608746573743238353600a81608746573743238353700a9160874" + "6573743238353800aa1608746573743238353900ab1608746573743238363000ac1608746573743238363100ad1608" + "746573743238363200ae1608746573743238363300af1608746573743238363400b01608746573743238363500b116" + "08746573743238363600b21608746573743238363700b31608746573743238363800b41608746573743238363900b5" + "1608746573743238373000b61608746573743238373100b71608746573743238373200b81608746573743238373300" + "b91608746573743238373400ba1608746573743238373500bb1608746573743238373600bc16087465737432383737" + "00bd1608746573743238373800be1608746573743238373900bf1608746573743238383000c0160874657374323838" + "3100c11608746573743238383200c21608746573743238383300c31608746573743238383400c41608746573743238" + "383500c51608746573743238383600c61608746573743238383700c71608746573743238383800c816087465737432" + "38383900c91608746573743238393000ca1608746573743238393100cb1608746573743238393200cc160874657374" + "3238393300cd1608746573743238393400ce1608746573743238393500cf1608746573743238393600d01608746573" + "743238393700d11608746573743238393800d21608746573743238393900d31608746573743239303000d416087465" + "73743239303100d51608746573743239303200d61608746573743239303300d71608746573743239303400d8160874" + "6573743239303500d91608746573743239303600da1608746573743239303700db1608746573743239303800dc1608" + "746573743239303900dd1608746573743239313000de1608746573743239313100df1608746573743239313200e016" + "08746573743239313300e11608746573743239313400e21608746573743239313500e31608746573743239313600e4" + "1608746573743239313700e51608746573743239313800e61608746573743239313900e71608746573743239323000" + "e81608746573743239323100e91608746573743239323200ea1608746573743239323300eb16087465737432393234" + "00ec1608746573743239323500ed1608746573743239323600ee1608746573743239323700ef160874657374323932" + "3800f01608746573743239323900f11608746573743239333000f21608746573743239333100f31608746573743239" + "333200f41608746573743239333300f51608746573743239333400f61608746573743239333500f716087465737432" + "39333600f81608746573743239333700f91608746573743239333800fa1608746573743239333900fb160874657374" + "3239343000fc1608746573743239343100fd1608746573743239343200fe1608746573743239343300ff1608746573" + "7432393434008017087465737432393435008117087465737432393436008217087465737432393437008317087465" + "7374323934380084170874657374323934390085170874657374323935300086170874657374323935310087170874" + "65737432393532008817087465737432393533008917087465737432393534008a17087465737432393535008b1708" + "7465737432393536008c17087465737432393537008d17087465737432393538008e17087465737432393539008f17" + "0874657374323936300090170874657374323936310091170874657374323936320092170874657374323936330093" + "1708746573743239363400941708746573743239363500951708746573743239363600961708746573743239363700" + "9717087465737432393638009817087465737432393639009917087465737432393730009a17087465737432393731" + "009b17087465737432393732009c17087465737432393733009d17087465737432393734009e170874657374323937" + "35009f1708746573743239373600a01708746573743239373700a11708746573743239373800a21708746573743239" + "373900a31708746573743239383000a41708746573743239383100a51708746573743239383200a617087465737432" + "39383300a71708746573743239383400a81708746573743239383500a91708746573743239383600aa170874657374" + "3239383700ab1708746573743239383800ac1708746573743239383900ad1708746573743239393000ae1708746573" + "743239393100af1708746573743239393200b01708746573743239393300b11708746573743239393400b217087465" + "73743239393500b31708746573743239393600b41708746573743239393700b51708746573743239393800b6170874" + "6573743239393900b71708746573743330303000b81708746573743330303100b91708746573743330303200ba1708" + "746573743330303300bb1708746573743330303400bc1708746573743330303500bd1708746573743330303600be17" + "08746573743330303700bf1708746573743330303800c01708746573743330303900c11708746573743330313000c2" + "1708746573743330313100c31708746573743330313200c41708746573743330313300c51708746573743330313400" + "c61708746573743330313500c71708746573743330313600c81708746573743330313700c917087465737433303138" + "00ca1708746573743330313900cb1708746573743330323000cc1708746573743330323100cd170874657374333032" + "3200ce1708746573743330323300cf1708746573743330323400d01708746573743330323500d11708746573743330" + "323600d21708746573743330323700d31708746573743330323800d41708746573743330323900d517087465737433" + "30333000d61708746573743330333100d71708746573743330333200d81708746573743330333300d9170874657374" + "3330333400da1708746573743330333500db1708746573743330333600dc1708746573743330333700dd1708746573" + "743330333800de1708746573743330333900df1708746573743330343000e01708746573743330343100e117087465" + "73743330343200e21708746573743330343300e31708746573743330343400e41708746573743330343500e5170874" + "6573743330343600e61708746573743330343700e71708746573743330343800e81708746573743330343900e91708" + "746573743330353000ea1708746573743330353100eb1708746573743330353200ec1708746573743330353300ed17" + "08746573743330353400ee1708746573743330353500ef1708746573743330353600f01708746573743330353700f1" + "1708746573743330353800f21708746573743330353900f31708746573743330363000f41708746573743330363100" + "f51708746573743330363200f61708746573743330363300f71708746573743330363400f817087465737433303635" + "00f91708746573743330363600fa1708746573743330363700fb1708746573743330363800fc170874657374333036" + "3900fd1708746573743330373000fe1708746573743330373100ff1708746573743330373200801808746573743330" + "3733008118087465737433303734008218087465737433303735008318087465737433303736008418087465737433" + "3037370085180874657374333037380086180874657374333037390087180874657374333038300088180874657374" + "33303831008918087465737433303832008a18087465737433303833008b18087465737433303834008c1808746573" + "7433303835008d18087465737433303836008e18087465737433303837008f18087465737433303838009018087465" + "7374333038390091180874657374333039300092180874657374333039310093180874657374333039320094180874" + "6573743330393300951808746573743330393400961808746573743330393500971808746573743330393600981808" + "7465737433303937009918087465737433303938009a18087465737433303939009b18087465737433313030009c18" + "087465737433313031009d18087465737433313032009e18087465737433313033009f1808746573743331303400a0" + "1808746573743331303500a11808746573743331303600a21808746573743331303700a31808746573743331303800" + "a41808746573743331303900a51808746573743331313000a61808746573743331313100a718087465737433313132" + "00a81808746573743331313300a91808746573743331313400aa1808746573743331313500ab180874657374333131" + "3600ac1808746573743331313700ad1808746573743331313800ae1808746573743331313900af1808746573743331" + "323000b01808746573743331323100b11808746573743331323200b21808746573743331323300b318087465737433" + "31323400b41808746573743331323500b51808746573743331323600b61808746573743331323700b7180874657374" + "3331323800b81808746573743331323900b91808746573743331333000ba1808746573743331333100bb1808746573" + "743331333200bc1808746573743331333300bd1808746573743331333400be1808746573743331333500bf18087465" + "73743331333600c01808746573743331333700c11808746573743331333800c21808746573743331333900c3180874" + "6573743331343000c41808746573743331343100c51808746573743331343200c61808746573743331343300c71808" + "746573743331343400c81808746573743331343500c91808746573743331343600ca1808746573743331343700cb18" + "08746573743331343800cc1808746573743331343900cd1808746573743331353000ce1808746573743331353100cf" + "1808746573743331353200d01808746573743331353300d11808746573743331353400d21808746573743331353500" + "d31808746573743331353600d41808746573743331353700d51808746573743331353800d618087465737433313539" + "00d71808746573743331363000d81808746573743331363100d91808746573743331363200da180874657374333136" + "3300db1808746573743331363400dc1808746573743331363500dd1808746573743331363600de1808746573743331" + "363700df1808746573743331363800e01808746573743331363900e11808746573743331373000e218087465737433" + "31373100e31808746573743331373200e41808746573743331373300e51808746573743331373400e6180874657374" + "3331373500e71808746573743331373600e81808746573743331373700e91808746573743331373800ea1808746573" + "743331373900eb1808746573743331383000ec1808746573743331383100ed1808746573743331383200ee18087465" + "73743331383300ef1808746573743331383400f01808746573743331383500f11808746573743331383600f2180874" + "6573743331383700f31808746573743331383800f41808746573743331383900f51808746573743331393000f61808" + "746573743331393100f71808746573743331393200f81808746573743331393300f91808746573743331393400fa18" + "08746573743331393500fb1808746573743331393600fc1808746573743331393700fd1808746573743331393800fe" + "1808746573743331393900ff1808746573743332303000801908746573743332303100811908746573743332303200" + "8219087465737433323033008319087465737433323034008419087465737433323035008519087465737433323036" + "0086190874657374333230370087190874657374333230380088190874657374333230390089190874657374333231" + "30008a19087465737433323131008b19087465737433323132008c19087465737433323133008d1908746573743332" + "3134008e19087465737433323135008f19087465737433323136009019087465737433323137009119087465737433" + "3231380092190874657374333231390093190874657374333232300094190874657374333232310095190874657374" + "3332323200961908746573743332323300971908746573743332323400981908746573743332323500991908746573" + "7433323236009a19087465737433323237009b19087465737433323238009c19087465737433323239009d19087465" + "737433323330009e19087465737433323331009f1908746573743332333200a01908746573743332333300a1190874" + "6573743332333400a21908746573743332333500a31908746573743332333600a41908746573743332333700a51908" + "746573743332333800a61908746573743332333900a71908746573743332343000a81908746573743332343100a919" + "08746573743332343200aa1908746573743332343300ab1908746573743332343400ac1908746573743332343500ad" + "1908746573743332343600ae1908746573743332343700af1908746573743332343800b01908746573743332343900" + "b11908746573743332353000b21908746573743332353100b31908746573743332353200b419087465737433323533" + "00b51908746573743332353400b61908746573743332353500b71908746573743332353600b8190874657374333235" + "3700b91908746573743332353800ba1908746573743332353900bb1908746573743332363000bc1908746573743332" + "363100bd1908746573743332363200be1908746573743332363300bf1908746573743332363400c019087465737433" + "32363500c11908746573743332363600c21908746573743332363700c31908746573743332363800c4190874657374" + "3332363900c51908746573743332373000c61908746573743332373100c71908746573743332373200c81908746573" + "743332373300c91908746573743332373400ca1908746573743332373500cb1908746573743332373600cc19087465" + "73743332373700cd1908746573743332373800ce1908746573743332373900cf1908746573743332383000d0190874" + "6573743332383100d11908746573743332383200d21908746573743332383300d31908746573743332383400d41908" + "746573743332383500d51908746573743332383600d61908746573743332383700d71908746573743332383800d819" + "08746573743332383900d91908746573743332393000da1908746573743332393100db1908746573743332393200dc" + "1908746573743332393300dd1908746573743332393400de1908746573743332393500df1908746573743332393600" + "e01908746573743332393700e11908746573743332393800e21908746573743332393900e319087465737433333030" + "00e41908746573743333303100e51908746573743333303200e61908746573743333303300e7190874657374333330" + "3400e81908746573743333303500e91908746573743333303600ea1908746573743333303700eb1908746573743333" + "303800ec1908746573743333303900ed1908746573743333313000ee1908746573743333313100ef19087465737433" + "33313200f01908746573743333313300f11908746573743333313400f21908746573743333313500f3190874657374" + "3333313600f41908746573743333313700f51908746573743333313800f61908746573743333313900f71908746573" + "743333323000f81908746573743333323100f91908746573743333323200fa1908746573743333323300fb19087465" + "73743333323400fc1908746573743333323500fd1908746573743333323600fe1908746573743333323700ff190874" + "6573743333323800801a08746573743333323900811a08746573743333333000821a08746573743333333100831a08" + "746573743333333200841a08746573743333333300851a08746573743333333400861a08746573743333333500871a" + "08746573743333333600881a08746573743333333700891a087465737433333338008a1a087465737433333339008b" + "1a087465737433333430008c1a087465737433333431008d1a087465737433333432008e1a08746573743333343300" + "8f1a08746573743333343400901a08746573743333343500911a08746573743333343600921a087465737433333437" + "00931a08746573743333343800941a08746573743333343900951a08746573743333353000961a0874657374333335" + "3100971a08746573743333353200981a08746573743333353300991a087465737433333534009a1a08746573743333" + "3535009b1a087465737433333536009c1a087465737433333537009d1a087465737433333538009e1a087465737433" + "333539009f1a08746573743333363000a01a08746573743333363100a11a08746573743333363200a21a0874657374" + "3333363300a31a08746573743333363400a41a08746573743333363500a51a08746573743333363600a61a08746573" + "743333363700a71a08746573743333363800a81a08746573743333363900a91a08746573743333373000aa1a087465" + "73743333373100ab1a08746573743333373200ac1a08746573743333373300ad1a08746573743333373400ae1a0874" + "6573743333373500af1a08746573743333373600b01a08746573743333373700b11a08746573743333373800b21a08" + "746573743333373900b31a08746573743333383000b41a08746573743333383100b51a08746573743333383200b61a" + "08746573743333383300b71a08746573743333383400b81a08746573743333383500b91a08746573743333383600ba" + "1a08746573743333383700bb1a08746573743333383800bc1a08746573743333383900bd1a08746573743333393000" + "be1a08746573743333393100bf1a08746573743333393200c01a08746573743333393300c11a087465737433333934" + "00c21a08746573743333393500c31a08746573743333393600c41a08746573743333393700c51a0874657374333339" + "3800c61a08746573743333393900c71a08746573743334303000c81a08746573743334303100c91a08746573743334" + "303200ca1a08746573743334303300cb1a08746573743334303400cc1a08746573743334303500cd1a087465737433" + "34303600ce1a08746573743334303700cf1a08746573743334303800d01a08746573743334303900d11a0874657374" + "3334313000d21a08746573743334313100d31a08746573743334313200d41a08746573743334313300d51a08746573" + "743334313400d61a08746573743334313500d71a08746573743334313600d81a08746573743334313700d91a087465" + "73743334313800da1a08746573743334313900db1a08746573743334323000dc1a08746573743334323100dd1a0874" + "6573743334323200de1a08746573743334323300df1a08746573743334323400e01a08746573743334323500e11a08" + "746573743334323600e21a08746573743334323700e31a08746573743334323800e41a08746573743334323900e51a" + "08746573743334333000e61a08746573743334333100e71a08746573743334333200e81a08746573743334333300e9" + "1a08746573743334333400ea1a08746573743334333500eb1a08746573743334333600ec1a08746573743334333700" + "ed1a08746573743334333800ee1a08746573743334333900ef1a08746573743334343000f01a087465737433343431" + "00f11a08746573743334343200f21a08746573743334343300f31a08746573743334343400f41a0874657374333434" + "3500f51a08746573743334343600f61a08746573743334343700f71a08746573743334343800f81a08746573743334" + "343900f91a08746573743334353000fa1a08746573743334353100fb1a08746573743334353200fc1a087465737433" + "34353300fd1a08746573743334353400fe1a08746573743334353500ff1a08746573743334353600801b0874657374" + "3334353700811b08746573743334353800821b08746573743334353900831b08746573743334363000841b08746573" + "743334363100851b08746573743334363200861b08746573743334363300871b08746573743334363400881b087465" + "73743334363500891b087465737433343636008a1b087465737433343637008b1b087465737433343638008c1b0874" + "65737433343639008d1b087465737433343730008e1b087465737433343731008f1b08746573743334373200901b08" + "746573743334373300911b08746573743334373400921b08746573743334373500931b08746573743334373600941b" + "08746573743334373700951b08746573743334373800961b08746573743334373900971b0874657374333438300098" + "1b08746573743334383100991b087465737433343832009a1b087465737433343833009b1b08746573743334383400" + "9c1b087465737433343835009d1b087465737433343836009e1b087465737433343837009f1b087465737433343838" + "00a01b08746573743334383900a11b08746573743334393000a21b08746573743334393100a31b0874657374333439" + "3200a41b08746573743334393300a51b08746573743334393400a61b08746573743334393500a71b08746573743334" + "393600a81b08746573743334393700a91b08746573743334393800aa1b08746573743334393900ab1b087465737433" + "35303000ac1b08746573743335303100ad1b08746573743335303200ae1b08746573743335303300af1b0874657374" + "3335303400b01b08746573743335303500b11b08746573743335303600b21b08746573743335303700b31b08746573" + "743335303800b41b08746573743335303900b51b08746573743335313000b61b08746573743335313100b71b087465" + "73743335313200b81b08746573743335313300b91b08746573743335313400ba1b08746573743335313500bb1b0874" + "6573743335313600bc1b08746573743335313700bd1b08746573743335313800be1b08746573743335313900bf1b08" + "746573743335323000c01b08746573743335323100c11b08746573743335323200c21b08746573743335323300c31b" + "08746573743335323400c41b08746573743335323500c51b08746573743335323600c61b08746573743335323700c7" + "1b08746573743335323800c81b08746573743335323900c91b08746573743335333000ca1b08746573743335333100" + "cb1b08746573743335333200cc1b08746573743335333300cd1b08746573743335333400ce1b087465737433353335" + "00cf1b08746573743335333600d01b08746573743335333700d11b08746573743335333800d21b0874657374333533" + "3900d31b08746573743335343000d41b08746573743335343100d51b08746573743335343200d61b08746573743335" + "343300d71b08746573743335343400d81b08746573743335343500d91b08746573743335343600da1b087465737433" + "35343700db1b08746573743335343800dc1b08746573743335343900dd1b08746573743335353000de1b0874657374" + "3335353100df1b08746573743335353200e01b08746573743335353300e11b08746573743335353400e21b08746573" + "743335353500e31b08746573743335353600e41b08746573743335353700e51b08746573743335353800e61b087465" + "73743335353900e71b08746573743335363000e81b08746573743335363100e91b08746573743335363200ea1b0874" + "6573743335363300eb1b08746573743335363400ec1b08746573743335363500ed1b08746573743335363600ee1b08" + "746573743335363700ef1b08746573743335363800f01b08746573743335363900f11b08746573743335373000f21b" + "08746573743335373100f31b08746573743335373200f41b08746573743335373300f51b08746573743335373400f6" + "1b08746573743335373500f71b08746573743335373600f81b08746573743335373700f91b08746573743335373800" + "fa1b08746573743335373900fb1b08746573743335383000fc1b08746573743335383100fd1b087465737433353832" + "00fe1b08746573743335383300ff1b08746573743335383400801c08746573743335383500811c0874657374333538" + "3600821c08746573743335383700831c08746573743335383800841c08746573743335383900851c08746573743335" + "393000861c08746573743335393100871c08746573743335393200881c08746573743335393300891c087465737433" + "353934008a1c087465737433353935008b1c087465737433353936008c1c087465737433353937008d1c0874657374" + "33353938008e1c087465737433353939008f1c08746573743336303000901c08746573743336303100911c08746573" + "743336303200921c08746573743336303300931c08746573743336303400941c08746573743336303500951c087465" + "73743336303600961c08746573743336303700971c08746573743336303800981c08746573743336303900991c0874" + "65737433363130009a1c087465737433363131009b1c087465737433363132009c1c087465737433363133009d1c08" + "7465737433363134009e1c087465737433363135009f1c08746573743336313600a01c08746573743336313700a11c" + "08746573743336313800a21c08746573743336313900a31c08746573743336323000a41c08746573743336323100a5" + "1c08746573743336323200a61c08746573743336323300a71c08746573743336323400a81c08746573743336323500" + "a91c08746573743336323600aa1c08746573743336323700ab1c08746573743336323800ac1c087465737433363239" + "00ad1c08746573743336333000ae1c08746573743336333100af1c08746573743336333200b01c0874657374333633" + "3300b11c08746573743336333400b21c08746573743336333500b31c08746573743336333600b41c08746573743336" + "333700b51c08746573743336333800b61c08746573743336333900b71c08746573743336343000b81c087465737433" + "36343100b91c08746573743336343200ba1c08746573743336343300bb1c08746573743336343400bc1c0874657374" + "3336343500bd1c08746573743336343600be1c08746573743336343700bf1c08746573743336343800c01c08746573" + "743336343900c11c08746573743336353000c21c08746573743336353100c31c08746573743336353200c41c087465" + "73743336353300c51c08746573743336353400c61c08746573743336353500c71c08746573743336353600c81c0874" + "6573743336353700c91c08746573743336353800ca1c08746573743336353900cb1c08746573743336363000cc1c08" + "746573743336363100cd1c08746573743336363200ce1c08746573743336363300cf1c08746573743336363400d01c" + "08746573743336363500d11c08746573743336363600d21c08746573743336363700d31c08746573743336363800d4" + "1c08746573743336363900d51c08746573743336373000d61c08746573743336373100d71c08746573743336373200" + "d81c08746573743336373300d91c08746573743336373400da1c08746573743336373500db1c087465737433363736" + "00dc1c08746573743336373700dd1c08746573743336373800de1c08746573743336373900df1c0874657374333638" + "3000e01c08746573743336383100e11c08746573743336383200e21c08746573743336383300e31c08746573743336" + "383400e41c08746573743336383500e51c08746573743336383600e61c08746573743336383700e71c087465737433" + "36383800e81c08746573743336383900e91c08746573743336393000ea1c08746573743336393100eb1c0874657374" + "3336393200ec1c08746573743336393300ed1c08746573743336393400ee1c08746573743336393500ef1c08746573" + "743336393600f01c08746573743336393700f11c08746573743336393800f21c08746573743336393900f31c087465" + "73743337303000f41c08746573743337303100f51c08746573743337303200f61c08746573743337303300f71c0874" + "6573743337303400f81c08746573743337303500f91c08746573743337303600fa1c08746573743337303700fb1c08" + "746573743337303800fc1c08746573743337303900fd1c08746573743337313000fe1c08746573743337313100ff1c" + "08746573743337313200801d08746573743337313300811d08746573743337313400821d0874657374333731350083" + "1d08746573743337313600841d08746573743337313700851d08746573743337313800861d08746573743337313900" + "871d08746573743337323000881d08746573743337323100891d087465737433373232008a1d087465737433373233" + "008b1d087465737433373234008c1d087465737433373235008d1d087465737433373236008e1d0874657374333732" + "37008f1d08746573743337323800901d08746573743337323900911d08746573743337333000921d08746573743337" + "333100931d08746573743337333200941d08746573743337333300951d08746573743337333400961d087465737433" + "37333500971d08746573743337333600981d08746573743337333700991d087465737433373338009a1d0874657374" + "33373339009b1d087465737433373430009c1d087465737433373431009d1d087465737433373432009e1d08746573" + "7433373433009f1d08746573743337343400a01d08746573743337343500a11d08746573743337343600a21d087465" + "73743337343700a31d08746573743337343800a41d08746573743337343900a51d08746573743337353000a61d0874" + "6573743337353100a71d08746573743337353200a81d08746573743337353300a91d08746573743337353400aa1d08" + "746573743337353500ab1d08746573743337353600ac1d08746573743337353700ad1d08746573743337353800ae1d" + "08746573743337353900af1d08746573743337363000b01d08746573743337363100b11d08746573743337363200b2" + "1d08746573743337363300b31d08746573743337363400b41d08746573743337363500b51d08746573743337363600" + "b61d08746573743337363700b71d08746573743337363800b81d08746573743337363900b91d087465737433373730" + "00ba1d08746573743337373100bb1d08746573743337373200bc1d08746573743337373300bd1d0874657374333737" + "3400be1d08746573743337373500bf1d08746573743337373600c01d08746573743337373700c11d08746573743337" + "373800c21d08746573743337373900c31d08746573743337383000c41d08746573743337383100c51d087465737433" + "37383200c61d08746573743337383300c71d08746573743337383400c81d08746573743337383500c91d0874657374" + "3337383600ca1d08746573743337383700cb1d08746573743337383800cc1d08746573743337383900cd1d08746573" + "743337393000ce1d08746573743337393100cf1d08746573743337393200d01d08746573743337393300d11d087465" + "73743337393400d21d08746573743337393500d31d08746573743337393600d41d08746573743337393700d51d0874" + "6573743337393800d61d08746573743337393900d71d08746573743338303000d81d08746573743338303100d91d08" + "746573743338303200da1d08746573743338303300db1d08746573743338303400dc1d08746573743338303500dd1d" + "08746573743338303600de1d08746573743338303700df1d08746573743338303800e01d08746573743338303900e1" + "1d08746573743338313000e21d08746573743338313100e31d08746573743338313200e41d08746573743338313300" + "e51d08746573743338313400e61d08746573743338313500e71d08746573743338313600e81d087465737433383137" + "00e91d08746573743338313800ea1d08746573743338313900eb1d08746573743338323000ec1d0874657374333832" + "3100ed1d08746573743338323200ee1d08746573743338323300ef1d08746573743338323400f01d08746573743338" + "323500f11d08746573743338323600f21d08746573743338323700f31d08746573743338323800f41d087465737433" + "38323900f51d08746573743338333000f61d08746573743338333100f71d08746573743338333200f81d0874657374" + "3338333300f91d08746573743338333400fa1d08746573743338333500fb1d08746573743338333600fc1d08746573" + "743338333700fd1d08746573743338333800fe1d08746573743338333900ff1d08746573743338343000801e087465" + "73743338343100811e08746573743338343200821e08746573743338343300831e08746573743338343400841e0874" + "6573743338343500851e08746573743338343600861e08746573743338343700871e08746573743338343800881e08" + "746573743338343900891e087465737433383530008a1e087465737433383531008b1e087465737433383532008c1e" + "087465737433383533008d1e087465737433383534008e1e087465737433383535008f1e0874657374333835360090" + "1e08746573743338353700911e08746573743338353800921e08746573743338353900931e08746573743338363000" + "941e08746573743338363100951e08746573743338363200961e08746573743338363300971e087465737433383634" + "00981e08746573743338363500991e087465737433383636009a1e087465737433383637009b1e0874657374333836" + "38009c1e087465737433383639009d1e087465737433383730009e1e087465737433383731009f1e08746573743338" + "373200a01e08746573743338373300a11e08746573743338373400a21e08746573743338373500a31e087465737433" + "38373600a41e08746573743338373700a51e08746573743338373800a61e08746573743338373900a71e0874657374" + "3338383000a81e08746573743338383100a91e08746573743338383200aa1e08746573743338383300ab1e08746573" + "743338383400ac1e08746573743338383500ad1e08746573743338383600ae1e08746573743338383700af1e087465" + "73743338383800b01e08746573743338383900b11e08746573743338393000b21e08746573743338393100b31e0874" + "6573743338393200b41e08746573743338393300b51e08746573743338393400b61e08746573743338393500b71e08" + "746573743338393600b81e08746573743338393700b91e08746573743338393800ba1e08746573743338393900bb1e" + "08746573743339303000bc1e08746573743339303100bd1e08746573743339303200be1e08746573743339303300bf" + "1e08746573743339303400c01e08746573743339303500c11e08746573743339303600c21e08746573743339303700" + "c31e08746573743339303800c41e08746573743339303900c51e08746573743339313000c61e087465737433393131" + "00c71e08746573743339313200c81e08746573743339313300c91e08746573743339313400ca1e0874657374333931" + "3500cb1e08746573743339313600cc1e08746573743339313700cd1e08746573743339313800ce1e08746573743339" + "313900cf1e08746573743339323000d01e08746573743339323100d11e08746573743339323200d21e087465737433" + "39323300d31e08746573743339323400d41e08746573743339323500d51e08746573743339323600d61e0874657374" + "3339323700d71e08746573743339323800d81e08746573743339323900d91e08746573743339333000da1e08746573" + "743339333100db1e08746573743339333200dc1e08746573743339333300dd1e08746573743339333400de1e087465" + "73743339333500df1e08746573743339333600e01e08746573743339333700e11e08746573743339333800e21e0874" + "6573743339333900e31e08746573743339343000e41e08746573743339343100e51e08746573743339343200e61e08" + "746573743339343300e71e08746573743339343400e81e08746573743339343500e91e08746573743339343600ea1e" + "08746573743339343700eb1e08746573743339343800ec1e08746573743339343900ed1e08746573743339353000ee" + "1e08746573743339353100ef1e08746573743339353200f01e08746573743339353300f11e08746573743339353400" + "f21e08746573743339353500f31e08746573743339353600f41e08746573743339353700f51e087465737433393538" + "00f61e08746573743339353900f71e08746573743339363000f81e08746573743339363100f91e0874657374333936" + "3200fa1e08746573743339363300fb1e08746573743339363400fc1e08746573743339363500fd1e08746573743339" + "363600fe1e08746573743339363700ff1e08746573743339363800801f08746573743339363900811f087465737433" + "39373000821f08746573743339373100831f08746573743339373200841f08746573743339373300851f0874657374" + "3339373400861f08746573743339373500871f08746573743339373600881f08746573743339373700891f08746573" + "7433393738008a1f087465737433393739008b1f087465737433393830008c1f087465737433393831008d1f087465" + "737433393832008e1f087465737433393833008f1f08746573743339383400901f08746573743339383500911f0874" + "6573743339383600921f08746573743339383700931f08746573743339383800941f08746573743339383900951f08" + "746573743339393000961f08746573743339393100971f08746573743339393200981f08746573743339393300991f" + "087465737433393934009a1f087465737433393935009b1f087465737433393936009c1f087465737433393937009d" + "1f087465737433393938009e1f087465737433393939009f1f08746573743430303000a01f08746573743430303100" + "a11f08746573743430303200a21f08746573743430303300a31f08746573743430303400a41f087465737434303035" + "00a51f08746573743430303600a61f08746573743430303700a71f08746573743430303800a81f0874657374343030" + "3900a91f08746573743430313000aa1f08746573743430313100ab1f08746573743430313200ac1f08746573743430" + "313300ad1f08746573743430313400ae1f08746573743430313500af1f08746573743430313600b01f087465737434" + "30313700b11f08746573743430313800b21f08746573743430313900b31f08746573743430323000b41f0874657374" + "3430323100b51f08746573743430323200b61f08746573743430323300b71f08746573743430323400b81f08746573" + "743430323500b91f08746573743430323600ba1f08746573743430323700bb1f08746573743430323800bc1f087465" + "73743430323900bd1f08746573743430333000be1f08746573743430333100bf1f08746573743430333200c01f0874" + "6573743430333300c11f08746573743430333400c21f08746573743430333500c31f08746573743430333600c41f08" + "746573743430333700c51f08746573743430333800c61f08746573743430333900c71f08746573743430343000c81f" + "08746573743430343100c91f08746573743430343200ca1f08746573743430343300cb1f08746573743430343400cc" + "1f08746573743430343500cd1f08746573743430343600ce1f08746573743430343700cf1f08746573743430343800" + "d01f08746573743430343900d11f08746573743430353000d21f08746573743430353100d31f087465737434303532" + "00d41f08746573743430353300d51f08746573743430353400d61f08746573743430353500d71f0874657374343035" + "3600d81f08746573743430353700d91f08746573743430353800da1f08746573743430353900db1f08746573743430" + "363000dc1f08746573743430363100dd1f08746573743430363200de1f08746573743430363300df1f087465737434" + "30363400e01f08746573743430363500e11f08746573743430363600e21f08746573743430363700e31f0874657374" + "3430363800e41f08746573743430363900e51f08746573743430373000e61f08746573743430373100e71f08746573" + "743430373200e81f08746573743430373300e91f08746573743430373400ea1f08746573743430373500eb1f087465" + "73743430373600ec1f08746573743430373700ed1f08746573743430373800ee1f08746573743430373900ef1f0874" + "6573743430383000f01f08746573743430383100f11f08746573743430383200f21f08746573743430383300f31f08" + "746573743430383400f41f08746573743430383500f51f08746573743430383600f61f08746573743430383700f71f" + "08746573743430383800f81f08746573743430383900f91f08746573743430393000fa1f08746573743430393100fb" + "1f08746573743430393200fc1f08746573743430393300fd1f08746573743430393400fe1f08746573743430393500" + "ff1f087465737434303936008020087465737434303937008120087465737434303938008220087465737434303939" + "0083200874657374343130300084200874657374343130310085200874657374343130320086200874657374343130" + "33008720087465737434313034008820087465737434313035008920087465737434313036008a2008746573743431" + "3037008b20087465737434313038008c20087465737434313039008d20087465737434313130008e20087465737434" + "313131008f200874657374343131320090200874657374343131330091200874657374343131340092200874657374" + "3431313500932008746573743431313600942008746573743431313700952008746573743431313800962008746573" + "7434313139009720087465737434313230009820087465737434313231009920087465737434313232009a20087465" + "737434313233009b20087465737434313234009c20087465737434313235009d20087465737434313236009e200874" + "65737434313237009f2008746573743431323800a02008746573743431323900a12008746573743431333000a22008" + "746573743431333100a32008746573743431333200a42008746573743431333300a52008746573743431333400a620" + "08746573743431333500a72008746573743431333600a82008746573743431333700a92008746573743431333800aa" + "2008746573743431333900ab2008746573743431343000ac2008746573743431343100ad2008746573743431343200" + "ae2008746573743431343300af2008746573743431343400b02008746573743431343500b120087465737434313436" + "00b22008746573743431343700b32008746573743431343800b42008746573743431343900b5200874657374343135" + "3000b62008746573743431353100b72008746573743431353200b82008746573743431353300b92008746573743431" + "353400ba2008746573743431353500bb2008746573743431353600bc2008746573743431353700bd20087465737434" + "31353800be2008746573743431353900bf2008746573743431363000c02008746573743431363100c1200874657374" + "3431363200c22008746573743431363300c32008746573743431363400c42008746573743431363500c52008746573" + "743431363600c62008746573743431363700c72008746573743431363800c82008746573743431363900c920087465" + "73743431373000ca2008746573743431373100cb2008746573743431373200cc2008746573743431373300cd200874" + "6573743431373400ce2008746573743431373500cf2008746573743431373600d02008746573743431373700d12008" + "746573743431373800d22008746573743431373900d32008746573743431383000d42008746573743431383100d520" + "08746573743431383200d62008746573743431383300d72008746573743431383400d82008746573743431383500d9" + "2008746573743431383600da2008746573743431383700db2008746573743431383800dc2008746573743431383900" + "dd2008746573743431393000de2008746573743431393100df2008746573743431393200e020087465737434313933" + "00e12008746573743431393400e22008746573743431393500e32008746573743431393600e4200874657374343139" + "3700e52008746573743431393800e62008746573743431393900e72008746573743432303000e82008746573743432" + "303100e92008746573743432303200ea2008746573743432303300eb2008746573743432303400ec20087465737434" + "32303500ed2008746573743432303600ee2008746573743432303700ef2008746573743432303800f0200874657374" + "3432303900f12008746573743432313000f22008746573743432313100f32008746573743432313200f42008746573" + "743432313300f52008746573743432313400f62008746573743432313500f72008746573743432313600f820087465" + "73743432313700f92008746573743432313800fa2008746573743432313900fb2008746573743432323000fc200874" + "6573743432323100fd2008746573743432323200fe2008746573743432323300ff2008746573743432323400802108" + "7465737434323235008121087465737434323236008221087465737434323237008321087465737434323238008421" + "0874657374343232390085210874657374343233300086210874657374343233310087210874657374343233320088" + "21087465737434323333008921087465737434323334008a21087465737434323335008b2108746573743432333600" + "8c21087465737434323337008d21087465737434323338008e21087465737434323339008f21087465737434323430" + "0090210874657374343234310091210874657374343234320092210874657374343234330093210874657374343234" + "3400942108746573743432343500952108746573743432343600962108746573743432343700972108746573743432" + "3438009821087465737434323439009921087465737434323530009a21087465737434323531009b21087465737434" + "323532009c21087465737434323533009d21087465737434323534009e21087465737434323535009f210874657374" + "3432353600a02108746573743432353700a12108746573743432353800a22108746573743432353900a32108746573" + "743432363000a42108746573743432363100a52108746573743432363200a62108746573743432363300a721087465" + "73743432363400a82108746573743432363500a92108746573743432363600aa2108746573743432363700ab210874" + "6573743432363800ac2108746573743432363900ad2108746573743432373000ae2108746573743432373100af2108" + "746573743432373200b02108746573743432373300b12108746573743432373400b22108746573743432373500b321" + "08746573743432373600b42108746573743432373700b52108746573743432373800b62108746573743432373900b7" + "2108746573743432383000b82108746573743432383100b92108746573743432383200ba2108746573743432383300" + "bb2108746573743432383400bc2108746573743432383500bd2108746573743432383600be21087465737434323837" + "00bf2108746573743432383800c02108746573743432383900c12108746573743432393000c2210874657374343239" + "3100c32108746573743432393200c42108746573743432393300c52108746573743432393400c62108746573743432" + "393500c72108746573743432393600c82108746573743432393700c92108746573743432393800ca21087465737434" + "32393900cb2108746573743433303000cc2108746573743433303100cd2108746573743433303200ce210874657374" + "3433303300cf2108746573743433303400d02108746573743433303500d12108746573743433303600d22108746573" + "743433303700d32108746573743433303800d42108746573743433303900d52108746573743433313000d621087465" + "73743433313100d72108746573743433313200d82108746573743433313300d92108746573743433313400da210874" + "6573743433313500db2108746573743433313600dc2108746573743433313700dd2108746573743433313800de2108" + "746573743433313900df2108746573743433323000e02108746573743433323100e12108746573743433323200e221" + "08746573743433323300e32108746573743433323400e42108746573743433323500e52108746573743433323600e6" + "2108746573743433323700e72108746573743433323800e82108746573743433323900e92108746573743433333000" + "ea2108746573743433333100eb2108746573743433333200ec2108746573743433333300ed21087465737434333334" + "00ee2108746573743433333500ef2108746573743433333600f02108746573743433333700f1210874657374343333" + "3800f22108746573743433333900f32108746573743433343000f42108746573743433343100f52108746573743433" + "343200f62108746573743433343300f72108746573743433343400f82108746573743433343500f921087465737434" + "33343600fa2108746573743433343700fb2108746573743433343800fc2108746573743433343900fd210874657374" + "3433353000fe2108746573743433353100ff2108746573743433353200802208746573743433353300812208746573" + "7434333534008222087465737434333535008322087465737434333536008422087465737434333537008522087465" + "7374343335380086220874657374343335390087220874657374343336300088220874657374343336310089220874" + "65737434333632008a22087465737434333633008b22087465737434333634008c22087465737434333635008d2208" + "7465737434333636008e22087465737434333637008f22087465737434333638009022087465737434333639009122" + "0874657374343337300092220874657374343337310093220874657374343337320094220874657374343337330095" + "2208746573743433373400962208746573743433373500972208746573743433373600982208746573743433373700" + "9922087465737434333738009a22087465737434333739009b22087465737434333830009c22087465737434333831" + "009d22087465737434333832009e22087465737434333833009f2208746573743433383400a0220874657374343338" + "3500a12208746573743433383600a22208746573743433383700a32208746573743433383800a42208746573743433" + "383900a52208746573743433393000a62208746573743433393100a72208746573743433393200a822087465737434" + "33393300a92208746573743433393400aa2208746573743433393500ab2208746573743433393600ac220874657374" + "3433393700ad2208746573743433393800ae2208746573743433393900af2208746573743434303000b02208746573" + "743434303100b12208746573743434303200b22208746573743434303300b32208746573743434303400b422087465" + "73743434303500b52208746573743434303600b62208746573743434303700b72208746573743434303800b8220874" + "6573743434303900b92208746573743434313000ba2208746573743434313100bb2208746573743434313200bc2208" + "746573743434313300bd2208746573743434313400be2208746573743434313500bf2208746573743434313600c022" + "08746573743434313700c12208746573743434313800c22208746573743434313900c32208746573743434323000c4" + "2208746573743434323100c52208746573743434323200c62208746573743434323300c72208746573743434323400" + "c82208746573743434323500c92208746573743434323600ca2208746573743434323700cb22087465737434343238" + "00cc2208746573743434323900cd2208746573743434333000ce2208746573743434333100cf220874657374343433" + "3200d02208746573743434333300d12208746573743434333400d22208746573743434333500d32208746573743434" + "333600d42208746573743434333700d52208746573743434333800d62208746573743434333900d722087465737434" + "34343000d82208746573743434343100d92208746573743434343200da2208746573743434343300db220874657374" + "3434343400dc2208746573743434343500dd2208746573743434343600de2208746573743434343700df2208746573" + "743434343800e02208746573743434343900e12208746573743434353000e22208746573743434353100e322087465" + "73743434353200e42208746573743434353300e52208746573743434353400e62208746573743434353500e7220874" + "6573743434353600e82208746573743434353700e92208746573743434353800ea2208746573743434353900eb2208" + "746573743434363000ec2208746573743434363100ed2208746573743434363200ee2208746573743434363300ef22" + "08746573743434363400f02208746573743434363500f12208746573743434363600f22208746573743434363700f3" + "2208746573743434363800f42208746573743434363900f52208746573743434373000f62208746573743434373100" + "f72208746573743434373200f82208746573743434373300f92208746573743434373400fa22087465737434343735" + "00fb2208746573743434373600fc2208746573743434373700fd2208746573743434373800fe220874657374343437" + "3900ff2208746573743434383000802308746573743434383100812308746573743434383200822308746573743434" + "3833008323087465737434343834008423087465737434343835008523087465737434343836008623087465737434" + "343837008723087465737434343838008823087465737434343839008923087465737434343930008a230874657374" + "34343931008b23087465737434343932008c23087465737434343933008d23087465737434343934008e2308746573" + "7434343935008f23087465737434343936009023087465737434343937009123087465737434343938009223087465" + "7374343439390093230874657374343530300094230874657374343530310095230874657374343530320096230874" + "65737434353033009723087465737434353034009823087465737434353035009923087465737434353036009a2308" + "7465737434353037009b23087465737434353038009c23087465737434353039009d23087465737434353130009e23" + "087465737434353131009f2308746573743435313200a02308746573743435313300a12308746573743435313400a2" + "2308746573743435313500a32308746573743435313600a42308746573743435313700a52308746573743435313800" + "a62308746573743435313900a72308746573743435323000a82308746573743435323100a923087465737434353232" + "00aa2308746573743435323300ab2308746573743435323400ac2308746573743435323500ad230874657374343532" + "3600ae2308746573743435323700af2308746573743435323800b02308746573743435323900b12308746573743435" + "333000b22308746573743435333100b32308746573743435333200b42308746573743435333300b523087465737434" + "35333400b62308746573743435333500b72308746573743435333600b82308746573743435333700b9230874657374" + "3435333800ba2308746573743435333900bb2308746573743435343000bc2308746573743435343100bd2308746573" + "743435343200be2308746573743435343300bf2308746573743435343400c02308746573743435343500c123087465" + "73743435343600c22308746573743435343700c32308746573743435343800c42308746573743435343900c5230874" + "6573743435353000c62308746573743435353100c72308746573743435353200c82308746573743435353300c92308" + "746573743435353400ca2308746573743435353500cb2308746573743435353600cc2308746573743435353700cd23" + "08746573743435353800ce2308746573743435353900cf2308746573743435363000d02308746573743435363100d1" + "2308746573743435363200d22308746573743435363300d32308746573743435363400d42308746573743435363500" + "d52308746573743435363600d62308746573743435363700d72308746573743435363800d823087465737434353639" + "00d92308746573743435373000da2308746573743435373100db2308746573743435373200dc230874657374343537" + "3300dd2308746573743435373400de2308746573743435373500df2308746573743435373600e02308746573743435" + "373700e12308746573743435373800e22308746573743435373900e32308746573743435383000e423087465737434" + "35383100e52308746573743435383200e62308746573743435383300e72308746573743435383400e8230874657374" + "3435383500e92308746573743435383600ea2308746573743435383700eb2308746573743435383800ec2308746573" + "743435383900ed2308746573743435393000ee2308746573743435393100ef2308746573743435393200f023087465" + "73743435393300f12308746573743435393400f22308746573743435393500f32308746573743435393600f4230874" + "6573743435393700f52308746573743435393800f62308746573743435393900f72308746573743436303000f82308" + "746573743436303100f92308746573743436303200fa2308746573743436303300fb2308746573743436303400fc23" + "08746573743436303500fd2308746573743436303600fe2308746573743436303700ff230874657374343630380080" + "2408746573743436303900812408746573743436313000822408746573743436313100832408746573743436313200" + "8424087465737434363133008524087465737434363134008624087465737434363135008724087465737434363136" + "008824087465737434363137008924087465737434363138008a24087465737434363139008b240874657374343632" + "30008c24087465737434363231008d24087465737434363232008e24087465737434363233008f2408746573743436" + "3234009024087465737434363235009124087465737434363236009224087465737434363237009324087465737434" + "3632380094240874657374343632390095240874657374343633300096240874657374343633310097240874657374" + "34363332009824087465737434363333009924087465737434363334009a24087465737434363335009b2408746573" + "7434363336009c24087465737434363337009d24087465737434363338009e24087465737434363339009f24087465" + "73743436343000a02408746573743436343100a12408746573743436343200a22408746573743436343300a3240874" + "6573743436343400a42408746573743436343500a52408746573743436343600a62408746573743436343700a72408" + "746573743436343800a82408746573743436343900a92408746573743436353000aa2408746573743436353100ab24" + "08746573743436353200ac2408746573743436353300ad2408746573743436353400ae2408746573743436353500af" + "2408746573743436353600b02408746573743436353700b12408746573743436353800b22408746573743436353900" + "b32408746573743436363000b42408746573743436363100b52408746573743436363200b624087465737434363633" + "00b72408746573743436363400b82408746573743436363500b92408746573743436363600ba240874657374343636" + "3700bb2408746573743436363800bc2408746573743436363900bd2408746573743436373000be2408746573743436" + "373100bf2408746573743436373200c02408746573743436373300c12408746573743436373400c224087465737434" + "36373500c32408746573743436373600c42408746573743436373700c52408746573743436373800c6240874657374" + "3436373900c72408746573743436383000c82408746573743436383100c92408746573743436383200ca2408746573" + "743436383300cb2408746573743436383400cc2408746573743436383500cd2408746573743436383600ce24087465" + "73743436383700cf2408746573743436383800d02408746573743436383900d12408746573743436393000d2240874" + "6573743436393100d32408746573743436393200d42408746573743436393300d52408746573743436393400d62408" + "746573743436393500d72408746573743436393600d82408746573743436393700d92408746573743436393800da24" + "08746573743436393900db2408746573743437303000dc2408746573743437303100dd2408746573743437303200de" + "2408746573743437303300df2408746573743437303400e02408746573743437303500e12408746573743437303600" + "e22408746573743437303700e32408746573743437303800e42408746573743437303900e524087465737434373130" + "00e62408746573743437313100e72408746573743437313200e82408746573743437313300e9240874657374343731" + "3400ea2408746573743437313500eb2408746573743437313600ec2408746573743437313700ed2408746573743437" + "313800ee2408746573743437313900ef2408746573743437323000f02408746573743437323100f124087465737434" + "37323200f22408746573743437323300f32408746573743437323400f42408746573743437323500f5240874657374" + "3437323600f62408746573743437323700f72408746573743437323800f82408746573743437323900f92408746573" + "743437333000fa2408746573743437333100fb2408746573743437333200fc2408746573743437333300fd24087465" + "73743437333400fe2408746573743437333500ff240874657374343733360080250874657374343733370081250874" + "6573743437333800822508746573743437333900832508746573743437343000842508746573743437343100852508" + "7465737434373432008625087465737434373433008725087465737434373434008825087465737434373435008925" + "087465737434373436008a25087465737434373437008b25087465737434373438008c25087465737434373439008d" + "25087465737434373530008e25087465737434373531008f2508746573743437353200902508746573743437353300" + "9125087465737434373534009225087465737434373535009325087465737434373536009425087465737434373537" + "0095250874657374343735380096250874657374343735390097250874657374343736300098250874657374343736" + "31009925087465737434373632009a25087465737434373633009b25087465737434373634009c2508746573743437" + "3635009d25087465737434373636009e25087465737434373637009f2508746573743437363800a025087465737434" + "37363900a12508746573743437373000a22508746573743437373100a32508746573743437373200a4250874657374" + "3437373300a52508746573743437373400a62508746573743437373500a72508746573743437373600a82508746573" + "743437373700a92508746573743437373800aa2508746573743437373900ab2508746573743437383000ac25087465" + "73743437383100ad2508746573743437383200ae2508746573743437383300af2508746573743437383400b0250874" + "6573743437383500b12508746573743437383600b22508746573743437383700b32508746573743437383800b42508" + "746573743437383900b52508746573743437393000b62508746573743437393100b72508746573743437393200b825" + "08746573743437393300b92508746573743437393400ba2508746573743437393500bb2508746573743437393600bc" + "2508746573743437393700bd2508746573743437393800be2508746573743437393900bf2508746573743438303000" + "c02508746573743438303100c12508746573743438303200c22508746573743438303300c325087465737434383034" + "00c42508746573743438303500c52508746573743438303600c62508746573743438303700c7250874657374343830" + "3800c82508746573743438303900c92508746573743438313000ca2508746573743438313100cb2508746573743438" + "313200cc2508746573743438313300cd2508746573743438313400ce2508746573743438313500cf25087465737434" + "38313600d02508746573743438313700d12508746573743438313800d22508746573743438313900d3250874657374" + "3438323000d42508746573743438323100d52508746573743438323200d62508746573743438323300d72508746573" + "743438323400d82508746573743438323500d92508746573743438323600da2508746573743438323700db25087465" + "73743438323800dc2508746573743438323900dd2508746573743438333000de2508746573743438333100df250874" + "6573743438333200e02508746573743438333300e12508746573743438333400e22508746573743438333500e32508" + "746573743438333600e42508746573743438333700e52508746573743438333800e62508746573743438333900e725" + "08746573743438343000e82508746573743438343100e92508746573743438343200ea2508746573743438343300eb" + "2508746573743438343400ec2508746573743438343500ed2508746573743438343600ee2508746573743438343700" + "ef2508746573743438343800f02508746573743438343900f12508746573743438353000f225087465737434383531" + "00f32508746573743438353200f42508746573743438353300f52508746573743438353400f6250874657374343835" + "3500f72508746573743438353600f82508746573743438353700f92508746573743438353800fa2508746573743438" + "353900fb2508746573743438363000fc2508746573743438363100fd2508746573743438363200fe25087465737434" + "38363300ff250874657374343836340080260874657374343836350081260874657374343836360082260874657374" + "3438363700832608746573743438363800842608746573743438363900852608746573743438373000862608746573" + "7434383731008726087465737434383732008826087465737434383733008926087465737434383734008a26087465" + "737434383735008b26087465737434383736008c26087465737434383737008d26087465737434383738008e260874" + "65737434383739008f2608746573743438383000902608746573743438383100912608746573743438383200922608" + "7465737434383833009326087465737434383834009426087465737434383835009526087465737434383836009626" + "087465737434383837009726087465737434383838009826087465737434383839009926087465737434383930009a" + "26087465737434383931009b26087465737434383932009c26087465737434383933009d2608746573743438393400" + "9e26087465737434383935009f2608746573743438393600a02608746573743438393700a126087465737434383938" + "00a22608746573743438393900a32608746573743439303000a42608746573743439303100a5260874657374343930" + "3200a62608746573743439303300a72608746573743439303400a82608746573743439303500a92608746573743439" + "303600aa2608746573743439303700ab2608746573743439303800ac2608746573743439303900ad26087465737434" + "39313000ae2608746573743439313100af2608746573743439313200b02608746573743439313300b1260874657374" + "3439313400b22608746573743439313500b32608746573743439313600b42608746573743439313700b52608746573" + "743439313800b62608746573743439313900b72608746573743439323000b82608746573743439323100b926087465" + "73743439323200ba2608746573743439323300bb2608746573743439323400bc2608746573743439323500bd260874" + "6573743439323600be2608746573743439323700bf2608746573743439323800c02608746573743439323900c12608" + "746573743439333000c22608746573743439333100c32608746573743439333200c42608746573743439333300c526" + "08746573743439333400c62608746573743439333500c72608746573743439333600c82608746573743439333700c9" + "2608746573743439333800ca2608746573743439333900cb2608746573743439343000cc2608746573743439343100" + "cd2608746573743439343200ce2608746573743439343300cf2608746573743439343400d026087465737434393435" + "00d12608746573743439343600d22608746573743439343700d32608746573743439343800d4260874657374343934" + "3900d52608746573743439353000d62608746573743439353100d72608746573743439353200d82608746573743439" + "353300d92608746573743439353400da2608746573743439353500db2608746573743439353600dc26087465737434" + "39353700dd2608746573743439353800de2608746573743439353900df2608746573743439363000e0260874657374" + "3439363100e12608746573743439363200e22608746573743439363300e32608746573743439363400e42608746573" + "743439363500e52608746573743439363600e62608746573743439363700e72608746573743439363800e826087465" + "73743439363900e92608746573743439373000ea2608746573743439373100eb2608746573743439373200ec260874" + "6573743439373300ed2608746573743439373400ee2608746573743439373500ef2608746573743439373600f02608" + "746573743439373700f12608746573743439373800f22608746573743439373900f32608746573743439383000f426" + "08746573743439383100f52608746573743439383200f62608746573743439383300f72608746573743439383400f8" + "2608746573743439383500f92608746573743439383600fa2608746573743439383700fb2608746573743439383800" + "fc2608746573743439383900fd2608746573743439393000fe2608746573743439393100ff26087465737434393932" + "0080270874657374343939330081270874657374343939340082270874657374343939350083270874657374343939" + "360084270874657374343939370085270874657374343939380086270874657374343939390087270ac2b802882707" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020" + "016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07002000" + "20016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020" + "0020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700" + "200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b07" + "00200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b" + "0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a" + "0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b0700200020016a0b070020002001" + "6a0b"; diff --git a/src/test/app/wasm_fixtures/fixture_locals_10k.cpp b/src/test/app/wasm_fixtures/fixture_locals_10k.cpp new file mode 100644 index 00000000000..fe3b11a7a8d --- /dev/null +++ b/src/test/app/wasm_fixtures/fixture_locals_10k.cpp @@ -0,0 +1,2126 @@ +// TODO: consider moving these to separate files (and figure out the build) + +#include + +extern std::string const locals10kHex = + "0061736d0100000001070160027f7f017f03020100070801047465737400000a9b8a0601978a06018e4e7f20002001" + "6a2102200120026a2103200220036a2104200320046a2105200420056a2106200520066a2107200620076a21082007" + "20086a2109200820096a210a2009200a6a210b200a200b6a210c200b200c6a210d200c200d6a210e200d200e6a210f" + "200e200f6a2110200f20106a2111201020116a2112201120126a2113201220136a2114201320146a2115201420156a" + "2116201520166a2117201620176a2118201720186a2119201820196a211a2019201a6a211b201a201b6a211c201b20" + "1c6a211d201c201d6a211e201d201e6a211f201e201f6a2120201f20206a2121202020216a2122202120226a212320" + "2220236a2124202320246a2125202420256a2126202520266a2127202620276a2128202720286a2129202820296a21" + "2a2029202a6a212b202a202b6a212c202b202c6a212d202c202d6a212e202d202e6a212f202e202f6a2130202f2030" + "6a2131203020316a2132203120326a2133203220336a2134203320346a2135203420356a2136203520366a21372036" + "20376a2138203720386a2139203820396a213a2039203a6a213b203a203b6a213c203b203c6a213d203c203d6a213e" + "203d203e6a213f203e203f6a2140203f20406a2141204020416a2142204120426a2143204220436a2144204320446a" + "2145204420456a2146204520466a2147204620476a2148204720486a2149204820496a214a2049204a6a214b204a20" + "4b6a214c204b204c6a214d204c204d6a214e204d204e6a214f204e204f6a2150204f20506a2151205020516a215220" + "5120526a2153205220536a2154205320546a2155205420556a2156205520566a2157205620576a2158205720586a21" + "59205820596a215a2059205a6a215b205a205b6a215c205b205c6a215d205c205d6a215e205d205e6a215f205e205f" + "6a2160205f20606a2161206020616a2162206120626a2163206220636a2164206320646a2165206420656a21662065" + "20666a2167206620676a2168206720686a2169206820696a216a2069206a6a216b206a206b6a216c206b206c6a216d" + "206c206d6a216e206d206e6a216f206e206f6a2170206f20706a2171207020716a2172207120726a2173207220736a" + "2174207320746a2175207420756a2176207520766a2177207620776a2178207720786a2179207820796a217a207920" + "7a6a217b207a207b6a217c207b207c6a217d207c207d6a217e207d207e6a217f207e207f6a218001207f2080016a21" + "81012080012081016a2182012081012082016a2183012082012083016a2184012083012084016a2185012084012085" + "016a2186012085012086016a2187012086012087016a2188012087012088016a2189012088012089016a218a012089" + "01208a016a218b01208a01208b016a218c01208b01208c016a218d01208c01208d016a218e01208d01208e016a218f" + "01208e01208f016a219001208f012090016a2191012090012091016a2192012091012092016a219301209201209301" + "6a2194012093012094016a2195012094012095016a2196012095012096016a2197012096012097016a219801209701" + "2098016a2199012098012099016a219a01209901209a016a219b01209a01209b016a219c01209b01209c016a219d01" + "209c01209d016a219e01209d01209e016a219f01209e01209f016a21a001209f0120a0016a21a10120a00120a1016a" + "21a20120a10120a2016a21a30120a20120a3016a21a40120a30120a4016a21a50120a40120a5016a21a60120a50120" + "a6016a21a70120a60120a7016a21a80120a70120a8016a21a90120a80120a9016a21aa0120a90120aa016a21ab0120" + "aa0120ab016a21ac0120ab0120ac016a21ad0120ac0120ad016a21ae0120ad0120ae016a21af0120ae0120af016a21" + "b00120af0120b0016a21b10120b00120b1016a21b20120b10120b2016a21b30120b20120b3016a21b40120b30120b4" + "016a21b50120b40120b5016a21b60120b50120b6016a21b70120b60120b7016a21b80120b70120b8016a21b90120b8" + "0120b9016a21ba0120b90120ba016a21bb0120ba0120bb016a21bc0120bb0120bc016a21bd0120bc0120bd016a21be" + "0120bd0120be016a21bf0120be0120bf016a21c00120bf0120c0016a21c10120c00120c1016a21c20120c10120c201" + "6a21c30120c20120c3016a21c40120c30120c4016a21c50120c40120c5016a21c60120c50120c6016a21c70120c601" + "20c7016a21c80120c70120c8016a21c90120c80120c9016a21ca0120c90120ca016a21cb0120ca0120cb016a21cc01" + "20cb0120cc016a21cd0120cc0120cd016a21ce0120cd0120ce016a21cf0120ce0120cf016a21d00120cf0120d0016a" + "21d10120d00120d1016a21d20120d10120d2016a21d30120d20120d3016a21d40120d30120d4016a21d50120d40120" + "d5016a21d60120d50120d6016a21d70120d60120d7016a21d80120d70120d8016a21d90120d80120d9016a21da0120" + "d90120da016a21db0120da0120db016a21dc0120db0120dc016a21dd0120dc0120dd016a21de0120dd0120de016a21" + "df0120de0120df016a21e00120df0120e0016a21e10120e00120e1016a21e20120e10120e2016a21e30120e20120e3" + "016a21e40120e30120e4016a21e50120e40120e5016a21e60120e50120e6016a21e70120e60120e7016a21e80120e7" + "0120e8016a21e90120e80120e9016a21ea0120e90120ea016a21eb0120ea0120eb016a21ec0120eb0120ec016a21ed" + "0120ec0120ed016a21ee0120ed0120ee016a21ef0120ee0120ef016a21f00120ef0120f0016a21f10120f00120f101" + "6a21f20120f10120f2016a21f30120f20120f3016a21f40120f30120f4016a21f50120f40120f5016a21f60120f501" + "20f6016a21f70120f60120f7016a21f80120f70120f8016a21f90120f80120f9016a21fa0120f90120fa016a21fb01" + "20fa0120fb016a21fc0120fb0120fc016a21fd0120fc0120fd016a21fe0120fd0120fe016a21ff0120fe0120ff016a" + "21800220ff012080026a2181022080022081026a2182022081022082026a2183022082022083026a21840220830220" + "84026a2185022084022085026a2186022085022086026a2187022086022087026a2188022087022088026a21890220" + "88022089026a218a02208902208a026a218b02208a02208b026a218c02208b02208c026a218d02208c02208d026a21" + "8e02208d02208e026a218f02208e02208f026a219002208f022090026a2191022090022091026a2192022091022092" + "026a2193022092022093026a2194022093022094026a2195022094022095026a2196022095022096026a2197022096" + "022097026a2198022097022098026a2199022098022099026a219a02209902209a026a219b02209a02209b026a219c" + "02209b02209c026a219d02209c02209d026a219e02209d02209e026a219f02209e02209f026a21a002209f0220a002" + "6a21a10220a00220a1026a21a20220a10220a2026a21a30220a20220a3026a21a40220a30220a4026a21a50220a402" + "20a5026a21a60220a50220a6026a21a70220a60220a7026a21a80220a70220a8026a21a90220a80220a9026a21aa02" + "20a90220aa026a21ab0220aa0220ab026a21ac0220ab0220ac026a21ad0220ac0220ad026a21ae0220ad0220ae026a" + "21af0220ae0220af026a21b00220af0220b0026a21b10220b00220b1026a21b20220b10220b2026a21b30220b20220" + "b3026a21b40220b30220b4026a21b50220b40220b5026a21b60220b50220b6026a21b70220b60220b7026a21b80220" + "b70220b8026a21b90220b80220b9026a21ba0220b90220ba026a21bb0220ba0220bb026a21bc0220bb0220bc026a21" + "bd0220bc0220bd026a21be0220bd0220be026a21bf0220be0220bf026a21c00220bf0220c0026a21c10220c00220c1" + "026a21c20220c10220c2026a21c30220c20220c3026a21c40220c30220c4026a21c50220c40220c5026a21c60220c5" + "0220c6026a21c70220c60220c7026a21c80220c70220c8026a21c90220c80220c9026a21ca0220c90220ca026a21cb" + "0220ca0220cb026a21cc0220cb0220cc026a21cd0220cc0220cd026a21ce0220cd0220ce026a21cf0220ce0220cf02" + "6a21d00220cf0220d0026a21d10220d00220d1026a21d20220d10220d2026a21d30220d20220d3026a21d40220d302" + "20d4026a21d50220d40220d5026a21d60220d50220d6026a21d70220d60220d7026a21d80220d70220d8026a21d902" + "20d80220d9026a21da0220d90220da026a21db0220da0220db026a21dc0220db0220dc026a21dd0220dc0220dd026a" + "21de0220dd0220de026a21df0220de0220df026a21e00220df0220e0026a21e10220e00220e1026a21e20220e10220" + "e2026a21e30220e20220e3026a21e40220e30220e4026a21e50220e40220e5026a21e60220e50220e6026a21e70220" + "e60220e7026a21e80220e70220e8026a21e90220e80220e9026a21ea0220e90220ea026a21eb0220ea0220eb026a21" + "ec0220eb0220ec026a21ed0220ec0220ed026a21ee0220ed0220ee026a21ef0220ee0220ef026a21f00220ef0220f0" + "026a21f10220f00220f1026a21f20220f10220f2026a21f30220f20220f3026a21f40220f30220f4026a21f50220f4" + "0220f5026a21f60220f50220f6026a21f70220f60220f7026a21f80220f70220f8026a21f90220f80220f9026a21fa" + "0220f90220fa026a21fb0220fa0220fb026a21fc0220fb0220fc026a21fd0220fc0220fd026a21fe0220fd0220fe02" + "6a21ff0220fe0220ff026a21800320ff022080036a2181032080032081036a2182032081032082036a218303208203" + "2083036a2184032083032084036a2185032084032085036a2186032085032086036a2187032086032087036a218803" + "2087032088036a2189032088032089036a218a03208903208a036a218b03208a03208b036a218c03208b03208c036a" + "218d03208c03208d036a218e03208d03208e036a218f03208e03208f036a219003208f032090036a21910320900320" + "91036a2192032091032092036a2193032092032093036a2194032093032094036a2195032094032095036a21960320" + "95032096036a2197032096032097036a2198032097032098036a2199032098032099036a219a03209903209a036a21" + "9b03209a03209b036a219c03209b03209c036a219d03209c03209d036a219e03209d03209e036a219f03209e03209f" + "036a21a003209f0320a0036a21a10320a00320a1036a21a20320a10320a2036a21a30320a20320a3036a21a40320a3" + "0320a4036a21a50320a40320a5036a21a60320a50320a6036a21a70320a60320a7036a21a80320a70320a8036a21a9" + "0320a80320a9036a21aa0320a90320aa036a21ab0320aa0320ab036a21ac0320ab0320ac036a21ad0320ac0320ad03" + "6a21ae0320ad0320ae036a21af0320ae0320af036a21b00320af0320b0036a21b10320b00320b1036a21b20320b103" + "20b2036a21b30320b20320b3036a21b40320b30320b4036a21b50320b40320b5036a21b60320b50320b6036a21b703" + "20b60320b7036a21b80320b70320b8036a21b90320b80320b9036a21ba0320b90320ba036a21bb0320ba0320bb036a" + "21bc0320bb0320bc036a21bd0320bc0320bd036a21be0320bd0320be036a21bf0320be0320bf036a21c00320bf0320" + "c0036a21c10320c00320c1036a21c20320c10320c2036a21c30320c20320c3036a21c40320c30320c4036a21c50320" + "c40320c5036a21c60320c50320c6036a21c70320c60320c7036a21c80320c70320c8036a21c90320c80320c9036a21" + "ca0320c90320ca036a21cb0320ca0320cb036a21cc0320cb0320cc036a21cd0320cc0320cd036a21ce0320cd0320ce" + "036a21cf0320ce0320cf036a21d00320cf0320d0036a21d10320d00320d1036a21d20320d10320d2036a21d30320d2" + "0320d3036a21d40320d30320d4036a21d50320d40320d5036a21d60320d50320d6036a21d70320d60320d7036a21d8" + "0320d70320d8036a21d90320d80320d9036a21da0320d90320da036a21db0320da0320db036a21dc0320db0320dc03" + "6a21dd0320dc0320dd036a21de0320dd0320de036a21df0320de0320df036a21e00320df0320e0036a21e10320e003" + "20e1036a21e20320e10320e2036a21e30320e20320e3036a21e40320e30320e4036a21e50320e40320e5036a21e603" + "20e50320e6036a21e70320e60320e7036a21e80320e70320e8036a21e90320e80320e9036a21ea0320e90320ea036a" + "21eb0320ea0320eb036a21ec0320eb0320ec036a21ed0320ec0320ed036a21ee0320ed0320ee036a21ef0320ee0320" + "ef036a21f00320ef0320f0036a21f10320f00320f1036a21f20320f10320f2036a21f30320f20320f3036a21f40320" + "f30320f4036a21f50320f40320f5036a21f60320f50320f6036a21f70320f60320f7036a21f80320f70320f8036a21" + "f90320f80320f9036a21fa0320f90320fa036a21fb0320fa0320fb036a21fc0320fb0320fc036a21fd0320fc0320fd" + "036a21fe0320fd0320fe036a21ff0320fe0320ff036a21800420ff032080046a2181042080042081046a2182042081" + "042082046a2183042082042083046a2184042083042084046a2185042084042085046a2186042085042086046a2187" + "042086042087046a2188042087042088046a2189042088042089046a218a04208904208a046a218b04208a04208b04" + "6a218c04208b04208c046a218d04208c04208d046a218e04208d04208e046a218f04208e04208f046a219004208f04" + "2090046a2191042090042091046a2192042091042092046a2193042092042093046a2194042093042094046a219504" + "2094042095046a2196042095042096046a2197042096042097046a2198042097042098046a2199042098042099046a" + "219a04209904209a046a219b04209a04209b046a219c04209b04209c046a219d04209c04209d046a219e04209d0420" + "9e046a219f04209e04209f046a21a004209f0420a0046a21a10420a00420a1046a21a20420a10420a2046a21a30420" + "a20420a3046a21a40420a30420a4046a21a50420a40420a5046a21a60420a50420a6046a21a70420a60420a7046a21" + "a80420a70420a8046a21a90420a80420a9046a21aa0420a90420aa046a21ab0420aa0420ab046a21ac0420ab0420ac" + "046a21ad0420ac0420ad046a21ae0420ad0420ae046a21af0420ae0420af046a21b00420af0420b0046a21b10420b0" + "0420b1046a21b20420b10420b2046a21b30420b20420b3046a21b40420b30420b4046a21b50420b40420b5046a21b6" + "0420b50420b6046a21b70420b60420b7046a21b80420b70420b8046a21b90420b80420b9046a21ba0420b90420ba04" + "6a21bb0420ba0420bb046a21bc0420bb0420bc046a21bd0420bc0420bd046a21be0420bd0420be046a21bf0420be04" + "20bf046a21c00420bf0420c0046a21c10420c00420c1046a21c20420c10420c2046a21c30420c20420c3046a21c404" + "20c30420c4046a21c50420c40420c5046a21c60420c50420c6046a21c70420c60420c7046a21c80420c70420c8046a" + "21c90420c80420c9046a21ca0420c90420ca046a21cb0420ca0420cb046a21cc0420cb0420cc046a21cd0420cc0420" + "cd046a21ce0420cd0420ce046a21cf0420ce0420cf046a21d00420cf0420d0046a21d10420d00420d1046a21d20420" + "d10420d2046a21d30420d20420d3046a21d40420d30420d4046a21d50420d40420d5046a21d60420d50420d6046a21" + "d70420d60420d7046a21d80420d70420d8046a21d90420d80420d9046a21da0420d90420da046a21db0420da0420db" + "046a21dc0420db0420dc046a21dd0420dc0420dd046a21de0420dd0420de046a21df0420de0420df046a21e00420df" + "0420e0046a21e10420e00420e1046a21e20420e10420e2046a21e30420e20420e3046a21e40420e30420e4046a21e5" + "0420e40420e5046a21e60420e50420e6046a21e70420e60420e7046a21e80420e70420e8046a21e90420e80420e904" + "6a21ea0420e90420ea046a21eb0420ea0420eb046a21ec0420eb0420ec046a21ed0420ec0420ed046a21ee0420ed04" + "20ee046a21ef0420ee0420ef046a21f00420ef0420f0046a21f10420f00420f1046a21f20420f10420f2046a21f304" + "20f20420f3046a21f40420f30420f4046a21f50420f40420f5046a21f60420f50420f6046a21f70420f60420f7046a" + "21f80420f70420f8046a21f90420f80420f9046a21fa0420f90420fa046a21fb0420fa0420fb046a21fc0420fb0420" + "fc046a21fd0420fc0420fd046a21fe0420fd0420fe046a21ff0420fe0420ff046a21800520ff042080056a21810520" + "80052081056a2182052081052082056a2183052082052083056a2184052083052084056a2185052084052085056a21" + "86052085052086056a2187052086052087056a2188052087052088056a2189052088052089056a218a05208905208a" + "056a218b05208a05208b056a218c05208b05208c056a218d05208c05208d056a218e05208d05208e056a218f05208e" + "05208f056a219005208f052090056a2191052090052091056a2192052091052092056a2193052092052093056a2194" + "052093052094056a2195052094052095056a2196052095052096056a2197052096052097056a219805209705209805" + "6a2199052098052099056a219a05209905209a056a219b05209a05209b056a219c05209b05209c056a219d05209c05" + "209d056a219e05209d05209e056a219f05209e05209f056a21a005209f0520a0056a21a10520a00520a1056a21a205" + "20a10520a2056a21a30520a20520a3056a21a40520a30520a4056a21a50520a40520a5056a21a60520a50520a6056a" + "21a70520a60520a7056a21a80520a70520a8056a21a90520a80520a9056a21aa0520a90520aa056a21ab0520aa0520" + "ab056a21ac0520ab0520ac056a21ad0520ac0520ad056a21ae0520ad0520ae056a21af0520ae0520af056a21b00520" + "af0520b0056a21b10520b00520b1056a21b20520b10520b2056a21b30520b20520b3056a21b40520b30520b4056a21" + "b50520b40520b5056a21b60520b50520b6056a21b70520b60520b7056a21b80520b70520b8056a21b90520b80520b9" + "056a21ba0520b90520ba056a21bb0520ba0520bb056a21bc0520bb0520bc056a21bd0520bc0520bd056a21be0520bd" + "0520be056a21bf0520be0520bf056a21c00520bf0520c0056a21c10520c00520c1056a21c20520c10520c2056a21c3" + "0520c20520c3056a21c40520c30520c4056a21c50520c40520c5056a21c60520c50520c6056a21c70520c60520c705" + "6a21c80520c70520c8056a21c90520c80520c9056a21ca0520c90520ca056a21cb0520ca0520cb056a21cc0520cb05" + "20cc056a21cd0520cc0520cd056a21ce0520cd0520ce056a21cf0520ce0520cf056a21d00520cf0520d0056a21d105" + "20d00520d1056a21d20520d10520d2056a21d30520d20520d3056a21d40520d30520d4056a21d50520d40520d5056a" + "21d60520d50520d6056a21d70520d60520d7056a21d80520d70520d8056a21d90520d80520d9056a21da0520d90520" + "da056a21db0520da0520db056a21dc0520db0520dc056a21dd0520dc0520dd056a21de0520dd0520de056a21df0520" + "de0520df056a21e00520df0520e0056a21e10520e00520e1056a21e20520e10520e2056a21e30520e20520e3056a21" + "e40520e30520e4056a21e50520e40520e5056a21e60520e50520e6056a21e70520e60520e7056a21e80520e70520e8" + "056a21e90520e80520e9056a21ea0520e90520ea056a21eb0520ea0520eb056a21ec0520eb0520ec056a21ed0520ec" + "0520ed056a21ee0520ed0520ee056a21ef0520ee0520ef056a21f00520ef0520f0056a21f10520f00520f1056a21f2" + "0520f10520f2056a21f30520f20520f3056a21f40520f30520f4056a21f50520f40520f5056a21f60520f50520f605" + "6a21f70520f60520f7056a21f80520f70520f8056a21f90520f80520f9056a21fa0520f90520fa056a21fb0520fa05" + "20fb056a21fc0520fb0520fc056a21fd0520fc0520fd056a21fe0520fd0520fe056a21ff0520fe0520ff056a218006" + "20ff052080066a2181062080062081066a2182062081062082066a2183062082062083066a2184062083062084066a" + "2185062084062085066a2186062085062086066a2187062086062087066a2188062087062088066a21890620880620" + "89066a218a06208906208a066a218b06208a06208b066a218c06208b06208c066a218d06208c06208d066a218e0620" + "8d06208e066a218f06208e06208f066a219006208f062090066a2191062090062091066a2192062091062092066a21" + "93062092062093066a2194062093062094066a2195062094062095066a2196062095062096066a2197062096062097" + "066a2198062097062098066a2199062098062099066a219a06209906209a066a219b06209a06209b066a219c06209b" + "06209c066a219d06209c06209d066a219e06209d06209e066a219f06209e06209f066a21a006209f0620a0066a21a1" + "0620a00620a1066a21a20620a10620a2066a21a30620a20620a3066a21a40620a30620a4066a21a50620a40620a506" + "6a21a60620a50620a6066a21a70620a60620a7066a21a80620a70620a8066a21a90620a80620a9066a21aa0620a906" + "20aa066a21ab0620aa0620ab066a21ac0620ab0620ac066a21ad0620ac0620ad066a21ae0620ad0620ae066a21af06" + "20ae0620af066a21b00620af0620b0066a21b10620b00620b1066a21b20620b10620b2066a21b30620b20620b3066a" + "21b40620b30620b4066a21b50620b40620b5066a21b60620b50620b6066a21b70620b60620b7066a21b80620b70620" + "b8066a21b90620b80620b9066a21ba0620b90620ba066a21bb0620ba0620bb066a21bc0620bb0620bc066a21bd0620" + "bc0620bd066a21be0620bd0620be066a21bf0620be0620bf066a21c00620bf0620c0066a21c10620c00620c1066a21" + "c20620c10620c2066a21c30620c20620c3066a21c40620c30620c4066a21c50620c40620c5066a21c60620c50620c6" + "066a21c70620c60620c7066a21c80620c70620c8066a21c90620c80620c9066a21ca0620c90620ca066a21cb0620ca" + "0620cb066a21cc0620cb0620cc066a21cd0620cc0620cd066a21ce0620cd0620ce066a21cf0620ce0620cf066a21d0" + "0620cf0620d0066a21d10620d00620d1066a21d20620d10620d2066a21d30620d20620d3066a21d40620d30620d406" + "6a21d50620d40620d5066a21d60620d50620d6066a21d70620d60620d7066a21d80620d70620d8066a21d90620d806" + "20d9066a21da0620d90620da066a21db0620da0620db066a21dc0620db0620dc066a21dd0620dc0620dd066a21de06" + "20dd0620de066a21df0620de0620df066a21e00620df0620e0066a21e10620e00620e1066a21e20620e10620e2066a" + "21e30620e20620e3066a21e40620e30620e4066a21e50620e40620e5066a21e60620e50620e6066a21e70620e60620" + "e7066a21e80620e70620e8066a21e90620e80620e9066a21ea0620e90620ea066a21eb0620ea0620eb066a21ec0620" + "eb0620ec066a21ed0620ec0620ed066a21ee0620ed0620ee066a21ef0620ee0620ef066a21f00620ef0620f0066a21" + "f10620f00620f1066a21f20620f10620f2066a21f30620f20620f3066a21f40620f30620f4066a21f50620f40620f5" + "066a21f60620f50620f6066a21f70620f60620f7066a21f80620f70620f8066a21f90620f80620f9066a21fa0620f9" + "0620fa066a21fb0620fa0620fb066a21fc0620fb0620fc066a21fd0620fc0620fd066a21fe0620fd0620fe066a21ff" + "0620fe0620ff066a21800720ff062080076a2181072080072081076a2182072081072082076a218307208207208307" + "6a2184072083072084076a2185072084072085076a2186072085072086076a2187072086072087076a218807208707" + "2088076a2189072088072089076a218a07208907208a076a218b07208a07208b076a218c07208b07208c076a218d07" + "208c07208d076a218e07208d07208e076a218f07208e07208f076a219007208f072090076a2191072090072091076a" + "2192072091072092076a2193072092072093076a2194072093072094076a2195072094072095076a21960720950720" + "96076a2197072096072097076a2198072097072098076a2199072098072099076a219a07209907209a076a219b0720" + "9a07209b076a219c07209b07209c076a219d07209c07209d076a219e07209d07209e076a219f07209e07209f076a21" + "a007209f0720a0076a21a10720a00720a1076a21a20720a10720a2076a21a30720a20720a3076a21a40720a30720a4" + "076a21a50720a40720a5076a21a60720a50720a6076a21a70720a60720a7076a21a80720a70720a8076a21a90720a8" + "0720a9076a21aa0720a90720aa076a21ab0720aa0720ab076a21ac0720ab0720ac076a21ad0720ac0720ad076a21ae" + "0720ad0720ae076a21af0720ae0720af076a21b00720af0720b0076a21b10720b00720b1076a21b20720b10720b207" + "6a21b30720b20720b3076a21b40720b30720b4076a21b50720b40720b5076a21b60720b50720b6076a21b70720b607" + "20b7076a21b80720b70720b8076a21b90720b80720b9076a21ba0720b90720ba076a21bb0720ba0720bb076a21bc07" + "20bb0720bc076a21bd0720bc0720bd076a21be0720bd0720be076a21bf0720be0720bf076a21c00720bf0720c0076a" + "21c10720c00720c1076a21c20720c10720c2076a21c30720c20720c3076a21c40720c30720c4076a21c50720c40720" + "c5076a21c60720c50720c6076a21c70720c60720c7076a21c80720c70720c8076a21c90720c80720c9076a21ca0720" + "c90720ca076a21cb0720ca0720cb076a21cc0720cb0720cc076a21cd0720cc0720cd076a21ce0720cd0720ce076a21" + "cf0720ce0720cf076a21d00720cf0720d0076a21d10720d00720d1076a21d20720d10720d2076a21d30720d20720d3" + "076a21d40720d30720d4076a21d50720d40720d5076a21d60720d50720d6076a21d70720d60720d7076a21d80720d7" + "0720d8076a21d90720d80720d9076a21da0720d90720da076a21db0720da0720db076a21dc0720db0720dc076a21dd" + "0720dc0720dd076a21de0720dd0720de076a21df0720de0720df076a21e00720df0720e0076a21e10720e00720e107" + "6a21e20720e10720e2076a21e30720e20720e3076a21e40720e30720e4076a21e50720e40720e5076a21e60720e507" + "20e6076a21e70720e60720e7076a21e80720e70720e8076a21e90720e80720e9076a21ea0720e90720ea076a21eb07" + "20ea0720eb076a21ec0720eb0720ec076a21ed0720ec0720ed076a21ee0720ed0720ee076a21ef0720ee0720ef076a" + "21f00720ef0720f0076a21f10720f00720f1076a21f20720f10720f2076a21f30720f20720f3076a21f40720f30720" + "f4076a21f50720f40720f5076a21f60720f50720f6076a21f70720f60720f7076a21f80720f70720f8076a21f90720" + "f80720f9076a21fa0720f90720fa076a21fb0720fa0720fb076a21fc0720fb0720fc076a21fd0720fc0720fd076a21" + "fe0720fd0720fe076a21ff0720fe0720ff076a21800820ff072080086a2181082080082081086a2182082081082082" + "086a2183082082082083086a2184082083082084086a2185082084082085086a2186082085082086086a2187082086" + "082087086a2188082087082088086a2189082088082089086a218a08208908208a086a218b08208a08208b086a218c" + "08208b08208c086a218d08208c08208d086a218e08208d08208e086a218f08208e08208f086a219008208f08209008" + "6a2191082090082091086a2192082091082092086a2193082092082093086a2194082093082094086a219508209408" + "2095086a2196082095082096086a2197082096082097086a2198082097082098086a2199082098082099086a219a08" + "209908209a086a219b08209a08209b086a219c08209b08209c086a219d08209c08209d086a219e08209d08209e086a" + "219f08209e08209f086a21a008209f0820a0086a21a10820a00820a1086a21a20820a10820a2086a21a30820a20820" + "a3086a21a40820a30820a4086a21a50820a40820a5086a21a60820a50820a6086a21a70820a60820a7086a21a80820" + "a70820a8086a21a90820a80820a9086a21aa0820a90820aa086a21ab0820aa0820ab086a21ac0820ab0820ac086a21" + "ad0820ac0820ad086a21ae0820ad0820ae086a21af0820ae0820af086a21b00820af0820b0086a21b10820b00820b1" + "086a21b20820b10820b2086a21b30820b20820b3086a21b40820b30820b4086a21b50820b40820b5086a21b60820b5" + "0820b6086a21b70820b60820b7086a21b80820b70820b8086a21b90820b80820b9086a21ba0820b90820ba086a21bb" + "0820ba0820bb086a21bc0820bb0820bc086a21bd0820bc0820bd086a21be0820bd0820be086a21bf0820be0820bf08" + "6a21c00820bf0820c0086a21c10820c00820c1086a21c20820c10820c2086a21c30820c20820c3086a21c40820c308" + "20c4086a21c50820c40820c5086a21c60820c50820c6086a21c70820c60820c7086a21c80820c70820c8086a21c908" + "20c80820c9086a21ca0820c90820ca086a21cb0820ca0820cb086a21cc0820cb0820cc086a21cd0820cc0820cd086a" + "21ce0820cd0820ce086a21cf0820ce0820cf086a21d00820cf0820d0086a21d10820d00820d1086a21d20820d10820" + "d2086a21d30820d20820d3086a21d40820d30820d4086a21d50820d40820d5086a21d60820d50820d6086a21d70820" + "d60820d7086a21d80820d70820d8086a21d90820d80820d9086a21da0820d90820da086a21db0820da0820db086a21" + "dc0820db0820dc086a21dd0820dc0820dd086a21de0820dd0820de086a21df0820de0820df086a21e00820df0820e0" + "086a21e10820e00820e1086a21e20820e10820e2086a21e30820e20820e3086a21e40820e30820e4086a21e50820e4" + "0820e5086a21e60820e50820e6086a21e70820e60820e7086a21e80820e70820e8086a21e90820e80820e9086a21ea" + "0820e90820ea086a21eb0820ea0820eb086a21ec0820eb0820ec086a21ed0820ec0820ed086a21ee0820ed0820ee08" + "6a21ef0820ee0820ef086a21f00820ef0820f0086a21f10820f00820f1086a21f20820f10820f2086a21f30820f208" + "20f3086a21f40820f30820f4086a21f50820f40820f5086a21f60820f50820f6086a21f70820f60820f7086a21f808" + "20f70820f8086a21f90820f80820f9086a21fa0820f90820fa086a21fb0820fa0820fb086a21fc0820fb0820fc086a" + "21fd0820fc0820fd086a21fe0820fd0820fe086a21ff0820fe0820ff086a21800920ff082080096a21810920800920" + "81096a2182092081092082096a2183092082092083096a2184092083092084096a2185092084092085096a21860920" + "85092086096a2187092086092087096a2188092087092088096a2189092088092089096a218a09208909208a096a21" + "8b09208a09208b096a218c09208b09208c096a218d09208c09208d096a218e09208d09208e096a218f09208e09208f" + "096a219009208f092090096a2191092090092091096a2192092091092092096a2193092092092093096a2194092093" + "092094096a2195092094092095096a2196092095092096096a2197092096092097096a2198092097092098096a2199" + "092098092099096a219a09209909209a096a219b09209a09209b096a219c09209b09209c096a219d09209c09209d09" + "6a219e09209d09209e096a219f09209e09209f096a21a009209f0920a0096a21a10920a00920a1096a21a20920a109" + "20a2096a21a30920a20920a3096a21a40920a30920a4096a21a50920a40920a5096a21a60920a50920a6096a21a709" + "20a60920a7096a21a80920a70920a8096a21a90920a80920a9096a21aa0920a90920aa096a21ab0920aa0920ab096a" + "21ac0920ab0920ac096a21ad0920ac0920ad096a21ae0920ad0920ae096a21af0920ae0920af096a21b00920af0920" + "b0096a21b10920b00920b1096a21b20920b10920b2096a21b30920b20920b3096a21b40920b30920b4096a21b50920" + "b40920b5096a21b60920b50920b6096a21b70920b60920b7096a21b80920b70920b8096a21b90920b80920b9096a21" + "ba0920b90920ba096a21bb0920ba0920bb096a21bc0920bb0920bc096a21bd0920bc0920bd096a21be0920bd0920be" + "096a21bf0920be0920bf096a21c00920bf0920c0096a21c10920c00920c1096a21c20920c10920c2096a21c30920c2" + "0920c3096a21c40920c30920c4096a21c50920c40920c5096a21c60920c50920c6096a21c70920c60920c7096a21c8" + "0920c70920c8096a21c90920c80920c9096a21ca0920c90920ca096a21cb0920ca0920cb096a21cc0920cb0920cc09" + "6a21cd0920cc0920cd096a21ce0920cd0920ce096a21cf0920ce0920cf096a21d00920cf0920d0096a21d10920d009" + "20d1096a21d20920d10920d2096a21d30920d20920d3096a21d40920d30920d4096a21d50920d40920d5096a21d609" + "20d50920d6096a21d70920d60920d7096a21d80920d70920d8096a21d90920d80920d9096a21da0920d90920da096a" + "21db0920da0920db096a21dc0920db0920dc096a21dd0920dc0920dd096a21de0920dd0920de096a21df0920de0920" + "df096a21e00920df0920e0096a21e10920e00920e1096a21e20920e10920e2096a21e30920e20920e3096a21e40920" + "e30920e4096a21e50920e40920e5096a21e60920e50920e6096a21e70920e60920e7096a21e80920e70920e8096a21" + "e90920e80920e9096a21ea0920e90920ea096a21eb0920ea0920eb096a21ec0920eb0920ec096a21ed0920ec0920ed" + "096a21ee0920ed0920ee096a21ef0920ee0920ef096a21f00920ef0920f0096a21f10920f00920f1096a21f20920f1" + "0920f2096a21f30920f20920f3096a21f40920f30920f4096a21f50920f40920f5096a21f60920f50920f6096a21f7" + "0920f60920f7096a21f80920f70920f8096a21f90920f80920f9096a21fa0920f90920fa096a21fb0920fa0920fb09" + "6a21fc0920fb0920fc096a21fd0920fc0920fd096a21fe0920fd0920fe096a21ff0920fe0920ff096a21800a20ff09" + "20800a6a21810a20800a20810a6a21820a20810a20820a6a21830a20820a20830a6a21840a20830a20840a6a21850a" + "20840a20850a6a21860a20850a20860a6a21870a20860a20870a6a21880a20870a20880a6a21890a20880a20890a6a" + "218a0a20890a208a0a6a218b0a208a0a208b0a6a218c0a208b0a208c0a6a218d0a208c0a208d0a6a218e0a208d0a20" + "8e0a6a218f0a208e0a208f0a6a21900a208f0a20900a6a21910a20900a20910a6a21920a20910a20920a6a21930a20" + "920a20930a6a21940a20930a20940a6a21950a20940a20950a6a21960a20950a20960a6a21970a20960a20970a6a21" + "980a20970a20980a6a21990a20980a20990a6a219a0a20990a209a0a6a219b0a209a0a209b0a6a219c0a209b0a209c" + "0a6a219d0a209c0a209d0a6a219e0a209d0a209e0a6a219f0a209e0a209f0a6a21a00a209f0a20a00a6a21a10a20a0" + "0a20a10a6a21a20a20a10a20a20a6a21a30a20a20a20a30a6a21a40a20a30a20a40a6a21a50a20a40a20a50a6a21a6" + "0a20a50a20a60a6a21a70a20a60a20a70a6a21a80a20a70a20a80a6a21a90a20a80a20a90a6a21aa0a20a90a20aa0a" + "6a21ab0a20aa0a20ab0a6a21ac0a20ab0a20ac0a6a21ad0a20ac0a20ad0a6a21ae0a20ad0a20ae0a6a21af0a20ae0a" + "20af0a6a21b00a20af0a20b00a6a21b10a20b00a20b10a6a21b20a20b10a20b20a6a21b30a20b20a20b30a6a21b40a" + "20b30a20b40a6a21b50a20b40a20b50a6a21b60a20b50a20b60a6a21b70a20b60a20b70a6a21b80a20b70a20b80a6a" + "21b90a20b80a20b90a6a21ba0a20b90a20ba0a6a21bb0a20ba0a20bb0a6a21bc0a20bb0a20bc0a6a21bd0a20bc0a20" + "bd0a6a21be0a20bd0a20be0a6a21bf0a20be0a20bf0a6a21c00a20bf0a20c00a6a21c10a20c00a20c10a6a21c20a20" + "c10a20c20a6a21c30a20c20a20c30a6a21c40a20c30a20c40a6a21c50a20c40a20c50a6a21c60a20c50a20c60a6a21" + "c70a20c60a20c70a6a21c80a20c70a20c80a6a21c90a20c80a20c90a6a21ca0a20c90a20ca0a6a21cb0a20ca0a20cb" + "0a6a21cc0a20cb0a20cc0a6a21cd0a20cc0a20cd0a6a21ce0a20cd0a20ce0a6a21cf0a20ce0a20cf0a6a21d00a20cf" + "0a20d00a6a21d10a20d00a20d10a6a21d20a20d10a20d20a6a21d30a20d20a20d30a6a21d40a20d30a20d40a6a21d5" + "0a20d40a20d50a6a21d60a20d50a20d60a6a21d70a20d60a20d70a6a21d80a20d70a20d80a6a21d90a20d80a20d90a" + "6a21da0a20d90a20da0a6a21db0a20da0a20db0a6a21dc0a20db0a20dc0a6a21dd0a20dc0a20dd0a6a21de0a20dd0a" + "20de0a6a21df0a20de0a20df0a6a21e00a20df0a20e00a6a21e10a20e00a20e10a6a21e20a20e10a20e20a6a21e30a" + "20e20a20e30a6a21e40a20e30a20e40a6a21e50a20e40a20e50a6a21e60a20e50a20e60a6a21e70a20e60a20e70a6a" + "21e80a20e70a20e80a6a21e90a20e80a20e90a6a21ea0a20e90a20ea0a6a21eb0a20ea0a20eb0a6a21ec0a20eb0a20" + "ec0a6a21ed0a20ec0a20ed0a6a21ee0a20ed0a20ee0a6a21ef0a20ee0a20ef0a6a21f00a20ef0a20f00a6a21f10a20" + "f00a20f10a6a21f20a20f10a20f20a6a21f30a20f20a20f30a6a21f40a20f30a20f40a6a21f50a20f40a20f50a6a21" + "f60a20f50a20f60a6a21f70a20f60a20f70a6a21f80a20f70a20f80a6a21f90a20f80a20f90a6a21fa0a20f90a20fa" + "0a6a21fb0a20fa0a20fb0a6a21fc0a20fb0a20fc0a6a21fd0a20fc0a20fd0a6a21fe0a20fd0a20fe0a6a21ff0a20fe" + "0a20ff0a6a21800b20ff0a20800b6a21810b20800b20810b6a21820b20810b20820b6a21830b20820b20830b6a2184" + "0b20830b20840b6a21850b20840b20850b6a21860b20850b20860b6a21870b20860b20870b6a21880b20870b20880b" + "6a21890b20880b20890b6a218a0b20890b208a0b6a218b0b208a0b208b0b6a218c0b208b0b208c0b6a218d0b208c0b" + "208d0b6a218e0b208d0b208e0b6a218f0b208e0b208f0b6a21900b208f0b20900b6a21910b20900b20910b6a21920b" + "20910b20920b6a21930b20920b20930b6a21940b20930b20940b6a21950b20940b20950b6a21960b20950b20960b6a" + "21970b20960b20970b6a21980b20970b20980b6a21990b20980b20990b6a219a0b20990b209a0b6a219b0b209a0b20" + "9b0b6a219c0b209b0b209c0b6a219d0b209c0b209d0b6a219e0b209d0b209e0b6a219f0b209e0b209f0b6a21a00b20" + "9f0b20a00b6a21a10b20a00b20a10b6a21a20b20a10b20a20b6a21a30b20a20b20a30b6a21a40b20a30b20a40b6a21" + "a50b20a40b20a50b6a21a60b20a50b20a60b6a21a70b20a60b20a70b6a21a80b20a70b20a80b6a21a90b20a80b20a9" + "0b6a21aa0b20a90b20aa0b6a21ab0b20aa0b20ab0b6a21ac0b20ab0b20ac0b6a21ad0b20ac0b20ad0b6a21ae0b20ad" + "0b20ae0b6a21af0b20ae0b20af0b6a21b00b20af0b20b00b6a21b10b20b00b20b10b6a21b20b20b10b20b20b6a21b3" + "0b20b20b20b30b6a21b40b20b30b20b40b6a21b50b20b40b20b50b6a21b60b20b50b20b60b6a21b70b20b60b20b70b" + "6a21b80b20b70b20b80b6a21b90b20b80b20b90b6a21ba0b20b90b20ba0b6a21bb0b20ba0b20bb0b6a21bc0b20bb0b" + "20bc0b6a21bd0b20bc0b20bd0b6a21be0b20bd0b20be0b6a21bf0b20be0b20bf0b6a21c00b20bf0b20c00b6a21c10b" + "20c00b20c10b6a21c20b20c10b20c20b6a21c30b20c20b20c30b6a21c40b20c30b20c40b6a21c50b20c40b20c50b6a" + "21c60b20c50b20c60b6a21c70b20c60b20c70b6a21c80b20c70b20c80b6a21c90b20c80b20c90b6a21ca0b20c90b20" + "ca0b6a21cb0b20ca0b20cb0b6a21cc0b20cb0b20cc0b6a21cd0b20cc0b20cd0b6a21ce0b20cd0b20ce0b6a21cf0b20" + "ce0b20cf0b6a21d00b20cf0b20d00b6a21d10b20d00b20d10b6a21d20b20d10b20d20b6a21d30b20d20b20d30b6a21" + "d40b20d30b20d40b6a21d50b20d40b20d50b6a21d60b20d50b20d60b6a21d70b20d60b20d70b6a21d80b20d70b20d8" + "0b6a21d90b20d80b20d90b6a21da0b20d90b20da0b6a21db0b20da0b20db0b6a21dc0b20db0b20dc0b6a21dd0b20dc" + "0b20dd0b6a21de0b20dd0b20de0b6a21df0b20de0b20df0b6a21e00b20df0b20e00b6a21e10b20e00b20e10b6a21e2" + "0b20e10b20e20b6a21e30b20e20b20e30b6a21e40b20e30b20e40b6a21e50b20e40b20e50b6a21e60b20e50b20e60b" + "6a21e70b20e60b20e70b6a21e80b20e70b20e80b6a21e90b20e80b20e90b6a21ea0b20e90b20ea0b6a21eb0b20ea0b" + "20eb0b6a21ec0b20eb0b20ec0b6a21ed0b20ec0b20ed0b6a21ee0b20ed0b20ee0b6a21ef0b20ee0b20ef0b6a21f00b" + "20ef0b20f00b6a21f10b20f00b20f10b6a21f20b20f10b20f20b6a21f30b20f20b20f30b6a21f40b20f30b20f40b6a" + "21f50b20f40b20f50b6a21f60b20f50b20f60b6a21f70b20f60b20f70b6a21f80b20f70b20f80b6a21f90b20f80b20" + "f90b6a21fa0b20f90b20fa0b6a21fb0b20fa0b20fb0b6a21fc0b20fb0b20fc0b6a21fd0b20fc0b20fd0b6a21fe0b20" + "fd0b20fe0b6a21ff0b20fe0b20ff0b6a21800c20ff0b20800c6a21810c20800c20810c6a21820c20810c20820c6a21" + "830c20820c20830c6a21840c20830c20840c6a21850c20840c20850c6a21860c20850c20860c6a21870c20860c2087" + "0c6a21880c20870c20880c6a21890c20880c20890c6a218a0c20890c208a0c6a218b0c208a0c208b0c6a218c0c208b" + "0c208c0c6a218d0c208c0c208d0c6a218e0c208d0c208e0c6a218f0c208e0c208f0c6a21900c208f0c20900c6a2191" + "0c20900c20910c6a21920c20910c20920c6a21930c20920c20930c6a21940c20930c20940c6a21950c20940c20950c" + "6a21960c20950c20960c6a21970c20960c20970c6a21980c20970c20980c6a21990c20980c20990c6a219a0c20990c" + "209a0c6a219b0c209a0c209b0c6a219c0c209b0c209c0c6a219d0c209c0c209d0c6a219e0c209d0c209e0c6a219f0c" + "209e0c209f0c6a21a00c209f0c20a00c6a21a10c20a00c20a10c6a21a20c20a10c20a20c6a21a30c20a20c20a30c6a" + "21a40c20a30c20a40c6a21a50c20a40c20a50c6a21a60c20a50c20a60c6a21a70c20a60c20a70c6a21a80c20a70c20" + "a80c6a21a90c20a80c20a90c6a21aa0c20a90c20aa0c6a21ab0c20aa0c20ab0c6a21ac0c20ab0c20ac0c6a21ad0c20" + "ac0c20ad0c6a21ae0c20ad0c20ae0c6a21af0c20ae0c20af0c6a21b00c20af0c20b00c6a21b10c20b00c20b10c6a21" + "b20c20b10c20b20c6a21b30c20b20c20b30c6a21b40c20b30c20b40c6a21b50c20b40c20b50c6a21b60c20b50c20b6" + "0c6a21b70c20b60c20b70c6a21b80c20b70c20b80c6a21b90c20b80c20b90c6a21ba0c20b90c20ba0c6a21bb0c20ba" + "0c20bb0c6a21bc0c20bb0c20bc0c6a21bd0c20bc0c20bd0c6a21be0c20bd0c20be0c6a21bf0c20be0c20bf0c6a21c0" + "0c20bf0c20c00c6a21c10c20c00c20c10c6a21c20c20c10c20c20c6a21c30c20c20c20c30c6a21c40c20c30c20c40c" + "6a21c50c20c40c20c50c6a21c60c20c50c20c60c6a21c70c20c60c20c70c6a21c80c20c70c20c80c6a21c90c20c80c" + "20c90c6a21ca0c20c90c20ca0c6a21cb0c20ca0c20cb0c6a21cc0c20cb0c20cc0c6a21cd0c20cc0c20cd0c6a21ce0c" + "20cd0c20ce0c6a21cf0c20ce0c20cf0c6a21d00c20cf0c20d00c6a21d10c20d00c20d10c6a21d20c20d10c20d20c6a" + "21d30c20d20c20d30c6a21d40c20d30c20d40c6a21d50c20d40c20d50c6a21d60c20d50c20d60c6a21d70c20d60c20" + "d70c6a21d80c20d70c20d80c6a21d90c20d80c20d90c6a21da0c20d90c20da0c6a21db0c20da0c20db0c6a21dc0c20" + "db0c20dc0c6a21dd0c20dc0c20dd0c6a21de0c20dd0c20de0c6a21df0c20de0c20df0c6a21e00c20df0c20e00c6a21" + "e10c20e00c20e10c6a21e20c20e10c20e20c6a21e30c20e20c20e30c6a21e40c20e30c20e40c6a21e50c20e40c20e5" + "0c6a21e60c20e50c20e60c6a21e70c20e60c20e70c6a21e80c20e70c20e80c6a21e90c20e80c20e90c6a21ea0c20e9" + "0c20ea0c6a21eb0c20ea0c20eb0c6a21ec0c20eb0c20ec0c6a21ed0c20ec0c20ed0c6a21ee0c20ed0c20ee0c6a21ef" + "0c20ee0c20ef0c6a21f00c20ef0c20f00c6a21f10c20f00c20f10c6a21f20c20f10c20f20c6a21f30c20f20c20f30c" + "6a21f40c20f30c20f40c6a21f50c20f40c20f50c6a21f60c20f50c20f60c6a21f70c20f60c20f70c6a21f80c20f70c" + "20f80c6a21f90c20f80c20f90c6a21fa0c20f90c20fa0c6a21fb0c20fa0c20fb0c6a21fc0c20fb0c20fc0c6a21fd0c" + "20fc0c20fd0c6a21fe0c20fd0c20fe0c6a21ff0c20fe0c20ff0c6a21800d20ff0c20800d6a21810d20800d20810d6a" + "21820d20810d20820d6a21830d20820d20830d6a21840d20830d20840d6a21850d20840d20850d6a21860d20850d20" + "860d6a21870d20860d20870d6a21880d20870d20880d6a21890d20880d20890d6a218a0d20890d208a0d6a218b0d20" + "8a0d208b0d6a218c0d208b0d208c0d6a218d0d208c0d208d0d6a218e0d208d0d208e0d6a218f0d208e0d208f0d6a21" + "900d208f0d20900d6a21910d20900d20910d6a21920d20910d20920d6a21930d20920d20930d6a21940d20930d2094" + "0d6a21950d20940d20950d6a21960d20950d20960d6a21970d20960d20970d6a21980d20970d20980d6a21990d2098" + "0d20990d6a219a0d20990d209a0d6a219b0d209a0d209b0d6a219c0d209b0d209c0d6a219d0d209c0d209d0d6a219e" + "0d209d0d209e0d6a219f0d209e0d209f0d6a21a00d209f0d20a00d6a21a10d20a00d20a10d6a21a20d20a10d20a20d" + "6a21a30d20a20d20a30d6a21a40d20a30d20a40d6a21a50d20a40d20a50d6a21a60d20a50d20a60d6a21a70d20a60d" + "20a70d6a21a80d20a70d20a80d6a21a90d20a80d20a90d6a21aa0d20a90d20aa0d6a21ab0d20aa0d20ab0d6a21ac0d" + "20ab0d20ac0d6a21ad0d20ac0d20ad0d6a21ae0d20ad0d20ae0d6a21af0d20ae0d20af0d6a21b00d20af0d20b00d6a" + "21b10d20b00d20b10d6a21b20d20b10d20b20d6a21b30d20b20d20b30d6a21b40d20b30d20b40d6a21b50d20b40d20" + "b50d6a21b60d20b50d20b60d6a21b70d20b60d20b70d6a21b80d20b70d20b80d6a21b90d20b80d20b90d6a21ba0d20" + "b90d20ba0d6a21bb0d20ba0d20bb0d6a21bc0d20bb0d20bc0d6a21bd0d20bc0d20bd0d6a21be0d20bd0d20be0d6a21" + "bf0d20be0d20bf0d6a21c00d20bf0d20c00d6a21c10d20c00d20c10d6a21c20d20c10d20c20d6a21c30d20c20d20c3" + "0d6a21c40d20c30d20c40d6a21c50d20c40d20c50d6a21c60d20c50d20c60d6a21c70d20c60d20c70d6a21c80d20c7" + "0d20c80d6a21c90d20c80d20c90d6a21ca0d20c90d20ca0d6a21cb0d20ca0d20cb0d6a21cc0d20cb0d20cc0d6a21cd" + "0d20cc0d20cd0d6a21ce0d20cd0d20ce0d6a21cf0d20ce0d20cf0d6a21d00d20cf0d20d00d6a21d10d20d00d20d10d" + "6a21d20d20d10d20d20d6a21d30d20d20d20d30d6a21d40d20d30d20d40d6a21d50d20d40d20d50d6a21d60d20d50d" + "20d60d6a21d70d20d60d20d70d6a21d80d20d70d20d80d6a21d90d20d80d20d90d6a21da0d20d90d20da0d6a21db0d" + "20da0d20db0d6a21dc0d20db0d20dc0d6a21dd0d20dc0d20dd0d6a21de0d20dd0d20de0d6a21df0d20de0d20df0d6a" + "21e00d20df0d20e00d6a21e10d20e00d20e10d6a21e20d20e10d20e20d6a21e30d20e20d20e30d6a21e40d20e30d20" + "e40d6a21e50d20e40d20e50d6a21e60d20e50d20e60d6a21e70d20e60d20e70d6a21e80d20e70d20e80d6a21e90d20" + "e80d20e90d6a21ea0d20e90d20ea0d6a21eb0d20ea0d20eb0d6a21ec0d20eb0d20ec0d6a21ed0d20ec0d20ed0d6a21" + "ee0d20ed0d20ee0d6a21ef0d20ee0d20ef0d6a21f00d20ef0d20f00d6a21f10d20f00d20f10d6a21f20d20f10d20f2" + "0d6a21f30d20f20d20f30d6a21f40d20f30d20f40d6a21f50d20f40d20f50d6a21f60d20f50d20f60d6a21f70d20f6" + "0d20f70d6a21f80d20f70d20f80d6a21f90d20f80d20f90d6a21fa0d20f90d20fa0d6a21fb0d20fa0d20fb0d6a21fc" + "0d20fb0d20fc0d6a21fd0d20fc0d20fd0d6a21fe0d20fd0d20fe0d6a21ff0d20fe0d20ff0d6a21800e20ff0d20800e" + "6a21810e20800e20810e6a21820e20810e20820e6a21830e20820e20830e6a21840e20830e20840e6a21850e20840e" + "20850e6a21860e20850e20860e6a21870e20860e20870e6a21880e20870e20880e6a21890e20880e20890e6a218a0e" + "20890e208a0e6a218b0e208a0e208b0e6a218c0e208b0e208c0e6a218d0e208c0e208d0e6a218e0e208d0e208e0e6a" + "218f0e208e0e208f0e6a21900e208f0e20900e6a21910e20900e20910e6a21920e20910e20920e6a21930e20920e20" + "930e6a21940e20930e20940e6a21950e20940e20950e6a21960e20950e20960e6a21970e20960e20970e6a21980e20" + "970e20980e6a21990e20980e20990e6a219a0e20990e209a0e6a219b0e209a0e209b0e6a219c0e209b0e209c0e6a21" + "9d0e209c0e209d0e6a219e0e209d0e209e0e6a219f0e209e0e209f0e6a21a00e209f0e20a00e6a21a10e20a00e20a1" + "0e6a21a20e20a10e20a20e6a21a30e20a20e20a30e6a21a40e20a30e20a40e6a21a50e20a40e20a50e6a21a60e20a5" + "0e20a60e6a21a70e20a60e20a70e6a21a80e20a70e20a80e6a21a90e20a80e20a90e6a21aa0e20a90e20aa0e6a21ab" + "0e20aa0e20ab0e6a21ac0e20ab0e20ac0e6a21ad0e20ac0e20ad0e6a21ae0e20ad0e20ae0e6a21af0e20ae0e20af0e" + "6a21b00e20af0e20b00e6a21b10e20b00e20b10e6a21b20e20b10e20b20e6a21b30e20b20e20b30e6a21b40e20b30e" + "20b40e6a21b50e20b40e20b50e6a21b60e20b50e20b60e6a21b70e20b60e20b70e6a21b80e20b70e20b80e6a21b90e" + "20b80e20b90e6a21ba0e20b90e20ba0e6a21bb0e20ba0e20bb0e6a21bc0e20bb0e20bc0e6a21bd0e20bc0e20bd0e6a" + "21be0e20bd0e20be0e6a21bf0e20be0e20bf0e6a21c00e20bf0e20c00e6a21c10e20c00e20c10e6a21c20e20c10e20" + "c20e6a21c30e20c20e20c30e6a21c40e20c30e20c40e6a21c50e20c40e20c50e6a21c60e20c50e20c60e6a21c70e20" + "c60e20c70e6a21c80e20c70e20c80e6a21c90e20c80e20c90e6a21ca0e20c90e20ca0e6a21cb0e20ca0e20cb0e6a21" + "cc0e20cb0e20cc0e6a21cd0e20cc0e20cd0e6a21ce0e20cd0e20ce0e6a21cf0e20ce0e20cf0e6a21d00e20cf0e20d0" + "0e6a21d10e20d00e20d10e6a21d20e20d10e20d20e6a21d30e20d20e20d30e6a21d40e20d30e20d40e6a21d50e20d4" + "0e20d50e6a21d60e20d50e20d60e6a21d70e20d60e20d70e6a21d80e20d70e20d80e6a21d90e20d80e20d90e6a21da" + "0e20d90e20da0e6a21db0e20da0e20db0e6a21dc0e20db0e20dc0e6a21dd0e20dc0e20dd0e6a21de0e20dd0e20de0e" + "6a21df0e20de0e20df0e6a21e00e20df0e20e00e6a21e10e20e00e20e10e6a21e20e20e10e20e20e6a21e30e20e20e" + "20e30e6a21e40e20e30e20e40e6a21e50e20e40e20e50e6a21e60e20e50e20e60e6a21e70e20e60e20e70e6a21e80e" + "20e70e20e80e6a21e90e20e80e20e90e6a21ea0e20e90e20ea0e6a21eb0e20ea0e20eb0e6a21ec0e20eb0e20ec0e6a" + "21ed0e20ec0e20ed0e6a21ee0e20ed0e20ee0e6a21ef0e20ee0e20ef0e6a21f00e20ef0e20f00e6a21f10e20f00e20" + "f10e6a21f20e20f10e20f20e6a21f30e20f20e20f30e6a21f40e20f30e20f40e6a21f50e20f40e20f50e6a21f60e20" + "f50e20f60e6a21f70e20f60e20f70e6a21f80e20f70e20f80e6a21f90e20f80e20f90e6a21fa0e20f90e20fa0e6a21" + "fb0e20fa0e20fb0e6a21fc0e20fb0e20fc0e6a21fd0e20fc0e20fd0e6a21fe0e20fd0e20fe0e6a21ff0e20fe0e20ff" + "0e6a21800f20ff0e20800f6a21810f20800f20810f6a21820f20810f20820f6a21830f20820f20830f6a21840f2083" + "0f20840f6a21850f20840f20850f6a21860f20850f20860f6a21870f20860f20870f6a21880f20870f20880f6a2189" + "0f20880f20890f6a218a0f20890f208a0f6a218b0f208a0f208b0f6a218c0f208b0f208c0f6a218d0f208c0f208d0f" + "6a218e0f208d0f208e0f6a218f0f208e0f208f0f6a21900f208f0f20900f6a21910f20900f20910f6a21920f20910f" + "20920f6a21930f20920f20930f6a21940f20930f20940f6a21950f20940f20950f6a21960f20950f20960f6a21970f" + "20960f20970f6a21980f20970f20980f6a21990f20980f20990f6a219a0f20990f209a0f6a219b0f209a0f209b0f6a" + "219c0f209b0f209c0f6a219d0f209c0f209d0f6a219e0f209d0f209e0f6a219f0f209e0f209f0f6a21a00f209f0f20" + "a00f6a21a10f20a00f20a10f6a21a20f20a10f20a20f6a21a30f20a20f20a30f6a21a40f20a30f20a40f6a21a50f20" + "a40f20a50f6a21a60f20a50f20a60f6a21a70f20a60f20a70f6a21a80f20a70f20a80f6a21a90f20a80f20a90f6a21" + "aa0f20a90f20aa0f6a21ab0f20aa0f20ab0f6a21ac0f20ab0f20ac0f6a21ad0f20ac0f20ad0f6a21ae0f20ad0f20ae" + "0f6a21af0f20ae0f20af0f6a21b00f20af0f20b00f6a21b10f20b00f20b10f6a21b20f20b10f20b20f6a21b30f20b2" + "0f20b30f6a21b40f20b30f20b40f6a21b50f20b40f20b50f6a21b60f20b50f20b60f6a21b70f20b60f20b70f6a21b8" + "0f20b70f20b80f6a21b90f20b80f20b90f6a21ba0f20b90f20ba0f6a21bb0f20ba0f20bb0f6a21bc0f20bb0f20bc0f" + "6a21bd0f20bc0f20bd0f6a21be0f20bd0f20be0f6a21bf0f20be0f20bf0f6a21c00f20bf0f20c00f6a21c10f20c00f" + "20c10f6a21c20f20c10f20c20f6a21c30f20c20f20c30f6a21c40f20c30f20c40f6a21c50f20c40f20c50f6a21c60f" + "20c50f20c60f6a21c70f20c60f20c70f6a21c80f20c70f20c80f6a21c90f20c80f20c90f6a21ca0f20c90f20ca0f6a" + "21cb0f20ca0f20cb0f6a21cc0f20cb0f20cc0f6a21cd0f20cc0f20cd0f6a21ce0f20cd0f20ce0f6a21cf0f20ce0f20" + "cf0f6a21d00f20cf0f20d00f6a21d10f20d00f20d10f6a21d20f20d10f20d20f6a21d30f20d20f20d30f6a21d40f20" + "d30f20d40f6a21d50f20d40f20d50f6a21d60f20d50f20d60f6a21d70f20d60f20d70f6a21d80f20d70f20d80f6a21" + "d90f20d80f20d90f6a21da0f20d90f20da0f6a21db0f20da0f20db0f6a21dc0f20db0f20dc0f6a21dd0f20dc0f20dd" + "0f6a21de0f20dd0f20de0f6a21df0f20de0f20df0f6a21e00f20df0f20e00f6a21e10f20e00f20e10f6a21e20f20e1" + "0f20e20f6a21e30f20e20f20e30f6a21e40f20e30f20e40f6a21e50f20e40f20e50f6a21e60f20e50f20e60f6a21e7" + "0f20e60f20e70f6a21e80f20e70f20e80f6a21e90f20e80f20e90f6a21ea0f20e90f20ea0f6a21eb0f20ea0f20eb0f" + "6a21ec0f20eb0f20ec0f6a21ed0f20ec0f20ed0f6a21ee0f20ed0f20ee0f6a21ef0f20ee0f20ef0f6a21f00f20ef0f" + "20f00f6a21f10f20f00f20f10f6a21f20f20f10f20f20f6a21f30f20f20f20f30f6a21f40f20f30f20f40f6a21f50f" + "20f40f20f50f6a21f60f20f50f20f60f6a21f70f20f60f20f70f6a21f80f20f70f20f80f6a21f90f20f80f20f90f6a" + "21fa0f20f90f20fa0f6a21fb0f20fa0f20fb0f6a21fc0f20fb0f20fc0f6a21fd0f20fc0f20fd0f6a21fe0f20fd0f20" + "fe0f6a21ff0f20fe0f20ff0f6a21801020ff0f2080106a2181102080102081106a2182102081102082106a21831020" + "82102083106a2184102083102084106a2185102084102085106a2186102085102086106a2187102086102087106a21" + "88102087102088106a2189102088102089106a218a10208910208a106a218b10208a10208b106a218c10208b10208c" + "106a218d10208c10208d106a218e10208d10208e106a218f10208e10208f106a219010208f102090106a2191102090" + "102091106a2192102091102092106a2193102092102093106a2194102093102094106a2195102094102095106a2196" + "102095102096106a2197102096102097106a2198102097102098106a2199102098102099106a219a10209910209a10" + "6a219b10209a10209b106a219c10209b10209c106a219d10209c10209d106a219e10209d10209e106a219f10209e10" + "209f106a21a010209f1020a0106a21a11020a01020a1106a21a21020a11020a2106a21a31020a21020a3106a21a410" + "20a31020a4106a21a51020a41020a5106a21a61020a51020a6106a21a71020a61020a7106a21a81020a71020a8106a" + "21a91020a81020a9106a21aa1020a91020aa106a21ab1020aa1020ab106a21ac1020ab1020ac106a21ad1020ac1020" + "ad106a21ae1020ad1020ae106a21af1020ae1020af106a21b01020af1020b0106a21b11020b01020b1106a21b21020" + "b11020b2106a21b31020b21020b3106a21b41020b31020b4106a21b51020b41020b5106a21b61020b51020b6106a21" + "b71020b61020b7106a21b81020b71020b8106a21b91020b81020b9106a21ba1020b91020ba106a21bb1020ba1020bb" + "106a21bc1020bb1020bc106a21bd1020bc1020bd106a21be1020bd1020be106a21bf1020be1020bf106a21c01020bf" + "1020c0106a21c11020c01020c1106a21c21020c11020c2106a21c31020c21020c3106a21c41020c31020c4106a21c5" + "1020c41020c5106a21c61020c51020c6106a21c71020c61020c7106a21c81020c71020c8106a21c91020c81020c910" + "6a21ca1020c91020ca106a21cb1020ca1020cb106a21cc1020cb1020cc106a21cd1020cc1020cd106a21ce1020cd10" + "20ce106a21cf1020ce1020cf106a21d01020cf1020d0106a21d11020d01020d1106a21d21020d11020d2106a21d310" + "20d21020d3106a21d41020d31020d4106a21d51020d41020d5106a21d61020d51020d6106a21d71020d61020d7106a" + "21d81020d71020d8106a21d91020d81020d9106a21da1020d91020da106a21db1020da1020db106a21dc1020db1020" + "dc106a21dd1020dc1020dd106a21de1020dd1020de106a21df1020de1020df106a21e01020df1020e0106a21e11020" + "e01020e1106a21e21020e11020e2106a21e31020e21020e3106a21e41020e31020e4106a21e51020e41020e5106a21" + "e61020e51020e6106a21e71020e61020e7106a21e81020e71020e8106a21e91020e81020e9106a21ea1020e91020ea" + "106a21eb1020ea1020eb106a21ec1020eb1020ec106a21ed1020ec1020ed106a21ee1020ed1020ee106a21ef1020ee" + "1020ef106a21f01020ef1020f0106a21f11020f01020f1106a21f21020f11020f2106a21f31020f21020f3106a21f4" + "1020f31020f4106a21f51020f41020f5106a21f61020f51020f6106a21f71020f61020f7106a21f81020f71020f810" + "6a21f91020f81020f9106a21fa1020f91020fa106a21fb1020fa1020fb106a21fc1020fb1020fc106a21fd1020fc10" + "20fd106a21fe1020fd1020fe106a21ff1020fe1020ff106a21801120ff102080116a2181112080112081116a218211" + "2081112082116a2183112082112083116a2184112083112084116a2185112084112085116a2186112085112086116a" + "2187112086112087116a2188112087112088116a2189112088112089116a218a11208911208a116a218b11208a1120" + "8b116a218c11208b11208c116a218d11208c11208d116a218e11208d11208e116a218f11208e11208f116a21901120" + "8f112090116a2191112090112091116a2192112091112092116a2193112092112093116a2194112093112094116a21" + "95112094112095116a2196112095112096116a2197112096112097116a2198112097112098116a2199112098112099" + "116a219a11209911209a116a219b11209a11209b116a219c11209b11209c116a219d11209c11209d116a219e11209d" + "11209e116a219f11209e11209f116a21a011209f1120a0116a21a11120a01120a1116a21a21120a11120a2116a21a3" + "1120a21120a3116a21a41120a31120a4116a21a51120a41120a5116a21a61120a51120a6116a21a71120a61120a711" + "6a21a81120a71120a8116a21a91120a81120a9116a21aa1120a91120aa116a21ab1120aa1120ab116a21ac1120ab11" + "20ac116a21ad1120ac1120ad116a21ae1120ad1120ae116a21af1120ae1120af116a21b01120af1120b0116a21b111" + "20b01120b1116a21b21120b11120b2116a21b31120b21120b3116a21b41120b31120b4116a21b51120b41120b5116a" + "21b61120b51120b6116a21b71120b61120b7116a21b81120b71120b8116a21b91120b81120b9116a21ba1120b91120" + "ba116a21bb1120ba1120bb116a21bc1120bb1120bc116a21bd1120bc1120bd116a21be1120bd1120be116a21bf1120" + "be1120bf116a21c01120bf1120c0116a21c11120c01120c1116a21c21120c11120c2116a21c31120c21120c3116a21" + "c41120c31120c4116a21c51120c41120c5116a21c61120c51120c6116a21c71120c61120c7116a21c81120c71120c8" + "116a21c91120c81120c9116a21ca1120c91120ca116a21cb1120ca1120cb116a21cc1120cb1120cc116a21cd1120cc" + "1120cd116a21ce1120cd1120ce116a21cf1120ce1120cf116a21d01120cf1120d0116a21d11120d01120d1116a21d2" + "1120d11120d2116a21d31120d21120d3116a21d41120d31120d4116a21d51120d41120d5116a21d61120d51120d611" + "6a21d71120d61120d7116a21d81120d71120d8116a21d91120d81120d9116a21da1120d91120da116a21db1120da11" + "20db116a21dc1120db1120dc116a21dd1120dc1120dd116a21de1120dd1120de116a21df1120de1120df116a21e011" + "20df1120e0116a21e11120e01120e1116a21e21120e11120e2116a21e31120e21120e3116a21e41120e31120e4116a" + "21e51120e41120e5116a21e61120e51120e6116a21e71120e61120e7116a21e81120e71120e8116a21e91120e81120" + "e9116a21ea1120e91120ea116a21eb1120ea1120eb116a21ec1120eb1120ec116a21ed1120ec1120ed116a21ee1120" + "ed1120ee116a21ef1120ee1120ef116a21f01120ef1120f0116a21f11120f01120f1116a21f21120f11120f2116a21" + "f31120f21120f3116a21f41120f31120f4116a21f51120f41120f5116a21f61120f51120f6116a21f71120f61120f7" + "116a21f81120f71120f8116a21f91120f81120f9116a21fa1120f91120fa116a21fb1120fa1120fb116a21fc1120fb" + "1120fc116a21fd1120fc1120fd116a21fe1120fd1120fe116a21ff1120fe1120ff116a21801220ff112080126a2181" + "122080122081126a2182122081122082126a2183122082122083126a2184122083122084126a218512208412208512" + "6a2186122085122086126a2187122086122087126a2188122087122088126a2189122088122089126a218a12208912" + "208a126a218b12208a12208b126a218c12208b12208c126a218d12208c12208d126a218e12208d12208e126a218f12" + "208e12208f126a219012208f122090126a2191122090122091126a2192122091122092126a2193122092122093126a" + "2194122093122094126a2195122094122095126a2196122095122096126a2197122096122097126a21981220971220" + "98126a2199122098122099126a219a12209912209a126a219b12209a12209b126a219c12209b12209c126a219d1220" + "9c12209d126a219e12209d12209e126a219f12209e12209f126a21a012209f1220a0126a21a11220a01220a1126a21" + "a21220a11220a2126a21a31220a21220a3126a21a41220a31220a4126a21a51220a41220a5126a21a61220a51220a6" + "126a21a71220a61220a7126a21a81220a71220a8126a21a91220a81220a9126a21aa1220a91220aa126a21ab1220aa" + "1220ab126a21ac1220ab1220ac126a21ad1220ac1220ad126a21ae1220ad1220ae126a21af1220ae1220af126a21b0" + "1220af1220b0126a21b11220b01220b1126a21b21220b11220b2126a21b31220b21220b3126a21b41220b31220b412" + "6a21b51220b41220b5126a21b61220b51220b6126a21b71220b61220b7126a21b81220b71220b8126a21b91220b812" + "20b9126a21ba1220b91220ba126a21bb1220ba1220bb126a21bc1220bb1220bc126a21bd1220bc1220bd126a21be12" + "20bd1220be126a21bf1220be1220bf126a21c01220bf1220c0126a21c11220c01220c1126a21c21220c11220c2126a" + "21c31220c21220c3126a21c41220c31220c4126a21c51220c41220c5126a21c61220c51220c6126a21c71220c61220" + "c7126a21c81220c71220c8126a21c91220c81220c9126a21ca1220c91220ca126a21cb1220ca1220cb126a21cc1220" + "cb1220cc126a21cd1220cc1220cd126a21ce1220cd1220ce126a21cf1220ce1220cf126a21d01220cf1220d0126a21" + "d11220d01220d1126a21d21220d11220d2126a21d31220d21220d3126a21d41220d31220d4126a21d51220d41220d5" + "126a21d61220d51220d6126a21d71220d61220d7126a21d81220d71220d8126a21d91220d81220d9126a21da1220d9" + "1220da126a21db1220da1220db126a21dc1220db1220dc126a21dd1220dc1220dd126a21de1220dd1220de126a21df" + "1220de1220df126a21e01220df1220e0126a21e11220e01220e1126a21e21220e11220e2126a21e31220e21220e312" + "6a21e41220e31220e4126a21e51220e41220e5126a21e61220e51220e6126a21e71220e61220e7126a21e81220e712" + "20e8126a21e91220e81220e9126a21ea1220e91220ea126a21eb1220ea1220eb126a21ec1220eb1220ec126a21ed12" + "20ec1220ed126a21ee1220ed1220ee126a21ef1220ee1220ef126a21f01220ef1220f0126a21f11220f01220f1126a" + "21f21220f11220f2126a21f31220f21220f3126a21f41220f31220f4126a21f51220f41220f5126a21f61220f51220" + "f6126a21f71220f61220f7126a21f81220f71220f8126a21f91220f81220f9126a21fa1220f91220fa126a21fb1220" + "fa1220fb126a21fc1220fb1220fc126a21fd1220fc1220fd126a21fe1220fd1220fe126a21ff1220fe1220ff126a21" + "801320ff122080136a2181132080132081136a2182132081132082136a2183132082132083136a2184132083132084" + "136a2185132084132085136a2186132085132086136a2187132086132087136a2188132087132088136a2189132088" + "132089136a218a13208913208a136a218b13208a13208b136a218c13208b13208c136a218d13208c13208d136a218e" + "13208d13208e136a218f13208e13208f136a219013208f132090136a2191132090132091136a219213209113209213" + "6a2193132092132093136a2194132093132094136a2195132094132095136a2196132095132096136a219713209613" + "2097136a2198132097132098136a2199132098132099136a219a13209913209a136a219b13209a13209b136a219c13" + "209b13209c136a219d13209c13209d136a219e13209d13209e136a219f13209e13209f136a21a013209f1320a0136a" + "21a11320a01320a1136a21a21320a11320a2136a21a31320a21320a3136a21a41320a31320a4136a21a51320a41320" + "a5136a21a61320a51320a6136a21a71320a61320a7136a21a81320a71320a8136a21a91320a81320a9136a21aa1320" + "a91320aa136a21ab1320aa1320ab136a21ac1320ab1320ac136a21ad1320ac1320ad136a21ae1320ad1320ae136a21" + "af1320ae1320af136a21b01320af1320b0136a21b11320b01320b1136a21b21320b11320b2136a21b31320b21320b3" + "136a21b41320b31320b4136a21b51320b41320b5136a21b61320b51320b6136a21b71320b61320b7136a21b81320b7" + "1320b8136a21b91320b81320b9136a21ba1320b91320ba136a21bb1320ba1320bb136a21bc1320bb1320bc136a21bd" + "1320bc1320bd136a21be1320bd1320be136a21bf1320be1320bf136a21c01320bf1320c0136a21c11320c01320c113" + "6a21c21320c11320c2136a21c31320c21320c3136a21c41320c31320c4136a21c51320c41320c5136a21c61320c513" + "20c6136a21c71320c61320c7136a21c81320c71320c8136a21c91320c81320c9136a21ca1320c91320ca136a21cb13" + "20ca1320cb136a21cc1320cb1320cc136a21cd1320cc1320cd136a21ce1320cd1320ce136a21cf1320ce1320cf136a" + "21d01320cf1320d0136a21d11320d01320d1136a21d21320d11320d2136a21d31320d21320d3136a21d41320d31320" + "d4136a21d51320d41320d5136a21d61320d51320d6136a21d71320d61320d7136a21d81320d71320d8136a21d91320" + "d81320d9136a21da1320d91320da136a21db1320da1320db136a21dc1320db1320dc136a21dd1320dc1320dd136a21" + "de1320dd1320de136a21df1320de1320df136a21e01320df1320e0136a21e11320e01320e1136a21e21320e11320e2" + "136a21e31320e21320e3136a21e41320e31320e4136a21e51320e41320e5136a21e61320e51320e6136a21e71320e6" + "1320e7136a21e81320e71320e8136a21e91320e81320e9136a21ea1320e91320ea136a21eb1320ea1320eb136a21ec" + "1320eb1320ec136a21ed1320ec1320ed136a21ee1320ed1320ee136a21ef1320ee1320ef136a21f01320ef1320f013" + "6a21f11320f01320f1136a21f21320f11320f2136a21f31320f21320f3136a21f41320f31320f4136a21f51320f413" + "20f5136a21f61320f51320f6136a21f71320f61320f7136a21f81320f71320f8136a21f91320f81320f9136a21fa13" + "20f91320fa136a21fb1320fa1320fb136a21fc1320fb1320fc136a21fd1320fc1320fd136a21fe1320fd1320fe136a" + "21ff1320fe1320ff136a21801420ff132080146a2181142080142081146a2182142081142082146a21831420821420" + "83146a2184142083142084146a2185142084142085146a2186142085142086146a2187142086142087146a21881420" + "87142088146a2189142088142089146a218a14208914208a146a218b14208a14208b146a218c14208b14208c146a21" + "8d14208c14208d146a218e14208d14208e146a218f14208e14208f146a219014208f142090146a2191142090142091" + "146a2192142091142092146a2193142092142093146a2194142093142094146a2195142094142095146a2196142095" + "142096146a2197142096142097146a2198142097142098146a2199142098142099146a219a14209914209a146a219b" + "14209a14209b146a219c14209b14209c146a219d14209c14209d146a219e14209d14209e146a219f14209e14209f14" + "6a21a014209f1420a0146a21a11420a01420a1146a21a21420a11420a2146a21a31420a21420a3146a21a41420a314" + "20a4146a21a51420a41420a5146a21a61420a51420a6146a21a71420a61420a7146a21a81420a71420a8146a21a914" + "20a81420a9146a21aa1420a91420aa146a21ab1420aa1420ab146a21ac1420ab1420ac146a21ad1420ac1420ad146a" + "21ae1420ad1420ae146a21af1420ae1420af146a21b01420af1420b0146a21b11420b01420b1146a21b21420b11420" + "b2146a21b31420b21420b3146a21b41420b31420b4146a21b51420b41420b5146a21b61420b51420b6146a21b71420" + "b61420b7146a21b81420b71420b8146a21b91420b81420b9146a21ba1420b91420ba146a21bb1420ba1420bb146a21" + "bc1420bb1420bc146a21bd1420bc1420bd146a21be1420bd1420be146a21bf1420be1420bf146a21c01420bf1420c0" + "146a21c11420c01420c1146a21c21420c11420c2146a21c31420c21420c3146a21c41420c31420c4146a21c51420c4" + "1420c5146a21c61420c51420c6146a21c71420c61420c7146a21c81420c71420c8146a21c91420c81420c9146a21ca" + "1420c91420ca146a21cb1420ca1420cb146a21cc1420cb1420cc146a21cd1420cc1420cd146a21ce1420cd1420ce14" + "6a21cf1420ce1420cf146a21d01420cf1420d0146a21d11420d01420d1146a21d21420d11420d2146a21d31420d214" + "20d3146a21d41420d31420d4146a21d51420d41420d5146a21d61420d51420d6146a21d71420d61420d7146a21d814" + "20d71420d8146a21d91420d81420d9146a21da1420d91420da146a21db1420da1420db146a21dc1420db1420dc146a" + "21dd1420dc1420dd146a21de1420dd1420de146a21df1420de1420df146a21e01420df1420e0146a21e11420e01420" + "e1146a21e21420e11420e2146a21e31420e21420e3146a21e41420e31420e4146a21e51420e41420e5146a21e61420" + "e51420e6146a21e71420e61420e7146a21e81420e71420e8146a21e91420e81420e9146a21ea1420e91420ea146a21" + "eb1420ea1420eb146a21ec1420eb1420ec146a21ed1420ec1420ed146a21ee1420ed1420ee146a21ef1420ee1420ef" + "146a21f01420ef1420f0146a21f11420f01420f1146a21f21420f11420f2146a21f31420f21420f3146a21f41420f3" + "1420f4146a21f51420f41420f5146a21f61420f51420f6146a21f71420f61420f7146a21f81420f71420f8146a21f9" + "1420f81420f9146a21fa1420f91420fa146a21fb1420fa1420fb146a21fc1420fb1420fc146a21fd1420fc1420fd14" + "6a21fe1420fd1420fe146a21ff1420fe1420ff146a21801520ff142080156a2181152080152081156a218215208115" + "2082156a2183152082152083156a2184152083152084156a2185152084152085156a2186152085152086156a218715" + "2086152087156a2188152087152088156a2189152088152089156a218a15208915208a156a218b15208a15208b156a" + "218c15208b15208c156a218d15208c15208d156a218e15208d15208e156a218f15208e15208f156a219015208f1520" + "90156a2191152090152091156a2192152091152092156a2193152092152093156a2194152093152094156a21951520" + "94152095156a2196152095152096156a2197152096152097156a2198152097152098156a2199152098152099156a21" + "9a15209915209a156a219b15209a15209b156a219c15209b15209c156a219d15209c15209d156a219e15209d15209e" + "156a219f15209e15209f156a21a015209f1520a0156a21a11520a01520a1156a21a21520a11520a2156a21a31520a2" + "1520a3156a21a41520a31520a4156a21a51520a41520a5156a21a61520a51520a6156a21a71520a61520a7156a21a8" + "1520a71520a8156a21a91520a81520a9156a21aa1520a91520aa156a21ab1520aa1520ab156a21ac1520ab1520ac15" + "6a21ad1520ac1520ad156a21ae1520ad1520ae156a21af1520ae1520af156a21b01520af1520b0156a21b11520b015" + "20b1156a21b21520b11520b2156a21b31520b21520b3156a21b41520b31520b4156a21b51520b41520b5156a21b615" + "20b51520b6156a21b71520b61520b7156a21b81520b71520b8156a21b91520b81520b9156a21ba1520b91520ba156a" + "21bb1520ba1520bb156a21bc1520bb1520bc156a21bd1520bc1520bd156a21be1520bd1520be156a21bf1520be1520" + "bf156a21c01520bf1520c0156a21c11520c01520c1156a21c21520c11520c2156a21c31520c21520c3156a21c41520" + "c31520c4156a21c51520c41520c5156a21c61520c51520c6156a21c71520c61520c7156a21c81520c71520c8156a21" + "c91520c81520c9156a21ca1520c91520ca156a21cb1520ca1520cb156a21cc1520cb1520cc156a21cd1520cc1520cd" + "156a21ce1520cd1520ce156a21cf1520ce1520cf156a21d01520cf1520d0156a21d11520d01520d1156a21d21520d1" + "1520d2156a21d31520d21520d3156a21d41520d31520d4156a21d51520d41520d5156a21d61520d51520d6156a21d7" + "1520d61520d7156a21d81520d71520d8156a21d91520d81520d9156a21da1520d91520da156a21db1520da1520db15" + "6a21dc1520db1520dc156a21dd1520dc1520dd156a21de1520dd1520de156a21df1520de1520df156a21e01520df15" + "20e0156a21e11520e01520e1156a21e21520e11520e2156a21e31520e21520e3156a21e41520e31520e4156a21e515" + "20e41520e5156a21e61520e51520e6156a21e71520e61520e7156a21e81520e71520e8156a21e91520e81520e9156a" + "21ea1520e91520ea156a21eb1520ea1520eb156a21ec1520eb1520ec156a21ed1520ec1520ed156a21ee1520ed1520" + "ee156a21ef1520ee1520ef156a21f01520ef1520f0156a21f11520f01520f1156a21f21520f11520f2156a21f31520" + "f21520f3156a21f41520f31520f4156a21f51520f41520f5156a21f61520f51520f6156a21f71520f61520f7156a21" + "f81520f71520f8156a21f91520f81520f9156a21fa1520f91520fa156a21fb1520fa1520fb156a21fc1520fb1520fc" + "156a21fd1520fc1520fd156a21fe1520fd1520fe156a21ff1520fe1520ff156a21801620ff152080166a2181162080" + "162081166a2182162081162082166a2183162082162083166a2184162083162084166a2185162084162085166a2186" + "162085162086166a2187162086162087166a2188162087162088166a2189162088162089166a218a16208916208a16" + "6a218b16208a16208b166a218c16208b16208c166a218d16208c16208d166a218e16208d16208e166a218f16208e16" + "208f166a219016208f162090166a2191162090162091166a2192162091162092166a2193162092162093166a219416" + "2093162094166a2195162094162095166a2196162095162096166a2197162096162097166a2198162097162098166a" + "2199162098162099166a219a16209916209a166a219b16209a16209b166a219c16209b16209c166a219d16209c1620" + "9d166a219e16209d16209e166a219f16209e16209f166a21a016209f1620a0166a21a11620a01620a1166a21a21620" + "a11620a2166a21a31620a21620a3166a21a41620a31620a4166a21a51620a41620a5166a21a61620a51620a6166a21" + "a71620a61620a7166a21a81620a71620a8166a21a91620a81620a9166a21aa1620a91620aa166a21ab1620aa1620ab" + "166a21ac1620ab1620ac166a21ad1620ac1620ad166a21ae1620ad1620ae166a21af1620ae1620af166a21b01620af" + "1620b0166a21b11620b01620b1166a21b21620b11620b2166a21b31620b21620b3166a21b41620b31620b4166a21b5" + "1620b41620b5166a21b61620b51620b6166a21b71620b61620b7166a21b81620b71620b8166a21b91620b81620b916" + "6a21ba1620b91620ba166a21bb1620ba1620bb166a21bc1620bb1620bc166a21bd1620bc1620bd166a21be1620bd16" + "20be166a21bf1620be1620bf166a21c01620bf1620c0166a21c11620c01620c1166a21c21620c11620c2166a21c316" + "20c21620c3166a21c41620c31620c4166a21c51620c41620c5166a21c61620c51620c6166a21c71620c61620c7166a" + "21c81620c71620c8166a21c91620c81620c9166a21ca1620c91620ca166a21cb1620ca1620cb166a21cc1620cb1620" + "cc166a21cd1620cc1620cd166a21ce1620cd1620ce166a21cf1620ce1620cf166a21d01620cf1620d0166a21d11620" + "d01620d1166a21d21620d11620d2166a21d31620d21620d3166a21d41620d31620d4166a21d51620d41620d5166a21" + "d61620d51620d6166a21d71620d61620d7166a21d81620d71620d8166a21d91620d81620d9166a21da1620d91620da" + "166a21db1620da1620db166a21dc1620db1620dc166a21dd1620dc1620dd166a21de1620dd1620de166a21df1620de" + "1620df166a21e01620df1620e0166a21e11620e01620e1166a21e21620e11620e2166a21e31620e21620e3166a21e4" + "1620e31620e4166a21e51620e41620e5166a21e61620e51620e6166a21e71620e61620e7166a21e81620e71620e816" + "6a21e91620e81620e9166a21ea1620e91620ea166a21eb1620ea1620eb166a21ec1620eb1620ec166a21ed1620ec16" + "20ed166a21ee1620ed1620ee166a21ef1620ee1620ef166a21f01620ef1620f0166a21f11620f01620f1166a21f216" + "20f11620f2166a21f31620f21620f3166a21f41620f31620f4166a21f51620f41620f5166a21f61620f51620f6166a" + "21f71620f61620f7166a21f81620f71620f8166a21f91620f81620f9166a21fa1620f91620fa166a21fb1620fa1620" + "fb166a21fc1620fb1620fc166a21fd1620fc1620fd166a21fe1620fd1620fe166a21ff1620fe1620ff166a21801720" + "ff162080176a2181172080172081176a2182172081172082176a2183172082172083176a2184172083172084176a21" + "85172084172085176a2186172085172086176a2187172086172087176a2188172087172088176a2189172088172089" + "176a218a17208917208a176a218b17208a17208b176a218c17208b17208c176a218d17208c17208d176a218e17208d" + "17208e176a218f17208e17208f176a219017208f172090176a2191172090172091176a2192172091172092176a2193" + "172092172093176a2194172093172094176a2195172094172095176a2196172095172096176a219717209617209717" + "6a2198172097172098176a2199172098172099176a219a17209917209a176a219b17209a17209b176a219c17209b17" + "209c176a219d17209c17209d176a219e17209d17209e176a219f17209e17209f176a21a017209f1720a0176a21a117" + "20a01720a1176a21a21720a11720a2176a21a31720a21720a3176a21a41720a31720a4176a21a51720a41720a5176a" + "21a61720a51720a6176a21a71720a61720a7176a21a81720a71720a8176a21a91720a81720a9176a21aa1720a91720" + "aa176a21ab1720aa1720ab176a21ac1720ab1720ac176a21ad1720ac1720ad176a21ae1720ad1720ae176a21af1720" + "ae1720af176a21b01720af1720b0176a21b11720b01720b1176a21b21720b11720b2176a21b31720b21720b3176a21" + "b41720b31720b4176a21b51720b41720b5176a21b61720b51720b6176a21b71720b61720b7176a21b81720b71720b8" + "176a21b91720b81720b9176a21ba1720b91720ba176a21bb1720ba1720bb176a21bc1720bb1720bc176a21bd1720bc" + "1720bd176a21be1720bd1720be176a21bf1720be1720bf176a21c01720bf1720c0176a21c11720c01720c1176a21c2" + "1720c11720c2176a21c31720c21720c3176a21c41720c31720c4176a21c51720c41720c5176a21c61720c51720c617" + "6a21c71720c61720c7176a21c81720c71720c8176a21c91720c81720c9176a21ca1720c91720ca176a21cb1720ca17" + "20cb176a21cc1720cb1720cc176a21cd1720cc1720cd176a21ce1720cd1720ce176a21cf1720ce1720cf176a21d017" + "20cf1720d0176a21d11720d01720d1176a21d21720d11720d2176a21d31720d21720d3176a21d41720d31720d4176a" + "21d51720d41720d5176a21d61720d51720d6176a21d71720d61720d7176a21d81720d71720d8176a21d91720d81720" + "d9176a21da1720d91720da176a21db1720da1720db176a21dc1720db1720dc176a21dd1720dc1720dd176a21de1720" + "dd1720de176a21df1720de1720df176a21e01720df1720e0176a21e11720e01720e1176a21e21720e11720e2176a21" + "e31720e21720e3176a21e41720e31720e4176a21e51720e41720e5176a21e61720e51720e6176a21e71720e61720e7" + "176a21e81720e71720e8176a21e91720e81720e9176a21ea1720e91720ea176a21eb1720ea1720eb176a21ec1720eb" + "1720ec176a21ed1720ec1720ed176a21ee1720ed1720ee176a21ef1720ee1720ef176a21f01720ef1720f0176a21f1" + "1720f01720f1176a21f21720f11720f2176a21f31720f21720f3176a21f41720f31720f4176a21f51720f41720f517" + "6a21f61720f51720f6176a21f71720f61720f7176a21f81720f71720f8176a21f91720f81720f9176a21fa1720f917" + "20fa176a21fb1720fa1720fb176a21fc1720fb1720fc176a21fd1720fc1720fd176a21fe1720fd1720fe176a21ff17" + "20fe1720ff176a21801820ff172080186a2181182080182081186a2182182081182082186a2183182082182083186a" + "2184182083182084186a2185182084182085186a2186182085182086186a2187182086182087186a21881820871820" + "88186a2189182088182089186a218a18208918208a186a218b18208a18208b186a218c18208b18208c186a218d1820" + "8c18208d186a218e18208d18208e186a218f18208e18208f186a219018208f182090186a2191182090182091186a21" + "92182091182092186a2193182092182093186a2194182093182094186a2195182094182095186a2196182095182096" + "186a2197182096182097186a2198182097182098186a2199182098182099186a219a18209918209a186a219b18209a" + "18209b186a219c18209b18209c186a219d18209c18209d186a219e18209d18209e186a219f18209e18209f186a21a0" + "18209f1820a0186a21a11820a01820a1186a21a21820a11820a2186a21a31820a21820a3186a21a41820a31820a418" + "6a21a51820a41820a5186a21a61820a51820a6186a21a71820a61820a7186a21a81820a71820a8186a21a91820a818" + "20a9186a21aa1820a91820aa186a21ab1820aa1820ab186a21ac1820ab1820ac186a21ad1820ac1820ad186a21ae18" + "20ad1820ae186a21af1820ae1820af186a21b01820af1820b0186a21b11820b01820b1186a21b21820b11820b2186a" + "21b31820b21820b3186a21b41820b31820b4186a21b51820b41820b5186a21b61820b51820b6186a21b71820b61820" + "b7186a21b81820b71820b8186a21b91820b81820b9186a21ba1820b91820ba186a21bb1820ba1820bb186a21bc1820" + "bb1820bc186a21bd1820bc1820bd186a21be1820bd1820be186a21bf1820be1820bf186a21c01820bf1820c0186a21" + "c11820c01820c1186a21c21820c11820c2186a21c31820c21820c3186a21c41820c31820c4186a21c51820c41820c5" + "186a21c61820c51820c6186a21c71820c61820c7186a21c81820c71820c8186a21c91820c81820c9186a21ca1820c9" + "1820ca186a21cb1820ca1820cb186a21cc1820cb1820cc186a21cd1820cc1820cd186a21ce1820cd1820ce186a21cf" + "1820ce1820cf186a21d01820cf1820d0186a21d11820d01820d1186a21d21820d11820d2186a21d31820d21820d318" + "6a21d41820d31820d4186a21d51820d41820d5186a21d61820d51820d6186a21d71820d61820d7186a21d81820d718" + "20d8186a21d91820d81820d9186a21da1820d91820da186a21db1820da1820db186a21dc1820db1820dc186a21dd18" + "20dc1820dd186a21de1820dd1820de186a21df1820de1820df186a21e01820df1820e0186a21e11820e01820e1186a" + "21e21820e11820e2186a21e31820e21820e3186a21e41820e31820e4186a21e51820e41820e5186a21e61820e51820" + "e6186a21e71820e61820e7186a21e81820e71820e8186a21e91820e81820e9186a21ea1820e91820ea186a21eb1820" + "ea1820eb186a21ec1820eb1820ec186a21ed1820ec1820ed186a21ee1820ed1820ee186a21ef1820ee1820ef186a21" + "f01820ef1820f0186a21f11820f01820f1186a21f21820f11820f2186a21f31820f21820f3186a21f41820f31820f4" + "186a21f51820f41820f5186a21f61820f51820f6186a21f71820f61820f7186a21f81820f71820f8186a21f91820f8" + "1820f9186a21fa1820f91820fa186a21fb1820fa1820fb186a21fc1820fb1820fc186a21fd1820fc1820fd186a21fe" + "1820fd1820fe186a21ff1820fe1820ff186a21801920ff182080196a2181192080192081196a218219208119208219" + "6a2183192082192083196a2184192083192084196a2185192084192085196a2186192085192086196a218719208619" + "2087196a2188192087192088196a2189192088192089196a218a19208919208a196a218b19208a19208b196a218c19" + "208b19208c196a218d19208c19208d196a218e19208d19208e196a218f19208e19208f196a219019208f192090196a" + "2191192090192091196a2192192091192092196a2193192092192093196a2194192093192094196a21951920941920" + "95196a2196192095192096196a2197192096192097196a2198192097192098196a2199192098192099196a219a1920" + "9919209a196a219b19209a19209b196a219c19209b19209c196a219d19209c19209d196a219e19209d19209e196a21" + "9f19209e19209f196a21a019209f1920a0196a21a11920a01920a1196a21a21920a11920a2196a21a31920a21920a3" + "196a21a41920a31920a4196a21a51920a41920a5196a21a61920a51920a6196a21a71920a61920a7196a21a81920a7" + "1920a8196a21a91920a81920a9196a21aa1920a91920aa196a21ab1920aa1920ab196a21ac1920ab1920ac196a21ad" + "1920ac1920ad196a21ae1920ad1920ae196a21af1920ae1920af196a21b01920af1920b0196a21b11920b01920b119" + "6a21b21920b11920b2196a21b31920b21920b3196a21b41920b31920b4196a21b51920b41920b5196a21b61920b519" + "20b6196a21b71920b61920b7196a21b81920b71920b8196a21b91920b81920b9196a21ba1920b91920ba196a21bb19" + "20ba1920bb196a21bc1920bb1920bc196a21bd1920bc1920bd196a21be1920bd1920be196a21bf1920be1920bf196a" + "21c01920bf1920c0196a21c11920c01920c1196a21c21920c11920c2196a21c31920c21920c3196a21c41920c31920" + "c4196a21c51920c41920c5196a21c61920c51920c6196a21c71920c61920c7196a21c81920c71920c8196a21c91920" + "c81920c9196a21ca1920c91920ca196a21cb1920ca1920cb196a21cc1920cb1920cc196a21cd1920cc1920cd196a21" + "ce1920cd1920ce196a21cf1920ce1920cf196a21d01920cf1920d0196a21d11920d01920d1196a21d21920d11920d2" + "196a21d31920d21920d3196a21d41920d31920d4196a21d51920d41920d5196a21d61920d51920d6196a21d71920d6" + "1920d7196a21d81920d71920d8196a21d91920d81920d9196a21da1920d91920da196a21db1920da1920db196a21dc" + "1920db1920dc196a21dd1920dc1920dd196a21de1920dd1920de196a21df1920de1920df196a21e01920df1920e019" + "6a21e11920e01920e1196a21e21920e11920e2196a21e31920e21920e3196a21e41920e31920e4196a21e51920e419" + "20e5196a21e61920e51920e6196a21e71920e61920e7196a21e81920e71920e8196a21e91920e81920e9196a21ea19" + "20e91920ea196a21eb1920ea1920eb196a21ec1920eb1920ec196a21ed1920ec1920ed196a21ee1920ed1920ee196a" + "21ef1920ee1920ef196a21f01920ef1920f0196a21f11920f01920f1196a21f21920f11920f2196a21f31920f21920" + "f3196a21f41920f31920f4196a21f51920f41920f5196a21f61920f51920f6196a21f71920f61920f7196a21f81920" + "f71920f8196a21f91920f81920f9196a21fa1920f91920fa196a21fb1920fa1920fb196a21fc1920fb1920fc196a21" + "fd1920fc1920fd196a21fe1920fd1920fe196a21ff1920fe1920ff196a21801a20ff1920801a6a21811a20801a2081" + "1a6a21821a20811a20821a6a21831a20821a20831a6a21841a20831a20841a6a21851a20841a20851a6a21861a2085" + "1a20861a6a21871a20861a20871a6a21881a20871a20881a6a21891a20881a20891a6a218a1a20891a208a1a6a218b" + "1a208a1a208b1a6a218c1a208b1a208c1a6a218d1a208c1a208d1a6a218e1a208d1a208e1a6a218f1a208e1a208f1a" + "6a21901a208f1a20901a6a21911a20901a20911a6a21921a20911a20921a6a21931a20921a20931a6a21941a20931a" + "20941a6a21951a20941a20951a6a21961a20951a20961a6a21971a20961a20971a6a21981a20971a20981a6a21991a" + "20981a20991a6a219a1a20991a209a1a6a219b1a209a1a209b1a6a219c1a209b1a209c1a6a219d1a209c1a209d1a6a" + "219e1a209d1a209e1a6a219f1a209e1a209f1a6a21a01a209f1a20a01a6a21a11a20a01a20a11a6a21a21a20a11a20" + "a21a6a21a31a20a21a20a31a6a21a41a20a31a20a41a6a21a51a20a41a20a51a6a21a61a20a51a20a61a6a21a71a20" + "a61a20a71a6a21a81a20a71a20a81a6a21a91a20a81a20a91a6a21aa1a20a91a20aa1a6a21ab1a20aa1a20ab1a6a21" + "ac1a20ab1a20ac1a6a21ad1a20ac1a20ad1a6a21ae1a20ad1a20ae1a6a21af1a20ae1a20af1a6a21b01a20af1a20b0" + "1a6a21b11a20b01a20b11a6a21b21a20b11a20b21a6a21b31a20b21a20b31a6a21b41a20b31a20b41a6a21b51a20b4" + "1a20b51a6a21b61a20b51a20b61a6a21b71a20b61a20b71a6a21b81a20b71a20b81a6a21b91a20b81a20b91a6a21ba" + "1a20b91a20ba1a6a21bb1a20ba1a20bb1a6a21bc1a20bb1a20bc1a6a21bd1a20bc1a20bd1a6a21be1a20bd1a20be1a" + "6a21bf1a20be1a20bf1a6a21c01a20bf1a20c01a6a21c11a20c01a20c11a6a21c21a20c11a20c21a6a21c31a20c21a" + "20c31a6a21c41a20c31a20c41a6a21c51a20c41a20c51a6a21c61a20c51a20c61a6a21c71a20c61a20c71a6a21c81a" + "20c71a20c81a6a21c91a20c81a20c91a6a21ca1a20c91a20ca1a6a21cb1a20ca1a20cb1a6a21cc1a20cb1a20cc1a6a" + "21cd1a20cc1a20cd1a6a21ce1a20cd1a20ce1a6a21cf1a20ce1a20cf1a6a21d01a20cf1a20d01a6a21d11a20d01a20" + "d11a6a21d21a20d11a20d21a6a21d31a20d21a20d31a6a21d41a20d31a20d41a6a21d51a20d41a20d51a6a21d61a20" + "d51a20d61a6a21d71a20d61a20d71a6a21d81a20d71a20d81a6a21d91a20d81a20d91a6a21da1a20d91a20da1a6a21" + "db1a20da1a20db1a6a21dc1a20db1a20dc1a6a21dd1a20dc1a20dd1a6a21de1a20dd1a20de1a6a21df1a20de1a20df" + "1a6a21e01a20df1a20e01a6a21e11a20e01a20e11a6a21e21a20e11a20e21a6a21e31a20e21a20e31a6a21e41a20e3" + "1a20e41a6a21e51a20e41a20e51a6a21e61a20e51a20e61a6a21e71a20e61a20e71a6a21e81a20e71a20e81a6a21e9" + "1a20e81a20e91a6a21ea1a20e91a20ea1a6a21eb1a20ea1a20eb1a6a21ec1a20eb1a20ec1a6a21ed1a20ec1a20ed1a" + "6a21ee1a20ed1a20ee1a6a21ef1a20ee1a20ef1a6a21f01a20ef1a20f01a6a21f11a20f01a20f11a6a21f21a20f11a" + "20f21a6a21f31a20f21a20f31a6a21f41a20f31a20f41a6a21f51a20f41a20f51a6a21f61a20f51a20f61a6a21f71a" + "20f61a20f71a6a21f81a20f71a20f81a6a21f91a20f81a20f91a6a21fa1a20f91a20fa1a6a21fb1a20fa1a20fb1a6a" + "21fc1a20fb1a20fc1a6a21fd1a20fc1a20fd1a6a21fe1a20fd1a20fe1a6a21ff1a20fe1a20ff1a6a21801b20ff1a20" + "801b6a21811b20801b20811b6a21821b20811b20821b6a21831b20821b20831b6a21841b20831b20841b6a21851b20" + "841b20851b6a21861b20851b20861b6a21871b20861b20871b6a21881b20871b20881b6a21891b20881b20891b6a21" + "8a1b20891b208a1b6a218b1b208a1b208b1b6a218c1b208b1b208c1b6a218d1b208c1b208d1b6a218e1b208d1b208e" + "1b6a218f1b208e1b208f1b6a21901b208f1b20901b6a21911b20901b20911b6a21921b20911b20921b6a21931b2092" + "1b20931b6a21941b20931b20941b6a21951b20941b20951b6a21961b20951b20961b6a21971b20961b20971b6a2198" + "1b20971b20981b6a21991b20981b20991b6a219a1b20991b209a1b6a219b1b209a1b209b1b6a219c1b209b1b209c1b" + "6a219d1b209c1b209d1b6a219e1b209d1b209e1b6a219f1b209e1b209f1b6a21a01b209f1b20a01b6a21a11b20a01b" + "20a11b6a21a21b20a11b20a21b6a21a31b20a21b20a31b6a21a41b20a31b20a41b6a21a51b20a41b20a51b6a21a61b" + "20a51b20a61b6a21a71b20a61b20a71b6a21a81b20a71b20a81b6a21a91b20a81b20a91b6a21aa1b20a91b20aa1b6a" + "21ab1b20aa1b20ab1b6a21ac1b20ab1b20ac1b6a21ad1b20ac1b20ad1b6a21ae1b20ad1b20ae1b6a21af1b20ae1b20" + "af1b6a21b01b20af1b20b01b6a21b11b20b01b20b11b6a21b21b20b11b20b21b6a21b31b20b21b20b31b6a21b41b20" + "b31b20b41b6a21b51b20b41b20b51b6a21b61b20b51b20b61b6a21b71b20b61b20b71b6a21b81b20b71b20b81b6a21" + "b91b20b81b20b91b6a21ba1b20b91b20ba1b6a21bb1b20ba1b20bb1b6a21bc1b20bb1b20bc1b6a21bd1b20bc1b20bd" + "1b6a21be1b20bd1b20be1b6a21bf1b20be1b20bf1b6a21c01b20bf1b20c01b6a21c11b20c01b20c11b6a21c21b20c1" + "1b20c21b6a21c31b20c21b20c31b6a21c41b20c31b20c41b6a21c51b20c41b20c51b6a21c61b20c51b20c61b6a21c7" + "1b20c61b20c71b6a21c81b20c71b20c81b6a21c91b20c81b20c91b6a21ca1b20c91b20ca1b6a21cb1b20ca1b20cb1b" + "6a21cc1b20cb1b20cc1b6a21cd1b20cc1b20cd1b6a21ce1b20cd1b20ce1b6a21cf1b20ce1b20cf1b6a21d01b20cf1b" + "20d01b6a21d11b20d01b20d11b6a21d21b20d11b20d21b6a21d31b20d21b20d31b6a21d41b20d31b20d41b6a21d51b" + "20d41b20d51b6a21d61b20d51b20d61b6a21d71b20d61b20d71b6a21d81b20d71b20d81b6a21d91b20d81b20d91b6a" + "21da1b20d91b20da1b6a21db1b20da1b20db1b6a21dc1b20db1b20dc1b6a21dd1b20dc1b20dd1b6a21de1b20dd1b20" + "de1b6a21df1b20de1b20df1b6a21e01b20df1b20e01b6a21e11b20e01b20e11b6a21e21b20e11b20e21b6a21e31b20" + "e21b20e31b6a21e41b20e31b20e41b6a21e51b20e41b20e51b6a21e61b20e51b20e61b6a21e71b20e61b20e71b6a21" + "e81b20e71b20e81b6a21e91b20e81b20e91b6a21ea1b20e91b20ea1b6a21eb1b20ea1b20eb1b6a21ec1b20eb1b20ec" + "1b6a21ed1b20ec1b20ed1b6a21ee1b20ed1b20ee1b6a21ef1b20ee1b20ef1b6a21f01b20ef1b20f01b6a21f11b20f0" + "1b20f11b6a21f21b20f11b20f21b6a21f31b20f21b20f31b6a21f41b20f31b20f41b6a21f51b20f41b20f51b6a21f6" + "1b20f51b20f61b6a21f71b20f61b20f71b6a21f81b20f71b20f81b6a21f91b20f81b20f91b6a21fa1b20f91b20fa1b" + "6a21fb1b20fa1b20fb1b6a21fc1b20fb1b20fc1b6a21fd1b20fc1b20fd1b6a21fe1b20fd1b20fe1b6a21ff1b20fe1b" + "20ff1b6a21801c20ff1b20801c6a21811c20801c20811c6a21821c20811c20821c6a21831c20821c20831c6a21841c" + "20831c20841c6a21851c20841c20851c6a21861c20851c20861c6a21871c20861c20871c6a21881c20871c20881c6a" + "21891c20881c20891c6a218a1c20891c208a1c6a218b1c208a1c208b1c6a218c1c208b1c208c1c6a218d1c208c1c20" + "8d1c6a218e1c208d1c208e1c6a218f1c208e1c208f1c6a21901c208f1c20901c6a21911c20901c20911c6a21921c20" + "911c20921c6a21931c20921c20931c6a21941c20931c20941c6a21951c20941c20951c6a21961c20951c20961c6a21" + "971c20961c20971c6a21981c20971c20981c6a21991c20981c20991c6a219a1c20991c209a1c6a219b1c209a1c209b" + "1c6a219c1c209b1c209c1c6a219d1c209c1c209d1c6a219e1c209d1c209e1c6a219f1c209e1c209f1c6a21a01c209f" + "1c20a01c6a21a11c20a01c20a11c6a21a21c20a11c20a21c6a21a31c20a21c20a31c6a21a41c20a31c20a41c6a21a5" + "1c20a41c20a51c6a21a61c20a51c20a61c6a21a71c20a61c20a71c6a21a81c20a71c20a81c6a21a91c20a81c20a91c" + "6a21aa1c20a91c20aa1c6a21ab1c20aa1c20ab1c6a21ac1c20ab1c20ac1c6a21ad1c20ac1c20ad1c6a21ae1c20ad1c" + "20ae1c6a21af1c20ae1c20af1c6a21b01c20af1c20b01c6a21b11c20b01c20b11c6a21b21c20b11c20b21c6a21b31c" + "20b21c20b31c6a21b41c20b31c20b41c6a21b51c20b41c20b51c6a21b61c20b51c20b61c6a21b71c20b61c20b71c6a" + "21b81c20b71c20b81c6a21b91c20b81c20b91c6a21ba1c20b91c20ba1c6a21bb1c20ba1c20bb1c6a21bc1c20bb1c20" + "bc1c6a21bd1c20bc1c20bd1c6a21be1c20bd1c20be1c6a21bf1c20be1c20bf1c6a21c01c20bf1c20c01c6a21c11c20" + "c01c20c11c6a21c21c20c11c20c21c6a21c31c20c21c20c31c6a21c41c20c31c20c41c6a21c51c20c41c20c51c6a21" + "c61c20c51c20c61c6a21c71c20c61c20c71c6a21c81c20c71c20c81c6a21c91c20c81c20c91c6a21ca1c20c91c20ca" + "1c6a21cb1c20ca1c20cb1c6a21cc1c20cb1c20cc1c6a21cd1c20cc1c20cd1c6a21ce1c20cd1c20ce1c6a21cf1c20ce" + "1c20cf1c6a21d01c20cf1c20d01c6a21d11c20d01c20d11c6a21d21c20d11c20d21c6a21d31c20d21c20d31c6a21d4" + "1c20d31c20d41c6a21d51c20d41c20d51c6a21d61c20d51c20d61c6a21d71c20d61c20d71c6a21d81c20d71c20d81c" + "6a21d91c20d81c20d91c6a21da1c20d91c20da1c6a21db1c20da1c20db1c6a21dc1c20db1c20dc1c6a21dd1c20dc1c" + "20dd1c6a21de1c20dd1c20de1c6a21df1c20de1c20df1c6a21e01c20df1c20e01c6a21e11c20e01c20e11c6a21e21c" + "20e11c20e21c6a21e31c20e21c20e31c6a21e41c20e31c20e41c6a21e51c20e41c20e51c6a21e61c20e51c20e61c6a" + "21e71c20e61c20e71c6a21e81c20e71c20e81c6a21e91c20e81c20e91c6a21ea1c20e91c20ea1c6a21eb1c20ea1c20" + "eb1c6a21ec1c20eb1c20ec1c6a21ed1c20ec1c20ed1c6a21ee1c20ed1c20ee1c6a21ef1c20ee1c20ef1c6a21f01c20" + "ef1c20f01c6a21f11c20f01c20f11c6a21f21c20f11c20f21c6a21f31c20f21c20f31c6a21f41c20f31c20f41c6a21" + "f51c20f41c20f51c6a21f61c20f51c20f61c6a21f71c20f61c20f71c6a21f81c20f71c20f81c6a21f91c20f81c20f9" + "1c6a21fa1c20f91c20fa1c6a21fb1c20fa1c20fb1c6a21fc1c20fb1c20fc1c6a21fd1c20fc1c20fd1c6a21fe1c20fd" + "1c20fe1c6a21ff1c20fe1c20ff1c6a21801d20ff1c20801d6a21811d20801d20811d6a21821d20811d20821d6a2183" + "1d20821d20831d6a21841d20831d20841d6a21851d20841d20851d6a21861d20851d20861d6a21871d20861d20871d" + "6a21881d20871d20881d6a21891d20881d20891d6a218a1d20891d208a1d6a218b1d208a1d208b1d6a218c1d208b1d" + "208c1d6a218d1d208c1d208d1d6a218e1d208d1d208e1d6a218f1d208e1d208f1d6a21901d208f1d20901d6a21911d" + "20901d20911d6a21921d20911d20921d6a21931d20921d20931d6a21941d20931d20941d6a21951d20941d20951d6a" + "21961d20951d20961d6a21971d20961d20971d6a21981d20971d20981d6a21991d20981d20991d6a219a1d20991d20" + "9a1d6a219b1d209a1d209b1d6a219c1d209b1d209c1d6a219d1d209c1d209d1d6a219e1d209d1d209e1d6a219f1d20" + "9e1d209f1d6a21a01d209f1d20a01d6a21a11d20a01d20a11d6a21a21d20a11d20a21d6a21a31d20a21d20a31d6a21" + "a41d20a31d20a41d6a21a51d20a41d20a51d6a21a61d20a51d20a61d6a21a71d20a61d20a71d6a21a81d20a71d20a8" + "1d6a21a91d20a81d20a91d6a21aa1d20a91d20aa1d6a21ab1d20aa1d20ab1d6a21ac1d20ab1d20ac1d6a21ad1d20ac" + "1d20ad1d6a21ae1d20ad1d20ae1d6a21af1d20ae1d20af1d6a21b01d20af1d20b01d6a21b11d20b01d20b11d6a21b2" + "1d20b11d20b21d6a21b31d20b21d20b31d6a21b41d20b31d20b41d6a21b51d20b41d20b51d6a21b61d20b51d20b61d" + "6a21b71d20b61d20b71d6a21b81d20b71d20b81d6a21b91d20b81d20b91d6a21ba1d20b91d20ba1d6a21bb1d20ba1d" + "20bb1d6a21bc1d20bb1d20bc1d6a21bd1d20bc1d20bd1d6a21be1d20bd1d20be1d6a21bf1d20be1d20bf1d6a21c01d" + "20bf1d20c01d6a21c11d20c01d20c11d6a21c21d20c11d20c21d6a21c31d20c21d20c31d6a21c41d20c31d20c41d6a" + "21c51d20c41d20c51d6a21c61d20c51d20c61d6a21c71d20c61d20c71d6a21c81d20c71d20c81d6a21c91d20c81d20" + "c91d6a21ca1d20c91d20ca1d6a21cb1d20ca1d20cb1d6a21cc1d20cb1d20cc1d6a21cd1d20cc1d20cd1d6a21ce1d20" + "cd1d20ce1d6a21cf1d20ce1d20cf1d6a21d01d20cf1d20d01d6a21d11d20d01d20d11d6a21d21d20d11d20d21d6a21" + "d31d20d21d20d31d6a21d41d20d31d20d41d6a21d51d20d41d20d51d6a21d61d20d51d20d61d6a21d71d20d61d20d7" + "1d6a21d81d20d71d20d81d6a21d91d20d81d20d91d6a21da1d20d91d20da1d6a21db1d20da1d20db1d6a21dc1d20db" + "1d20dc1d6a21dd1d20dc1d20dd1d6a21de1d20dd1d20de1d6a21df1d20de1d20df1d6a21e01d20df1d20e01d6a21e1" + "1d20e01d20e11d6a21e21d20e11d20e21d6a21e31d20e21d20e31d6a21e41d20e31d20e41d6a21e51d20e41d20e51d" + "6a21e61d20e51d20e61d6a21e71d20e61d20e71d6a21e81d20e71d20e81d6a21e91d20e81d20e91d6a21ea1d20e91d" + "20ea1d6a21eb1d20ea1d20eb1d6a21ec1d20eb1d20ec1d6a21ed1d20ec1d20ed1d6a21ee1d20ed1d20ee1d6a21ef1d" + "20ee1d20ef1d6a21f01d20ef1d20f01d6a21f11d20f01d20f11d6a21f21d20f11d20f21d6a21f31d20f21d20f31d6a" + "21f41d20f31d20f41d6a21f51d20f41d20f51d6a21f61d20f51d20f61d6a21f71d20f61d20f71d6a21f81d20f71d20" + "f81d6a21f91d20f81d20f91d6a21fa1d20f91d20fa1d6a21fb1d20fa1d20fb1d6a21fc1d20fb1d20fc1d6a21fd1d20" + "fc1d20fd1d6a21fe1d20fd1d20fe1d6a21ff1d20fe1d20ff1d6a21801e20ff1d20801e6a21811e20801e20811e6a21" + "821e20811e20821e6a21831e20821e20831e6a21841e20831e20841e6a21851e20841e20851e6a21861e20851e2086" + "1e6a21871e20861e20871e6a21881e20871e20881e6a21891e20881e20891e6a218a1e20891e208a1e6a218b1e208a" + "1e208b1e6a218c1e208b1e208c1e6a218d1e208c1e208d1e6a218e1e208d1e208e1e6a218f1e208e1e208f1e6a2190" + "1e208f1e20901e6a21911e20901e20911e6a21921e20911e20921e6a21931e20921e20931e6a21941e20931e20941e" + "6a21951e20941e20951e6a21961e20951e20961e6a21971e20961e20971e6a21981e20971e20981e6a21991e20981e" + "20991e6a219a1e20991e209a1e6a219b1e209a1e209b1e6a219c1e209b1e209c1e6a219d1e209c1e209d1e6a219e1e" + "209d1e209e1e6a219f1e209e1e209f1e6a21a01e209f1e20a01e6a21a11e20a01e20a11e6a21a21e20a11e20a21e6a" + "21a31e20a21e20a31e6a21a41e20a31e20a41e6a21a51e20a41e20a51e6a21a61e20a51e20a61e6a21a71e20a61e20" + "a71e6a21a81e20a71e20a81e6a21a91e20a81e20a91e6a21aa1e20a91e20aa1e6a21ab1e20aa1e20ab1e6a21ac1e20" + "ab1e20ac1e6a21ad1e20ac1e20ad1e6a21ae1e20ad1e20ae1e6a21af1e20ae1e20af1e6a21b01e20af1e20b01e6a21" + "b11e20b01e20b11e6a21b21e20b11e20b21e6a21b31e20b21e20b31e6a21b41e20b31e20b41e6a21b51e20b41e20b5" + "1e6a21b61e20b51e20b61e6a21b71e20b61e20b71e6a21b81e20b71e20b81e6a21b91e20b81e20b91e6a21ba1e20b9" + "1e20ba1e6a21bb1e20ba1e20bb1e6a21bc1e20bb1e20bc1e6a21bd1e20bc1e20bd1e6a21be1e20bd1e20be1e6a21bf" + "1e20be1e20bf1e6a21c01e20bf1e20c01e6a21c11e20c01e20c11e6a21c21e20c11e20c21e6a21c31e20c21e20c31e" + "6a21c41e20c31e20c41e6a21c51e20c41e20c51e6a21c61e20c51e20c61e6a21c71e20c61e20c71e6a21c81e20c71e" + "20c81e6a21c91e20c81e20c91e6a21ca1e20c91e20ca1e6a21cb1e20ca1e20cb1e6a21cc1e20cb1e20cc1e6a21cd1e" + "20cc1e20cd1e6a21ce1e20cd1e20ce1e6a21cf1e20ce1e20cf1e6a21d01e20cf1e20d01e6a21d11e20d01e20d11e6a" + "21d21e20d11e20d21e6a21d31e20d21e20d31e6a21d41e20d31e20d41e6a21d51e20d41e20d51e6a21d61e20d51e20" + "d61e6a21d71e20d61e20d71e6a21d81e20d71e20d81e6a21d91e20d81e20d91e6a21da1e20d91e20da1e6a21db1e20" + "da1e20db1e6a21dc1e20db1e20dc1e6a21dd1e20dc1e20dd1e6a21de1e20dd1e20de1e6a21df1e20de1e20df1e6a21" + "e01e20df1e20e01e6a21e11e20e01e20e11e6a21e21e20e11e20e21e6a21e31e20e21e20e31e6a21e41e20e31e20e4" + "1e6a21e51e20e41e20e51e6a21e61e20e51e20e61e6a21e71e20e61e20e71e6a21e81e20e71e20e81e6a21e91e20e8" + "1e20e91e6a21ea1e20e91e20ea1e6a21eb1e20ea1e20eb1e6a21ec1e20eb1e20ec1e6a21ed1e20ec1e20ed1e6a21ee" + "1e20ed1e20ee1e6a21ef1e20ee1e20ef1e6a21f01e20ef1e20f01e6a21f11e20f01e20f11e6a21f21e20f11e20f21e" + "6a21f31e20f21e20f31e6a21f41e20f31e20f41e6a21f51e20f41e20f51e6a21f61e20f51e20f61e6a21f71e20f61e" + "20f71e6a21f81e20f71e20f81e6a21f91e20f81e20f91e6a21fa1e20f91e20fa1e6a21fb1e20fa1e20fb1e6a21fc1e" + "20fb1e20fc1e6a21fd1e20fc1e20fd1e6a21fe1e20fd1e20fe1e6a21ff1e20fe1e20ff1e6a21801f20ff1e20801f6a" + "21811f20801f20811f6a21821f20811f20821f6a21831f20821f20831f6a21841f20831f20841f6a21851f20841f20" + "851f6a21861f20851f20861f6a21871f20861f20871f6a21881f20871f20881f6a21891f20881f20891f6a218a1f20" + "891f208a1f6a218b1f208a1f208b1f6a218c1f208b1f208c1f6a218d1f208c1f208d1f6a218e1f208d1f208e1f6a21" + "8f1f208e1f208f1f6a21901f208f1f20901f6a21911f20901f20911f6a21921f20911f20921f6a21931f20921f2093" + "1f6a21941f20931f20941f6a21951f20941f20951f6a21961f20951f20961f6a21971f20961f20971f6a21981f2097" + "1f20981f6a21991f20981f20991f6a219a1f20991f209a1f6a219b1f209a1f209b1f6a219c1f209b1f209c1f6a219d" + "1f209c1f209d1f6a219e1f209d1f209e1f6a219f1f209e1f209f1f6a21a01f209f1f20a01f6a21a11f20a01f20a11f" + "6a21a21f20a11f20a21f6a21a31f20a21f20a31f6a21a41f20a31f20a41f6a21a51f20a41f20a51f6a21a61f20a51f" + "20a61f6a21a71f20a61f20a71f6a21a81f20a71f20a81f6a21a91f20a81f20a91f6a21aa1f20a91f20aa1f6a21ab1f" + "20aa1f20ab1f6a21ac1f20ab1f20ac1f6a21ad1f20ac1f20ad1f6a21ae1f20ad1f20ae1f6a21af1f20ae1f20af1f6a" + "21b01f20af1f20b01f6a21b11f20b01f20b11f6a21b21f20b11f20b21f6a21b31f20b21f20b31f6a21b41f20b31f20" + "b41f6a21b51f20b41f20b51f6a21b61f20b51f20b61f6a21b71f20b61f20b71f6a21b81f20b71f20b81f6a21b91f20" + "b81f20b91f6a21ba1f20b91f20ba1f6a21bb1f20ba1f20bb1f6a21bc1f20bb1f20bc1f6a21bd1f20bc1f20bd1f6a21" + "be1f20bd1f20be1f6a21bf1f20be1f20bf1f6a21c01f20bf1f20c01f6a21c11f20c01f20c11f6a21c21f20c11f20c2" + "1f6a21c31f20c21f20c31f6a21c41f20c31f20c41f6a21c51f20c41f20c51f6a21c61f20c51f20c61f6a21c71f20c6" + "1f20c71f6a21c81f20c71f20c81f6a21c91f20c81f20c91f6a21ca1f20c91f20ca1f6a21cb1f20ca1f20cb1f6a21cc" + "1f20cb1f20cc1f6a21cd1f20cc1f20cd1f6a21ce1f20cd1f20ce1f6a21cf1f20ce1f20cf1f6a21d01f20cf1f20d01f" + "6a21d11f20d01f20d11f6a21d21f20d11f20d21f6a21d31f20d21f20d31f6a21d41f20d31f20d41f6a21d51f20d41f" + "20d51f6a21d61f20d51f20d61f6a21d71f20d61f20d71f6a21d81f20d71f20d81f6a21d91f20d81f20d91f6a21da1f" + "20d91f20da1f6a21db1f20da1f20db1f6a21dc1f20db1f20dc1f6a21dd1f20dc1f20dd1f6a21de1f20dd1f20de1f6a" + "21df1f20de1f20df1f6a21e01f20df1f20e01f6a21e11f20e01f20e11f6a21e21f20e11f20e21f6a21e31f20e21f20" + "e31f6a21e41f20e31f20e41f6a21e51f20e41f20e51f6a21e61f20e51f20e61f6a21e71f20e61f20e71f6a21e81f20" + "e71f20e81f6a21e91f20e81f20e91f6a21ea1f20e91f20ea1f6a21eb1f20ea1f20eb1f6a21ec1f20eb1f20ec1f6a21" + "ed1f20ec1f20ed1f6a21ee1f20ed1f20ee1f6a21ef1f20ee1f20ef1f6a21f01f20ef1f20f01f6a21f11f20f01f20f1" + "1f6a21f21f20f11f20f21f6a21f31f20f21f20f31f6a21f41f20f31f20f41f6a21f51f20f41f20f51f6a21f61f20f5" + "1f20f61f6a21f71f20f61f20f71f6a21f81f20f71f20f81f6a21f91f20f81f20f91f6a21fa1f20f91f20fa1f6a21fb" + "1f20fa1f20fb1f6a21fc1f20fb1f20fc1f6a21fd1f20fc1f20fd1f6a21fe1f20fd1f20fe1f6a21ff1f20fe1f20ff1f" + "6a21802020ff1f2080206a2181202080202081206a2182202081202082206a2183202082202083206a218420208320" + "2084206a2185202084202085206a2186202085202086206a2187202086202087206a2188202087202088206a218920" + "2088202089206a218a20208920208a206a218b20208a20208b206a218c20208b20208c206a218d20208c20208d206a" + "218e20208d20208e206a218f20208e20208f206a219020208f202090206a2191202090202091206a21922020912020" + "92206a2193202092202093206a2194202093202094206a2195202094202095206a2196202095202096206a21972020" + "96202097206a2198202097202098206a2199202098202099206a219a20209920209a206a219b20209a20209b206a21" + "9c20209b20209c206a219d20209c20209d206a219e20209d20209e206a219f20209e20209f206a21a020209f2020a0" + "206a21a12020a02020a1206a21a22020a12020a2206a21a32020a22020a3206a21a42020a32020a4206a21a52020a4" + "2020a5206a21a62020a52020a6206a21a72020a62020a7206a21a82020a72020a8206a21a92020a82020a9206a21aa" + "2020a92020aa206a21ab2020aa2020ab206a21ac2020ab2020ac206a21ad2020ac2020ad206a21ae2020ad2020ae20" + "6a21af2020ae2020af206a21b02020af2020b0206a21b12020b02020b1206a21b22020b12020b2206a21b32020b220" + "20b3206a21b42020b32020b4206a21b52020b42020b5206a21b62020b52020b6206a21b72020b62020b7206a21b820" + "20b72020b8206a21b92020b82020b9206a21ba2020b92020ba206a21bb2020ba2020bb206a21bc2020bb2020bc206a" + "21bd2020bc2020bd206a21be2020bd2020be206a21bf2020be2020bf206a21c02020bf2020c0206a21c12020c02020" + "c1206a21c22020c12020c2206a21c32020c22020c3206a21c42020c32020c4206a21c52020c42020c5206a21c62020" + "c52020c6206a21c72020c62020c7206a21c82020c72020c8206a21c92020c82020c9206a21ca2020c92020ca206a21" + "cb2020ca2020cb206a21cc2020cb2020cc206a21cd2020cc2020cd206a21ce2020cd2020ce206a21cf2020ce2020cf" + "206a21d02020cf2020d0206a21d12020d02020d1206a21d22020d12020d2206a21d32020d22020d3206a21d42020d3" + "2020d4206a21d52020d42020d5206a21d62020d52020d6206a21d72020d62020d7206a21d82020d72020d8206a21d9" + "2020d82020d9206a21da2020d92020da206a21db2020da2020db206a21dc2020db2020dc206a21dd2020dc2020dd20" + "6a21de2020dd2020de206a21df2020de2020df206a21e02020df2020e0206a21e12020e02020e1206a21e22020e120" + "20e2206a21e32020e22020e3206a21e42020e32020e4206a21e52020e42020e5206a21e62020e52020e6206a21e720" + "20e62020e7206a21e82020e72020e8206a21e92020e82020e9206a21ea2020e92020ea206a21eb2020ea2020eb206a" + "21ec2020eb2020ec206a21ed2020ec2020ed206a21ee2020ed2020ee206a21ef2020ee2020ef206a21f02020ef2020" + "f0206a21f12020f02020f1206a21f22020f12020f2206a21f32020f22020f3206a21f42020f32020f4206a21f52020" + "f42020f5206a21f62020f52020f6206a21f72020f62020f7206a21f82020f72020f8206a21f92020f82020f9206a21" + "fa2020f92020fa206a21fb2020fa2020fb206a21fc2020fb2020fc206a21fd2020fc2020fd206a21fe2020fd2020fe" + "206a21ff2020fe2020ff206a21802120ff202080216a2181212080212081216a2182212081212082216a2183212082" + "212083216a2184212083212084216a2185212084212085216a2186212085212086216a2187212086212087216a2188" + "212087212088216a2189212088212089216a218a21208921208a216a218b21208a21208b216a218c21208b21208c21" + "6a218d21208c21208d216a218e21208d21208e216a218f21208e21208f216a219021208f212090216a219121209021" + "2091216a2192212091212092216a2193212092212093216a2194212093212094216a2195212094212095216a219621" + "2095212096216a2197212096212097216a2198212097212098216a2199212098212099216a219a21209921209a216a" + "219b21209a21209b216a219c21209b21209c216a219d21209c21209d216a219e21209d21209e216a219f21209e2120" + "9f216a21a021209f2120a0216a21a12120a02120a1216a21a22120a12120a2216a21a32120a22120a3216a21a42120" + "a32120a4216a21a52120a42120a5216a21a62120a52120a6216a21a72120a62120a7216a21a82120a72120a8216a21" + "a92120a82120a9216a21aa2120a92120aa216a21ab2120aa2120ab216a21ac2120ab2120ac216a21ad2120ac2120ad" + "216a21ae2120ad2120ae216a21af2120ae2120af216a21b02120af2120b0216a21b12120b02120b1216a21b22120b1" + "2120b2216a21b32120b22120b3216a21b42120b32120b4216a21b52120b42120b5216a21b62120b52120b6216a21b7" + "2120b62120b7216a21b82120b72120b8216a21b92120b82120b9216a21ba2120b92120ba216a21bb2120ba2120bb21" + "6a21bc2120bb2120bc216a21bd2120bc2120bd216a21be2120bd2120be216a21bf2120be2120bf216a21c02120bf21" + "20c0216a21c12120c02120c1216a21c22120c12120c2216a21c32120c22120c3216a21c42120c32120c4216a21c521" + "20c42120c5216a21c62120c52120c6216a21c72120c62120c7216a21c82120c72120c8216a21c92120c82120c9216a" + "21ca2120c92120ca216a21cb2120ca2120cb216a21cc2120cb2120cc216a21cd2120cc2120cd216a21ce2120cd2120" + "ce216a21cf2120ce2120cf216a21d02120cf2120d0216a21d12120d02120d1216a21d22120d12120d2216a21d32120" + "d22120d3216a21d42120d32120d4216a21d52120d42120d5216a21d62120d52120d6216a21d72120d62120d7216a21" + "d82120d72120d8216a21d92120d82120d9216a21da2120d92120da216a21db2120da2120db216a21dc2120db2120dc" + "216a21dd2120dc2120dd216a21de2120dd2120de216a21df2120de2120df216a21e02120df2120e0216a21e12120e0" + "2120e1216a21e22120e12120e2216a21e32120e22120e3216a21e42120e32120e4216a21e52120e42120e5216a21e6" + "2120e52120e6216a21e72120e62120e7216a21e82120e72120e8216a21e92120e82120e9216a21ea2120e92120ea21" + "6a21eb2120ea2120eb216a21ec2120eb2120ec216a21ed2120ec2120ed216a21ee2120ed2120ee216a21ef2120ee21" + "20ef216a21f02120ef2120f0216a21f12120f02120f1216a21f22120f12120f2216a21f32120f22120f3216a21f421" + "20f32120f4216a21f52120f42120f5216a21f62120f52120f6216a21f72120f62120f7216a21f82120f72120f8216a" + "21f92120f82120f9216a21fa2120f92120fa216a21fb2120fa2120fb216a21fc2120fb2120fc216a21fd2120fc2120" + "fd216a21fe2120fd2120fe216a21ff2120fe2120ff216a21802220ff212080226a2181222080222081226a21822220" + "81222082226a2183222082222083226a2184222083222084226a2185222084222085226a2186222085222086226a21" + "87222086222087226a2188222087222088226a2189222088222089226a218a22208922208a226a218b22208a22208b" + "226a218c22208b22208c226a218d22208c22208d226a218e22208d22208e226a218f22208e22208f226a219022208f" + "222090226a2191222090222091226a2192222091222092226a2193222092222093226a2194222093222094226a2195" + "222094222095226a2196222095222096226a2197222096222097226a2198222097222098226a219922209822209922" + "6a219a22209922209a226a219b22209a22209b226a219c22209b22209c226a219d22209c22209d226a219e22209d22" + "209e226a219f22209e22209f226a21a022209f2220a0226a21a12220a02220a1226a21a22220a12220a2226a21a322" + "20a22220a3226a21a42220a32220a4226a21a52220a42220a5226a21a62220a52220a6226a21a72220a62220a7226a" + "21a82220a72220a8226a21a92220a82220a9226a21aa2220a92220aa226a21ab2220aa2220ab226a21ac2220ab2220" + "ac226a21ad2220ac2220ad226a21ae2220ad2220ae226a21af2220ae2220af226a21b02220af2220b0226a21b12220" + "b02220b1226a21b22220b12220b2226a21b32220b22220b3226a21b42220b32220b4226a21b52220b42220b5226a21" + "b62220b52220b6226a21b72220b62220b7226a21b82220b72220b8226a21b92220b82220b9226a21ba2220b92220ba" + "226a21bb2220ba2220bb226a21bc2220bb2220bc226a21bd2220bc2220bd226a21be2220bd2220be226a21bf2220be" + "2220bf226a21c02220bf2220c0226a21c12220c02220c1226a21c22220c12220c2226a21c32220c22220c3226a21c4" + "2220c32220c4226a21c52220c42220c5226a21c62220c52220c6226a21c72220c62220c7226a21c82220c72220c822" + "6a21c92220c82220c9226a21ca2220c92220ca226a21cb2220ca2220cb226a21cc2220cb2220cc226a21cd2220cc22" + "20cd226a21ce2220cd2220ce226a21cf2220ce2220cf226a21d02220cf2220d0226a21d12220d02220d1226a21d222" + "20d12220d2226a21d32220d22220d3226a21d42220d32220d4226a21d52220d42220d5226a21d62220d52220d6226a" + "21d72220d62220d7226a21d82220d72220d8226a21d92220d82220d9226a21da2220d92220da226a21db2220da2220" + "db226a21dc2220db2220dc226a21dd2220dc2220dd226a21de2220dd2220de226a21df2220de2220df226a21e02220" + "df2220e0226a21e12220e02220e1226a21e22220e12220e2226a21e32220e22220e3226a21e42220e32220e4226a21" + "e52220e42220e5226a21e62220e52220e6226a21e72220e62220e7226a21e82220e72220e8226a21e92220e82220e9" + "226a21ea2220e92220ea226a21eb2220ea2220eb226a21ec2220eb2220ec226a21ed2220ec2220ed226a21ee2220ed" + "2220ee226a21ef2220ee2220ef226a21f02220ef2220f0226a21f12220f02220f1226a21f22220f12220f2226a21f3" + "2220f22220f3226a21f42220f32220f4226a21f52220f42220f5226a21f62220f52220f6226a21f72220f62220f722" + "6a21f82220f72220f8226a21f92220f82220f9226a21fa2220f92220fa226a21fb2220fa2220fb226a21fc2220fb22" + "20fc226a21fd2220fc2220fd226a21fe2220fd2220fe226a21ff2220fe2220ff226a21802320ff222080236a218123" + "2080232081236a2182232081232082236a2183232082232083236a2184232083232084236a2185232084232085236a" + "2186232085232086236a2187232086232087236a2188232087232088236a2189232088232089236a218a2320892320" + "8a236a218b23208a23208b236a218c23208b23208c236a218d23208c23208d236a218e23208d23208e236a218f2320" + "8e23208f236a219023208f232090236a2191232090232091236a2192232091232092236a2193232092232093236a21" + "94232093232094236a2195232094232095236a2196232095232096236a2197232096232097236a2198232097232098" + "236a2199232098232099236a219a23209923209a236a219b23209a23209b236a219c23209b23209c236a219d23209c" + "23209d236a219e23209d23209e236a219f23209e23209f236a21a023209f2320a0236a21a12320a02320a1236a21a2" + "2320a12320a2236a21a32320a22320a3236a21a42320a32320a4236a21a52320a42320a5236a21a62320a52320a623" + "6a21a72320a62320a7236a21a82320a72320a8236a21a92320a82320a9236a21aa2320a92320aa236a21ab2320aa23" + "20ab236a21ac2320ab2320ac236a21ad2320ac2320ad236a21ae2320ad2320ae236a21af2320ae2320af236a21b023" + "20af2320b0236a21b12320b02320b1236a21b22320b12320b2236a21b32320b22320b3236a21b42320b32320b4236a" + "21b52320b42320b5236a21b62320b52320b6236a21b72320b62320b7236a21b82320b72320b8236a21b92320b82320" + "b9236a21ba2320b92320ba236a21bb2320ba2320bb236a21bc2320bb2320bc236a21bd2320bc2320bd236a21be2320" + "bd2320be236a21bf2320be2320bf236a21c02320bf2320c0236a21c12320c02320c1236a21c22320c12320c2236a21" + "c32320c22320c3236a21c42320c32320c4236a21c52320c42320c5236a21c62320c52320c6236a21c72320c62320c7" + "236a21c82320c72320c8236a21c92320c82320c9236a21ca2320c92320ca236a21cb2320ca2320cb236a21cc2320cb" + "2320cc236a21cd2320cc2320cd236a21ce2320cd2320ce236a21cf2320ce2320cf236a21d02320cf2320d0236a21d1" + "2320d02320d1236a21d22320d12320d2236a21d32320d22320d3236a21d42320d32320d4236a21d52320d42320d523" + "6a21d62320d52320d6236a21d72320d62320d7236a21d82320d72320d8236a21d92320d82320d9236a21da2320d923" + "20da236a21db2320da2320db236a21dc2320db2320dc236a21dd2320dc2320dd236a21de2320dd2320de236a21df23" + "20de2320df236a21e02320df2320e0236a21e12320e02320e1236a21e22320e12320e2236a21e32320e22320e3236a" + "21e42320e32320e4236a21e52320e42320e5236a21e62320e52320e6236a21e72320e62320e7236a21e82320e72320" + "e8236a21e92320e82320e9236a21ea2320e92320ea236a21eb2320ea2320eb236a21ec2320eb2320ec236a21ed2320" + "ec2320ed236a21ee2320ed2320ee236a21ef2320ee2320ef236a21f02320ef2320f0236a21f12320f02320f1236a21" + "f22320f12320f2236a21f32320f22320f3236a21f42320f32320f4236a21f52320f42320f5236a21f62320f52320f6" + "236a21f72320f62320f7236a21f82320f72320f8236a21f92320f82320f9236a21fa2320f92320fa236a21fb2320fa" + "2320fb236a21fc2320fb2320fc236a21fd2320fc2320fd236a21fe2320fd2320fe236a21ff2320fe2320ff236a2180" + "2420ff232080246a2181242080242081246a2182242081242082246a2183242082242083246a218424208324208424" + "6a2185242084242085246a2186242085242086246a2187242086242087246a2188242087242088246a218924208824" + "2089246a218a24208924208a246a218b24208a24208b246a218c24208b24208c246a218d24208c24208d246a218e24" + "208d24208e246a218f24208e24208f246a219024208f242090246a2191242090242091246a2192242091242092246a" + "2193242092242093246a2194242093242094246a2195242094242095246a2196242095242096246a21972420962420" + "97246a2198242097242098246a2199242098242099246a219a24209924209a246a219b24209a24209b246a219c2420" + "9b24209c246a219d24209c24209d246a219e24209d24209e246a219f24209e24209f246a21a024209f2420a0246a21" + "a12420a02420a1246a21a22420a12420a2246a21a32420a22420a3246a21a42420a32420a4246a21a52420a42420a5" + "246a21a62420a52420a6246a21a72420a62420a7246a21a82420a72420a8246a21a92420a82420a9246a21aa2420a9" + "2420aa246a21ab2420aa2420ab246a21ac2420ab2420ac246a21ad2420ac2420ad246a21ae2420ad2420ae246a21af" + "2420ae2420af246a21b02420af2420b0246a21b12420b02420b1246a21b22420b12420b2246a21b32420b22420b324" + "6a21b42420b32420b4246a21b52420b42420b5246a21b62420b52420b6246a21b72420b62420b7246a21b82420b724" + "20b8246a21b92420b82420b9246a21ba2420b92420ba246a21bb2420ba2420bb246a21bc2420bb2420bc246a21bd24" + "20bc2420bd246a21be2420bd2420be246a21bf2420be2420bf246a21c02420bf2420c0246a21c12420c02420c1246a" + "21c22420c12420c2246a21c32420c22420c3246a21c42420c32420c4246a21c52420c42420c5246a21c62420c52420" + "c6246a21c72420c62420c7246a21c82420c72420c8246a21c92420c82420c9246a21ca2420c92420ca246a21cb2420" + "ca2420cb246a21cc2420cb2420cc246a21cd2420cc2420cd246a21ce2420cd2420ce246a21cf2420ce2420cf246a21" + "d02420cf2420d0246a21d12420d02420d1246a21d22420d12420d2246a21d32420d22420d3246a21d42420d32420d4" + "246a21d52420d42420d5246a21d62420d52420d6246a21d72420d62420d7246a21d82420d72420d8246a21d92420d8" + "2420d9246a21da2420d92420da246a21db2420da2420db246a21dc2420db2420dc246a21dd2420dc2420dd246a21de" + "2420dd2420de246a21df2420de2420df246a21e02420df2420e0246a21e12420e02420e1246a21e22420e12420e224" + "6a21e32420e22420e3246a21e42420e32420e4246a21e52420e42420e5246a21e62420e52420e6246a21e72420e624" + "20e7246a21e82420e72420e8246a21e92420e82420e9246a21ea2420e92420ea246a21eb2420ea2420eb246a21ec24" + "20eb2420ec246a21ed2420ec2420ed246a21ee2420ed2420ee246a21ef2420ee2420ef246a21f02420ef2420f0246a" + "21f12420f02420f1246a21f22420f12420f2246a21f32420f22420f3246a21f42420f32420f4246a21f52420f42420" + "f5246a21f62420f52420f6246a21f72420f62420f7246a21f82420f72420f8246a21f92420f82420f9246a21fa2420" + "f92420fa246a21fb2420fa2420fb246a21fc2420fb2420fc246a21fd2420fc2420fd246a21fe2420fd2420fe246a21" + "ff2420fe2420ff246a21802520ff242080256a2181252080252081256a2182252081252082256a2183252082252083" + "256a2184252083252084256a2185252084252085256a2186252085252086256a2187252086252087256a2188252087" + "252088256a2189252088252089256a218a25208925208a256a218b25208a25208b256a218c25208b25208c256a218d" + "25208c25208d256a218e25208d25208e256a218f25208e25208f256a219025208f252090256a219125209025209125" + "6a2192252091252092256a2193252092252093256a2194252093252094256a2195252094252095256a219625209525" + "2096256a2197252096252097256a2198252097252098256a2199252098252099256a219a25209925209a256a219b25" + "209a25209b256a219c25209b25209c256a219d25209c25209d256a219e25209d25209e256a219f25209e25209f256a" + "21a025209f2520a0256a21a12520a02520a1256a21a22520a12520a2256a21a32520a22520a3256a21a42520a32520" + "a4256a21a52520a42520a5256a21a62520a52520a6256a21a72520a62520a7256a21a82520a72520a8256a21a92520" + "a82520a9256a21aa2520a92520aa256a21ab2520aa2520ab256a21ac2520ab2520ac256a21ad2520ac2520ad256a21" + "ae2520ad2520ae256a21af2520ae2520af256a21b02520af2520b0256a21b12520b02520b1256a21b22520b12520b2" + "256a21b32520b22520b3256a21b42520b32520b4256a21b52520b42520b5256a21b62520b52520b6256a21b72520b6" + "2520b7256a21b82520b72520b8256a21b92520b82520b9256a21ba2520b92520ba256a21bb2520ba2520bb256a21bc" + "2520bb2520bc256a21bd2520bc2520bd256a21be2520bd2520be256a21bf2520be2520bf256a21c02520bf2520c025" + "6a21c12520c02520c1256a21c22520c12520c2256a21c32520c22520c3256a21c42520c32520c4256a21c52520c425" + "20c5256a21c62520c52520c6256a21c72520c62520c7256a21c82520c72520c8256a21c92520c82520c9256a21ca25" + "20c92520ca256a21cb2520ca2520cb256a21cc2520cb2520cc256a21cd2520cc2520cd256a21ce2520cd2520ce256a" + "21cf2520ce2520cf256a21d02520cf2520d0256a21d12520d02520d1256a21d22520d12520d2256a21d32520d22520" + "d3256a21d42520d32520d4256a21d52520d42520d5256a21d62520d52520d6256a21d72520d62520d7256a21d82520" + "d72520d8256a21d92520d82520d9256a21da2520d92520da256a21db2520da2520db256a21dc2520db2520dc256a21" + "dd2520dc2520dd256a21de2520dd2520de256a21df2520de2520df256a21e02520df2520e0256a21e12520e02520e1" + "256a21e22520e12520e2256a21e32520e22520e3256a21e42520e32520e4256a21e52520e42520e5256a21e62520e5" + "2520e6256a21e72520e62520e7256a21e82520e72520e8256a21e92520e82520e9256a21ea2520e92520ea256a21eb" + "2520ea2520eb256a21ec2520eb2520ec256a21ed2520ec2520ed256a21ee2520ed2520ee256a21ef2520ee2520ef25" + "6a21f02520ef2520f0256a21f12520f02520f1256a21f22520f12520f2256a21f32520f22520f3256a21f42520f325" + "20f4256a21f52520f42520f5256a21f62520f52520f6256a21f72520f62520f7256a21f82520f72520f8256a21f925" + "20f82520f9256a21fa2520f92520fa256a21fb2520fa2520fb256a21fc2520fb2520fc256a21fd2520fc2520fd256a" + "21fe2520fd2520fe256a21ff2520fe2520ff256a21802620ff252080266a2181262080262081266a21822620812620" + "82266a2183262082262083266a2184262083262084266a2185262084262085266a2186262085262086266a21872620" + "86262087266a2188262087262088266a2189262088262089266a218a26208926208a266a218b26208a26208b266a21" + "8c26208b26208c266a218d26208c26208d266a218e26208d26208e266a218f26208e26208f266a219026208f262090" + "266a2191262090262091266a2192262091262092266a2193262092262093266a2194262093262094266a2195262094" + "262095266a2196262095262096266a2197262096262097266a2198262097262098266a2199262098262099266a219a" + "26209926209a266a219b26209a26209b266a219c26209b26209c266a219d26209c26209d266a219e26209d26209e26" + "6a219f26209e26209f266a21a026209f2620a0266a21a12620a02620a1266a21a22620a12620a2266a21a32620a226" + "20a3266a21a42620a32620a4266a21a52620a42620a5266a21a62620a52620a6266a21a72620a62620a7266a21a826" + "20a72620a8266a21a92620a82620a9266a21aa2620a92620aa266a21ab2620aa2620ab266a21ac2620ab2620ac266a" + "21ad2620ac2620ad266a21ae2620ad2620ae266a21af2620ae2620af266a21b02620af2620b0266a21b12620b02620" + "b1266a21b22620b12620b2266a21b32620b22620b3266a21b42620b32620b4266a21b52620b42620b5266a21b62620" + "b52620b6266a21b72620b62620b7266a21b82620b72620b8266a21b92620b82620b9266a21ba2620b92620ba266a21" + "bb2620ba2620bb266a21bc2620bb2620bc266a21bd2620bc2620bd266a21be2620bd2620be266a21bf2620be2620bf" + "266a21c02620bf2620c0266a21c12620c02620c1266a21c22620c12620c2266a21c32620c22620c3266a21c42620c3" + "2620c4266a21c52620c42620c5266a21c62620c52620c6266a21c72620c62620c7266a21c82620c72620c8266a21c9" + "2620c82620c9266a21ca2620c92620ca266a21cb2620ca2620cb266a21cc2620cb2620cc266a21cd2620cc2620cd26" + "6a21ce2620cd2620ce266a21cf2620ce2620cf266a21d02620cf2620d0266a21d12620d02620d1266a21d22620d126" + "20d2266a21d32620d22620d3266a21d42620d32620d4266a21d52620d42620d5266a21d62620d52620d6266a21d726" + "20d62620d7266a21d82620d72620d8266a21d92620d82620d9266a21da2620d92620da266a21db2620da2620db266a" + "21dc2620db2620dc266a21dd2620dc2620dd266a21de2620dd2620de266a21df2620de2620df266a21e02620df2620" + "e0266a21e12620e02620e1266a21e22620e12620e2266a21e32620e22620e3266a21e42620e32620e4266a21e52620" + "e42620e5266a21e62620e52620e6266a21e72620e62620e7266a21e82620e72620e8266a21e92620e82620e9266a21" + "ea2620e92620ea266a21eb2620ea2620eb266a21ec2620eb2620ec266a21ed2620ec2620ed266a21ee2620ed2620ee" + "266a21ef2620ee2620ef266a21f02620ef2620f0266a21f12620f02620f1266a21f22620f12620f2266a21f32620f2" + "2620f3266a21f42620f32620f4266a21f52620f42620f5266a21f62620f52620f6266a21f72620f62620f7266a21f8" + "2620f72620f8266a21f92620f82620f9266a21fa2620f92620fa266a21fb2620fa2620fb266a21fc2620fb2620fc26" + "6a21fd2620fc2620fd266a21fe2620fd2620fe266a21ff2620fe2620ff266a21802720ff262080276a218127208027" + "2081276a2182272081272082276a2183272082272083276a2184272083272084276a2185272084272085276a218627" + "2085272086276a2187272086272087276a2188272087272088276a2189272088272089276a218a27208927208a276a" + "218b27208a27208b276a218c27208b27208c276a218d27208c27208d276a218e27208d27208e276a218f27208e2720" + "8f276a219027208f272090276a2191272090272091276a2192272091272092276a2193272092272093276a21942720" + "93272094276a2195272094272095276a2196272095272096276a2197272096272097276a2198272097272098276a21" + "99272098272099276a219a27209927209a276a219b27209a27209b276a219c27209b27209c276a219d27209c27209d" + "276a219e27209d27209e276a219f27209e27209f276a21a027209f2720a0276a21a12720a02720a1276a21a22720a1" + "2720a2276a21a32720a22720a3276a21a42720a32720a4276a21a52720a42720a5276a21a62720a52720a6276a21a7" + "2720a62720a7276a21a82720a72720a8276a21a92720a82720a9276a21aa2720a92720aa276a21ab2720aa2720ab27" + "6a21ac2720ab2720ac276a21ad2720ac2720ad276a21ae2720ad2720ae276a21af2720ae2720af276a21b02720af27" + "20b0276a21b12720b02720b1276a21b22720b12720b2276a21b32720b22720b3276a21b42720b32720b4276a21b527" + "20b42720b5276a21b62720b52720b6276a21b72720b62720b7276a21b82720b72720b8276a21b92720b82720b9276a" + "21ba2720b92720ba276a21bb2720ba2720bb276a21bc2720bb2720bc276a21bd2720bc2720bd276a21be2720bd2720" + "be276a21bf2720be2720bf276a21c02720bf2720c0276a21c12720c02720c1276a21c22720c12720c2276a21c32720" + "c22720c3276a21c42720c32720c4276a21c52720c42720c5276a21c62720c52720c6276a21c72720c62720c7276a21" + "c82720c72720c8276a21c92720c82720c9276a21ca2720c92720ca276a21cb2720ca2720cb276a21cc2720cb2720cc" + "276a21cd2720cc2720cd276a21ce2720cd2720ce276a21cf2720ce2720cf276a21d02720cf2720d0276a21d12720d0" + "2720d1276a21d22720d12720d2276a21d32720d22720d3276a21d42720d32720d4276a21d52720d42720d5276a21d6" + "2720d52720d6276a21d72720d62720d7276a21d82720d72720d8276a21d92720d82720d9276a21da2720d92720da27" + "6a21db2720da2720db276a21dc2720db2720dc276a21dd2720dc2720dd276a21de2720dd2720de276a21df2720de27" + "20df276a21e02720df2720e0276a21e12720e02720e1276a21e22720e12720e2276a21e32720e22720e3276a21e427" + "20e32720e4276a21e52720e42720e5276a21e62720e52720e6276a21e72720e62720e7276a21e82720e72720e8276a" + "21e92720e82720e9276a21ea2720e92720ea276a21eb2720ea2720eb276a21ec2720eb2720ec276a21ed2720ec2720" + "ed276a21ee2720ed2720ee276a21ef2720ee2720ef276a21f02720ef2720f0276a21f12720f02720f1276a21f22720" + "f12720f2276a21f32720f22720f3276a21f42720f32720f4276a21f52720f42720f5276a21f62720f52720f6276a21" + "f72720f62720f7276a21f82720f72720f8276a21f92720f82720f9276a21fa2720f92720fa276a21fb2720fa2720fb" + "276a21fc2720fb2720fc276a21fd2720fc2720fd276a21fe2720fd2720fe276a21ff2720fe2720ff276a21802820ff" + "272080286a2181282080282081286a2182282081282082286a2183282082282083286a2184282083282084286a2185" + "282084282085286a2186282085282086286a2187282086282087286a2188282087282088286a218928208828208928" + "6a218a28208928208a286a218b28208a28208b286a218c28208b28208c286a218d28208c28208d286a218e28208d28" + "208e286a218f28208e28208f286a219028208f282090286a2191282090282091286a2192282091282092286a219328" + "2092282093286a2194282093282094286a2195282094282095286a2196282095282096286a2197282096282097286a" + "2198282097282098286a2199282098282099286a219a28209928209a286a219b28209a28209b286a219c28209b2820" + "9c286a219d28209c28209d286a219e28209d28209e286a219f28209e28209f286a21a028209f2820a0286a21a12820" + "a02820a1286a21a22820a12820a2286a21a32820a22820a3286a21a42820a32820a4286a21a52820a42820a5286a21" + "a62820a52820a6286a21a72820a62820a7286a21a82820a72820a8286a21a92820a82820a9286a21aa2820a92820aa" + "286a21ab2820aa2820ab286a21ac2820ab2820ac286a21ad2820ac2820ad286a21ae2820ad2820ae286a21af2820ae" + "2820af286a21b02820af2820b0286a21b12820b02820b1286a21b22820b12820b2286a21b32820b22820b3286a21b4" + "2820b32820b4286a21b52820b42820b5286a21b62820b52820b6286a21b72820b62820b7286a21b82820b72820b828" + "6a21b92820b82820b9286a21ba2820b92820ba286a21bb2820ba2820bb286a21bc2820bb2820bc286a21bd2820bc28" + "20bd286a21be2820bd2820be286a21bf2820be2820bf286a21c02820bf2820c0286a21c12820c02820c1286a21c228" + "20c12820c2286a21c32820c22820c3286a21c42820c32820c4286a21c52820c42820c5286a21c62820c52820c6286a" + "21c72820c62820c7286a21c82820c72820c8286a21c92820c82820c9286a21ca2820c92820ca286a21cb2820ca2820" + "cb286a21cc2820cb2820cc286a21cd2820cc2820cd286a21ce2820cd2820ce286a21cf2820ce2820cf286a21d02820" + "cf2820d0286a21d12820d02820d1286a21d22820d12820d2286a21d32820d22820d3286a21d42820d32820d4286a21" + "d52820d42820d5286a21d62820d52820d6286a21d72820d62820d7286a21d82820d72820d8286a21d92820d82820d9" + "286a21da2820d92820da286a21db2820da2820db286a21dc2820db2820dc286a21dd2820dc2820dd286a21de2820dd" + "2820de286a21df2820de2820df286a21e02820df2820e0286a21e12820e02820e1286a21e22820e12820e2286a21e3" + "2820e22820e3286a21e42820e32820e4286a21e52820e42820e5286a21e62820e52820e6286a21e72820e62820e728" + "6a21e82820e72820e8286a21e92820e82820e9286a21ea2820e92820ea286a21eb2820ea2820eb286a21ec2820eb28" + "20ec286a21ed2820ec2820ed286a21ee2820ed2820ee286a21ef2820ee2820ef286a21f02820ef2820f0286a21f128" + "20f02820f1286a21f22820f12820f2286a21f32820f22820f3286a21f42820f32820f4286a21f52820f42820f5286a" + "21f62820f52820f6286a21f72820f62820f7286a21f82820f72820f8286a21f92820f82820f9286a21fa2820f92820" + "fa286a21fb2820fa2820fb286a21fc2820fb2820fc286a21fd2820fc2820fd286a21fe2820fd2820fe286a21ff2820" + "fe2820ff286a21802920ff282080296a2181292080292081296a2182292081292082296a2183292082292083296a21" + "84292083292084296a2185292084292085296a2186292085292086296a2187292086292087296a2188292087292088" + "296a2189292088292089296a218a29208929208a296a218b29208a29208b296a218c29208b29208c296a218d29208c" + "29208d296a218e29208d29208e296a218f29208e29208f296a219029208f292090296a2191292090292091296a2192" + "292091292092296a2193292092292093296a2194292093292094296a2195292094292095296a219629209529209629" + "6a2197292096292097296a2198292097292098296a2199292098292099296a219a29209929209a296a219b29209a29" + "209b296a219c29209b29209c296a219d29209c29209d296a219e29209d29209e296a219f29209e29209f296a21a029" + "209f2920a0296a21a12920a02920a1296a21a22920a12920a2296a21a32920a22920a3296a21a42920a32920a4296a" + "21a52920a42920a5296a21a62920a52920a6296a21a72920a62920a7296a21a82920a72920a8296a21a92920a82920" + "a9296a21aa2920a92920aa296a21ab2920aa2920ab296a21ac2920ab2920ac296a21ad2920ac2920ad296a21ae2920" + "ad2920ae296a21af2920ae2920af296a21b02920af2920b0296a21b12920b02920b1296a21b22920b12920b2296a21" + "b32920b22920b3296a21b42920b32920b4296a21b52920b42920b5296a21b62920b52920b6296a21b72920b62920b7" + "296a21b82920b72920b8296a21b92920b82920b9296a21ba2920b92920ba296a21bb2920ba2920bb296a21bc2920bb" + "2920bc296a21bd2920bc2920bd296a21be2920bd2920be296a21bf2920be2920bf296a21c02920bf2920c0296a21c1" + "2920c02920c1296a21c22920c12920c2296a21c32920c22920c3296a21c42920c32920c4296a21c52920c42920c529" + "6a21c62920c52920c6296a21c72920c62920c7296a21c82920c72920c8296a21c92920c82920c9296a21ca2920c929" + "20ca296a21cb2920ca2920cb296a21cc2920cb2920cc296a21cd2920cc2920cd296a21ce2920cd2920ce296a21cf29" + "20ce2920cf296a21d02920cf2920d0296a21d12920d02920d1296a21d22920d12920d2296a21d32920d22920d3296a" + "21d42920d32920d4296a21d52920d42920d5296a21d62920d52920d6296a21d72920d62920d7296a21d82920d72920" + "d8296a21d92920d82920d9296a21da2920d92920da296a21db2920da2920db296a21dc2920db2920dc296a21dd2920" + "dc2920dd296a21de2920dd2920de296a21df2920de2920df296a21e02920df2920e0296a21e12920e02920e1296a21" + "e22920e12920e2296a21e32920e22920e3296a21e42920e32920e4296a21e52920e42920e5296a21e62920e52920e6" + "296a21e72920e62920e7296a21e82920e72920e8296a21e92920e82920e9296a21ea2920e92920ea296a21eb2920ea" + "2920eb296a21ec2920eb2920ec296a21ed2920ec2920ed296a21ee2920ed2920ee296a21ef2920ee2920ef296a21f0" + "2920ef2920f0296a21f12920f02920f1296a21f22920f12920f2296a21f32920f22920f3296a21f42920f32920f429" + "6a21f52920f42920f5296a21f62920f52920f6296a21f72920f62920f7296a21f82920f72920f8296a21f92920f829" + "20f9296a21fa2920f92920fa296a21fb2920fa2920fb296a21fc2920fb2920fc296a21fd2920fc2920fd296a21fe29" + "20fd2920fe296a21ff2920fe2920ff296a21802a20ff2920802a6a21812a20802a20812a6a21822a20812a20822a6a" + "21832a20822a20832a6a21842a20832a20842a6a21852a20842a20852a6a21862a20852a20862a6a21872a20862a20" + "872a6a21882a20872a20882a6a21892a20882a20892a6a218a2a20892a208a2a6a218b2a208a2a208b2a6a218c2a20" + "8b2a208c2a6a218d2a208c2a208d2a6a218e2a208d2a208e2a6a218f2a208e2a208f2a6a21902a208f2a20902a6a21" + "912a20902a20912a6a21922a20912a20922a6a21932a20922a20932a6a21942a20932a20942a6a21952a20942a2095" + "2a6a21962a20952a20962a6a21972a20962a20972a6a21982a20972a20982a6a21992a20982a20992a6a219a2a2099" + "2a209a2a6a219b2a209a2a209b2a6a219c2a209b2a209c2a6a219d2a209c2a209d2a6a219e2a209d2a209e2a6a219f" + "2a209e2a209f2a6a21a02a209f2a20a02a6a21a12a20a02a20a12a6a21a22a20a12a20a22a6a21a32a20a22a20a32a" + "6a21a42a20a32a20a42a6a21a52a20a42a20a52a6a21a62a20a52a20a62a6a21a72a20a62a20a72a6a21a82a20a72a" + "20a82a6a21a92a20a82a20a92a6a21aa2a20a92a20aa2a6a21ab2a20aa2a20ab2a6a21ac2a20ab2a20ac2a6a21ad2a" + "20ac2a20ad2a6a21ae2a20ad2a20ae2a6a21af2a20ae2a20af2a6a21b02a20af2a20b02a6a21b12a20b02a20b12a6a" + "21b22a20b12a20b22a6a21b32a20b22a20b32a6a21b42a20b32a20b42a6a21b52a20b42a20b52a6a21b62a20b52a20" + "b62a6a21b72a20b62a20b72a6a21b82a20b72a20b82a6a21b92a20b82a20b92a6a21ba2a20b92a20ba2a6a21bb2a20" + "ba2a20bb2a6a21bc2a20bb2a20bc2a6a21bd2a20bc2a20bd2a6a21be2a20bd2a20be2a6a21bf2a20be2a20bf2a6a21" + "c02a20bf2a20c02a6a21c12a20c02a20c12a6a21c22a20c12a20c22a6a21c32a20c22a20c32a6a21c42a20c32a20c4" + "2a6a21c52a20c42a20c52a6a21c62a20c52a20c62a6a21c72a20c62a20c72a6a21c82a20c72a20c82a6a21c92a20c8" + "2a20c92a6a21ca2a20c92a20ca2a6a21cb2a20ca2a20cb2a6a21cc2a20cb2a20cc2a6a21cd2a20cc2a20cd2a6a21ce" + "2a20cd2a20ce2a6a21cf2a20ce2a20cf2a6a21d02a20cf2a20d02a6a21d12a20d02a20d12a6a21d22a20d12a20d22a" + "6a21d32a20d22a20d32a6a21d42a20d32a20d42a6a21d52a20d42a20d52a6a21d62a20d52a20d62a6a21d72a20d62a" + "20d72a6a21d82a20d72a20d82a6a21d92a20d82a20d92a6a21da2a20d92a20da2a6a21db2a20da2a20db2a6a21dc2a" + "20db2a20dc2a6a21dd2a20dc2a20dd2a6a21de2a20dd2a20de2a6a21df2a20de2a20df2a6a21e02a20df2a20e02a6a" + "21e12a20e02a20e12a6a21e22a20e12a20e22a6a21e32a20e22a20e32a6a21e42a20e32a20e42a6a21e52a20e42a20" + "e52a6a21e62a20e52a20e62a6a21e72a20e62a20e72a6a21e82a20e72a20e82a6a21e92a20e82a20e92a6a21ea2a20" + "e92a20ea2a6a21eb2a20ea2a20eb2a6a21ec2a20eb2a20ec2a6a21ed2a20ec2a20ed2a6a21ee2a20ed2a20ee2a6a21" + "ef2a20ee2a20ef2a6a21f02a20ef2a20f02a6a21f12a20f02a20f12a6a21f22a20f12a20f22a6a21f32a20f22a20f3" + "2a6a21f42a20f32a20f42a6a21f52a20f42a20f52a6a21f62a20f52a20f62a6a21f72a20f62a20f72a6a21f82a20f7" + "2a20f82a6a21f92a20f82a20f92a6a21fa2a20f92a20fa2a6a21fb2a20fa2a20fb2a6a21fc2a20fb2a20fc2a6a21fd" + "2a20fc2a20fd2a6a21fe2a20fd2a20fe2a6a21ff2a20fe2a20ff2a6a21802b20ff2a20802b6a21812b20802b20812b" + "6a21822b20812b20822b6a21832b20822b20832b6a21842b20832b20842b6a21852b20842b20852b6a21862b20852b" + "20862b6a21872b20862b20872b6a21882b20872b20882b6a21892b20882b20892b6a218a2b20892b208a2b6a218b2b" + "208a2b208b2b6a218c2b208b2b208c2b6a218d2b208c2b208d2b6a218e2b208d2b208e2b6a218f2b208e2b208f2b6a" + "21902b208f2b20902b6a21912b20902b20912b6a21922b20912b20922b6a21932b20922b20932b6a21942b20932b20" + "942b6a21952b20942b20952b6a21962b20952b20962b6a21972b20962b20972b6a21982b20972b20982b6a21992b20" + "982b20992b6a219a2b20992b209a2b6a219b2b209a2b209b2b6a219c2b209b2b209c2b6a219d2b209c2b209d2b6a21" + "9e2b209d2b209e2b6a219f2b209e2b209f2b6a21a02b209f2b20a02b6a21a12b20a02b20a12b6a21a22b20a12b20a2" + "2b6a21a32b20a22b20a32b6a21a42b20a32b20a42b6a21a52b20a42b20a52b6a21a62b20a52b20a62b6a21a72b20a6" + "2b20a72b6a21a82b20a72b20a82b6a21a92b20a82b20a92b6a21aa2b20a92b20aa2b6a21ab2b20aa2b20ab2b6a21ac" + "2b20ab2b20ac2b6a21ad2b20ac2b20ad2b6a21ae2b20ad2b20ae2b6a21af2b20ae2b20af2b6a21b02b20af2b20b02b" + "6a21b12b20b02b20b12b6a21b22b20b12b20b22b6a21b32b20b22b20b32b6a21b42b20b32b20b42b6a21b52b20b42b" + "20b52b6a21b62b20b52b20b62b6a21b72b20b62b20b72b6a21b82b20b72b20b82b6a21b92b20b82b20b92b6a21ba2b" + "20b92b20ba2b6a21bb2b20ba2b20bb2b6a21bc2b20bb2b20bc2b6a21bd2b20bc2b20bd2b6a21be2b20bd2b20be2b6a" + "21bf2b20be2b20bf2b6a21c02b20bf2b20c02b6a21c12b20c02b20c12b6a21c22b20c12b20c22b6a21c32b20c22b20" + "c32b6a21c42b20c32b20c42b6a21c52b20c42b20c52b6a21c62b20c52b20c62b6a21c72b20c62b20c72b6a21c82b20" + "c72b20c82b6a21c92b20c82b20c92b6a21ca2b20c92b20ca2b6a21cb2b20ca2b20cb2b6a21cc2b20cb2b20cc2b6a21" + "cd2b20cc2b20cd2b6a21ce2b20cd2b20ce2b6a21cf2b20ce2b20cf2b6a21d02b20cf2b20d02b6a21d12b20d02b20d1" + "2b6a21d22b20d12b20d22b6a21d32b20d22b20d32b6a21d42b20d32b20d42b6a21d52b20d42b20d52b6a21d62b20d5" + "2b20d62b6a21d72b20d62b20d72b6a21d82b20d72b20d82b6a21d92b20d82b20d92b6a21da2b20d92b20da2b6a21db" + "2b20da2b20db2b6a21dc2b20db2b20dc2b6a21dd2b20dc2b20dd2b6a21de2b20dd2b20de2b6a21df2b20de2b20df2b" + "6a21e02b20df2b20e02b6a21e12b20e02b20e12b6a21e22b20e12b20e22b6a21e32b20e22b20e32b6a21e42b20e32b" + "20e42b6a21e52b20e42b20e52b6a21e62b20e52b20e62b6a21e72b20e62b20e72b6a21e82b20e72b20e82b6a21e92b" + "20e82b20e92b6a21ea2b20e92b20ea2b6a21eb2b20ea2b20eb2b6a21ec2b20eb2b20ec2b6a21ed2b20ec2b20ed2b6a" + "21ee2b20ed2b20ee2b6a21ef2b20ee2b20ef2b6a21f02b20ef2b20f02b6a21f12b20f02b20f12b6a21f22b20f12b20" + "f22b6a21f32b20f22b20f32b6a21f42b20f32b20f42b6a21f52b20f42b20f52b6a21f62b20f52b20f62b6a21f72b20" + "f62b20f72b6a21f82b20f72b20f82b6a21f92b20f82b20f92b6a21fa2b20f92b20fa2b6a21fb2b20fa2b20fb2b6a21" + "fc2b20fb2b20fc2b6a21fd2b20fc2b20fd2b6a21fe2b20fd2b20fe2b6a21ff2b20fe2b20ff2b6a21802c20ff2b2080" + "2c6a21812c20802c20812c6a21822c20812c20822c6a21832c20822c20832c6a21842c20832c20842c6a21852c2084" + "2c20852c6a21862c20852c20862c6a21872c20862c20872c6a21882c20872c20882c6a21892c20882c20892c6a218a" + "2c20892c208a2c6a218b2c208a2c208b2c6a218c2c208b2c208c2c6a218d2c208c2c208d2c6a218e2c208d2c208e2c" + "6a218f2c208e2c208f2c6a21902c208f2c20902c6a21912c20902c20912c6a21922c20912c20922c6a21932c20922c" + "20932c6a21942c20932c20942c6a21952c20942c20952c6a21962c20952c20962c6a21972c20962c20972c6a21982c" + "20972c20982c6a21992c20982c20992c6a219a2c20992c209a2c6a219b2c209a2c209b2c6a219c2c209b2c209c2c6a" + "219d2c209c2c209d2c6a219e2c209d2c209e2c6a219f2c209e2c209f2c6a21a02c209f2c20a02c6a21a12c20a02c20" + "a12c6a21a22c20a12c20a22c6a21a32c20a22c20a32c6a21a42c20a32c20a42c6a21a52c20a42c20a52c6a21a62c20" + "a52c20a62c6a21a72c20a62c20a72c6a21a82c20a72c20a82c6a21a92c20a82c20a92c6a21aa2c20a92c20aa2c6a21" + "ab2c20aa2c20ab2c6a21ac2c20ab2c20ac2c6a21ad2c20ac2c20ad2c6a21ae2c20ad2c20ae2c6a21af2c20ae2c20af" + "2c6a21b02c20af2c20b02c6a21b12c20b02c20b12c6a21b22c20b12c20b22c6a21b32c20b22c20b32c6a21b42c20b3" + "2c20b42c6a21b52c20b42c20b52c6a21b62c20b52c20b62c6a21b72c20b62c20b72c6a21b82c20b72c20b82c6a21b9" + "2c20b82c20b92c6a21ba2c20b92c20ba2c6a21bb2c20ba2c20bb2c6a21bc2c20bb2c20bc2c6a21bd2c20bc2c20bd2c" + "6a21be2c20bd2c20be2c6a21bf2c20be2c20bf2c6a21c02c20bf2c20c02c6a21c12c20c02c20c12c6a21c22c20c12c" + "20c22c6a21c32c20c22c20c32c6a21c42c20c32c20c42c6a21c52c20c42c20c52c6a21c62c20c52c20c62c6a21c72c" + "20c62c20c72c6a21c82c20c72c20c82c6a21c92c20c82c20c92c6a21ca2c20c92c20ca2c6a21cb2c20ca2c20cb2c6a" + "21cc2c20cb2c20cc2c6a21cd2c20cc2c20cd2c6a21ce2c20cd2c20ce2c6a21cf2c20ce2c20cf2c6a21d02c20cf2c20" + "d02c6a21d12c20d02c20d12c6a21d22c20d12c20d22c6a21d32c20d22c20d32c6a21d42c20d32c20d42c6a21d52c20" + "d42c20d52c6a21d62c20d52c20d62c6a21d72c20d62c20d72c6a21d82c20d72c20d82c6a21d92c20d82c20d92c6a21" + "da2c20d92c20da2c6a21db2c20da2c20db2c6a21dc2c20db2c20dc2c6a21dd2c20dc2c20dd2c6a21de2c20dd2c20de" + "2c6a21df2c20de2c20df2c6a21e02c20df2c20e02c6a21e12c20e02c20e12c6a21e22c20e12c20e22c6a21e32c20e2" + "2c20e32c6a21e42c20e32c20e42c6a21e52c20e42c20e52c6a21e62c20e52c20e62c6a21e72c20e62c20e72c6a21e8" + "2c20e72c20e82c6a21e92c20e82c20e92c6a21ea2c20e92c20ea2c6a21eb2c20ea2c20eb2c6a21ec2c20eb2c20ec2c" + "6a21ed2c20ec2c20ed2c6a21ee2c20ed2c20ee2c6a21ef2c20ee2c20ef2c6a21f02c20ef2c20f02c6a21f12c20f02c" + "20f12c6a21f22c20f12c20f22c6a21f32c20f22c20f32c6a21f42c20f32c20f42c6a21f52c20f42c20f52c6a21f62c" + "20f52c20f62c6a21f72c20f62c20f72c6a21f82c20f72c20f82c6a21f92c20f82c20f92c6a21fa2c20f92c20fa2c6a" + "21fb2c20fa2c20fb2c6a21fc2c20fb2c20fc2c6a21fd2c20fc2c20fd2c6a21fe2c20fd2c20fe2c6a21ff2c20fe2c20" + "ff2c6a21802d20ff2c20802d6a21812d20802d20812d6a21822d20812d20822d6a21832d20822d20832d6a21842d20" + "832d20842d6a21852d20842d20852d6a21862d20852d20862d6a21872d20862d20872d6a21882d20872d20882d6a21" + "892d20882d20892d6a218a2d20892d208a2d6a218b2d208a2d208b2d6a218c2d208b2d208c2d6a218d2d208c2d208d" + "2d6a218e2d208d2d208e2d6a218f2d208e2d208f2d6a21902d208f2d20902d6a21912d20902d20912d6a21922d2091" + "2d20922d6a21932d20922d20932d6a21942d20932d20942d6a21952d20942d20952d6a21962d20952d20962d6a2197" + "2d20962d20972d6a21982d20972d20982d6a21992d20982d20992d6a219a2d20992d209a2d6a219b2d209a2d209b2d" + "6a219c2d209b2d209c2d6a219d2d209c2d209d2d6a219e2d209d2d209e2d6a219f2d209e2d209f2d6a21a02d209f2d" + "20a02d6a21a12d20a02d20a12d6a21a22d20a12d20a22d6a21a32d20a22d20a32d6a21a42d20a32d20a42d6a21a52d" + "20a42d20a52d6a21a62d20a52d20a62d6a21a72d20a62d20a72d6a21a82d20a72d20a82d6a21a92d20a82d20a92d6a" + "21aa2d20a92d20aa2d6a21ab2d20aa2d20ab2d6a21ac2d20ab2d20ac2d6a21ad2d20ac2d20ad2d6a21ae2d20ad2d20" + "ae2d6a21af2d20ae2d20af2d6a21b02d20af2d20b02d6a21b12d20b02d20b12d6a21b22d20b12d20b22d6a21b32d20" + "b22d20b32d6a21b42d20b32d20b42d6a21b52d20b42d20b52d6a21b62d20b52d20b62d6a21b72d20b62d20b72d6a21" + "b82d20b72d20b82d6a21b92d20b82d20b92d6a21ba2d20b92d20ba2d6a21bb2d20ba2d20bb2d6a21bc2d20bb2d20bc" + "2d6a21bd2d20bc2d20bd2d6a21be2d20bd2d20be2d6a21bf2d20be2d20bf2d6a21c02d20bf2d20c02d6a21c12d20c0" + "2d20c12d6a21c22d20c12d20c22d6a21c32d20c22d20c32d6a21c42d20c32d20c42d6a21c52d20c42d20c52d6a21c6" + "2d20c52d20c62d6a21c72d20c62d20c72d6a21c82d20c72d20c82d6a21c92d20c82d20c92d6a21ca2d20c92d20ca2d" + "6a21cb2d20ca2d20cb2d6a21cc2d20cb2d20cc2d6a21cd2d20cc2d20cd2d6a21ce2d20cd2d20ce2d6a21cf2d20ce2d" + "20cf2d6a21d02d20cf2d20d02d6a21d12d20d02d20d12d6a21d22d20d12d20d22d6a21d32d20d22d20d32d6a21d42d" + "20d32d20d42d6a21d52d20d42d20d52d6a21d62d20d52d20d62d6a21d72d20d62d20d72d6a21d82d20d72d20d82d6a" + "21d92d20d82d20d92d6a21da2d20d92d20da2d6a21db2d20da2d20db2d6a21dc2d20db2d20dc2d6a21dd2d20dc2d20" + "dd2d6a21de2d20dd2d20de2d6a21df2d20de2d20df2d6a21e02d20df2d20e02d6a21e12d20e02d20e12d6a21e22d20" + "e12d20e22d6a21e32d20e22d20e32d6a21e42d20e32d20e42d6a21e52d20e42d20e52d6a21e62d20e52d20e62d6a21" + "e72d20e62d20e72d6a21e82d20e72d20e82d6a21e92d20e82d20e92d6a21ea2d20e92d20ea2d6a21eb2d20ea2d20eb" + "2d6a21ec2d20eb2d20ec2d6a21ed2d20ec2d20ed2d6a21ee2d20ed2d20ee2d6a21ef2d20ee2d20ef2d6a21f02d20ef" + "2d20f02d6a21f12d20f02d20f12d6a21f22d20f12d20f22d6a21f32d20f22d20f32d6a21f42d20f32d20f42d6a21f5" + "2d20f42d20f52d6a21f62d20f52d20f62d6a21f72d20f62d20f72d6a21f82d20f72d20f82d6a21f92d20f82d20f92d" + "6a21fa2d20f92d20fa2d6a21fb2d20fa2d20fb2d6a21fc2d20fb2d20fc2d6a21fd2d20fc2d20fd2d6a21fe2d20fd2d" + "20fe2d6a21ff2d20fe2d20ff2d6a21802e20ff2d20802e6a21812e20802e20812e6a21822e20812e20822e6a21832e" + "20822e20832e6a21842e20832e20842e6a21852e20842e20852e6a21862e20852e20862e6a21872e20862e20872e6a" + "21882e20872e20882e6a21892e20882e20892e6a218a2e20892e208a2e6a218b2e208a2e208b2e6a218c2e208b2e20" + "8c2e6a218d2e208c2e208d2e6a218e2e208d2e208e2e6a218f2e208e2e208f2e6a21902e208f2e20902e6a21912e20" + "902e20912e6a21922e20912e20922e6a21932e20922e20932e6a21942e20932e20942e6a21952e20942e20952e6a21" + "962e20952e20962e6a21972e20962e20972e6a21982e20972e20982e6a21992e20982e20992e6a219a2e20992e209a" + "2e6a219b2e209a2e209b2e6a219c2e209b2e209c2e6a219d2e209c2e209d2e6a219e2e209d2e209e2e6a219f2e209e" + "2e209f2e6a21a02e209f2e20a02e6a21a12e20a02e20a12e6a21a22e20a12e20a22e6a21a32e20a22e20a32e6a21a4" + "2e20a32e20a42e6a21a52e20a42e20a52e6a21a62e20a52e20a62e6a21a72e20a62e20a72e6a21a82e20a72e20a82e" + "6a21a92e20a82e20a92e6a21aa2e20a92e20aa2e6a21ab2e20aa2e20ab2e6a21ac2e20ab2e20ac2e6a21ad2e20ac2e" + "20ad2e6a21ae2e20ad2e20ae2e6a21af2e20ae2e20af2e6a21b02e20af2e20b02e6a21b12e20b02e20b12e6a21b22e" + "20b12e20b22e6a21b32e20b22e20b32e6a21b42e20b32e20b42e6a21b52e20b42e20b52e6a21b62e20b52e20b62e6a" + "21b72e20b62e20b72e6a21b82e20b72e20b82e6a21b92e20b82e20b92e6a21ba2e20b92e20ba2e6a21bb2e20ba2e20" + "bb2e6a21bc2e20bb2e20bc2e6a21bd2e20bc2e20bd2e6a21be2e20bd2e20be2e6a21bf2e20be2e20bf2e6a21c02e20" + "bf2e20c02e6a21c12e20c02e20c12e6a21c22e20c12e20c22e6a21c32e20c22e20c32e6a21c42e20c32e20c42e6a21" + "c52e20c42e20c52e6a21c62e20c52e20c62e6a21c72e20c62e20c72e6a21c82e20c72e20c82e6a21c92e20c82e20c9" + "2e6a21ca2e20c92e20ca2e6a21cb2e20ca2e20cb2e6a21cc2e20cb2e20cc2e6a21cd2e20cc2e20cd2e6a21ce2e20cd" + "2e20ce2e6a21cf2e20ce2e20cf2e6a21d02e20cf2e20d02e6a21d12e20d02e20d12e6a21d22e20d12e20d22e6a21d3" + "2e20d22e20d32e6a21d42e20d32e20d42e6a21d52e20d42e20d52e6a21d62e20d52e20d62e6a21d72e20d62e20d72e" + "6a21d82e20d72e20d82e6a21d92e20d82e20d92e6a21da2e20d92e20da2e6a21db2e20da2e20db2e6a21dc2e20db2e" + "20dc2e6a21dd2e20dc2e20dd2e6a21de2e20dd2e20de2e6a21df2e20de2e20df2e6a21e02e20df2e20e02e6a21e12e" + "20e02e20e12e6a21e22e20e12e20e22e6a21e32e20e22e20e32e6a21e42e20e32e20e42e6a21e52e20e42e20e52e6a" + "21e62e20e52e20e62e6a21e72e20e62e20e72e6a21e82e20e72e20e82e6a21e92e20e82e20e92e6a21ea2e20e92e20" + "ea2e6a21eb2e20ea2e20eb2e6a21ec2e20eb2e20ec2e6a21ed2e20ec2e20ed2e6a21ee2e20ed2e20ee2e6a21ef2e20" + "ee2e20ef2e6a21f02e20ef2e20f02e6a21f12e20f02e20f12e6a21f22e20f12e20f22e6a21f32e20f22e20f32e6a21" + "f42e20f32e20f42e6a21f52e20f42e20f52e6a21f62e20f52e20f62e6a21f72e20f62e20f72e6a21f82e20f72e20f8" + "2e6a21f92e20f82e20f92e6a21fa2e20f92e20fa2e6a21fb2e20fa2e20fb2e6a21fc2e20fb2e20fc2e6a21fd2e20fc" + "2e20fd2e6a21fe2e20fd2e20fe2e6a21ff2e20fe2e20ff2e6a21802f20ff2e20802f6a21812f20802f20812f6a2182" + "2f20812f20822f6a21832f20822f20832f6a21842f20832f20842f6a21852f20842f20852f6a21862f20852f20862f" + "6a21872f20862f20872f6a21882f20872f20882f6a21892f20882f20892f6a218a2f20892f208a2f6a218b2f208a2f" + "208b2f6a218c2f208b2f208c2f6a218d2f208c2f208d2f6a218e2f208d2f208e2f6a218f2f208e2f208f2f6a21902f" + "208f2f20902f6a21912f20902f20912f6a21922f20912f20922f6a21932f20922f20932f6a21942f20932f20942f6a" + "21952f20942f20952f6a21962f20952f20962f6a21972f20962f20972f6a21982f20972f20982f6a21992f20982f20" + "992f6a219a2f20992f209a2f6a219b2f209a2f209b2f6a219c2f209b2f209c2f6a219d2f209c2f209d2f6a219e2f20" + "9d2f209e2f6a219f2f209e2f209f2f6a21a02f209f2f20a02f6a21a12f20a02f20a12f6a21a22f20a12f20a22f6a21" + "a32f20a22f20a32f6a21a42f20a32f20a42f6a21a52f20a42f20a52f6a21a62f20a52f20a62f6a21a72f20a62f20a7" + "2f6a21a82f20a72f20a82f6a21a92f20a82f20a92f6a21aa2f20a92f20aa2f6a21ab2f20aa2f20ab2f6a21ac2f20ab" + "2f20ac2f6a21ad2f20ac2f20ad2f6a21ae2f20ad2f20ae2f6a21af2f20ae2f20af2f6a21b02f20af2f20b02f6a21b1" + "2f20b02f20b12f6a21b22f20b12f20b22f6a21b32f20b22f20b32f6a21b42f20b32f20b42f6a21b52f20b42f20b52f" + "6a21b62f20b52f20b62f6a21b72f20b62f20b72f6a21b82f20b72f20b82f6a21b92f20b82f20b92f6a21ba2f20b92f" + "20ba2f6a21bb2f20ba2f20bb2f6a21bc2f20bb2f20bc2f6a21bd2f20bc2f20bd2f6a21be2f20bd2f20be2f6a21bf2f" + "20be2f20bf2f6a21c02f20bf2f20c02f6a21c12f20c02f20c12f6a21c22f20c12f20c22f6a21c32f20c22f20c32f6a" + "21c42f20c32f20c42f6a21c52f20c42f20c52f6a21c62f20c52f20c62f6a21c72f20c62f20c72f6a21c82f20c72f20" + "c82f6a21c92f20c82f20c92f6a21ca2f20c92f20ca2f6a21cb2f20ca2f20cb2f6a21cc2f20cb2f20cc2f6a21cd2f20" + "cc2f20cd2f6a21ce2f20cd2f20ce2f6a21cf2f20ce2f20cf2f6a21d02f20cf2f20d02f6a21d12f20d02f20d12f6a21" + "d22f20d12f20d22f6a21d32f20d22f20d32f6a21d42f20d32f20d42f6a21d52f20d42f20d52f6a21d62f20d52f20d6" + "2f6a21d72f20d62f20d72f6a21d82f20d72f20d82f6a21d92f20d82f20d92f6a21da2f20d92f20da2f6a21db2f20da" + "2f20db2f6a21dc2f20db2f20dc2f6a21dd2f20dc2f20dd2f6a21de2f20dd2f20de2f6a21df2f20de2f20df2f6a21e0" + "2f20df2f20e02f6a21e12f20e02f20e12f6a21e22f20e12f20e22f6a21e32f20e22f20e32f6a21e42f20e32f20e42f" + "6a21e52f20e42f20e52f6a21e62f20e52f20e62f6a21e72f20e62f20e72f6a21e82f20e72f20e82f6a21e92f20e82f" + "20e92f6a21ea2f20e92f20ea2f6a21eb2f20ea2f20eb2f6a21ec2f20eb2f20ec2f6a21ed2f20ec2f20ed2f6a21ee2f" + "20ed2f20ee2f6a21ef2f20ee2f20ef2f6a21f02f20ef2f20f02f6a21f12f20f02f20f12f6a21f22f20f12f20f22f6a" + "21f32f20f22f20f32f6a21f42f20f32f20f42f6a21f52f20f42f20f52f6a21f62f20f52f20f62f6a21f72f20f62f20" + "f72f6a21f82f20f72f20f82f6a21f92f20f82f20f92f6a21fa2f20f92f20fa2f6a21fb2f20fa2f20fb2f6a21fc2f20" + "fb2f20fc2f6a21fd2f20fc2f20fd2f6a21fe2f20fd2f20fe2f6a21ff2f20fe2f20ff2f6a21803020ff2f2080306a21" + "81302080302081306a2182302081302082306a2183302082302083306a2184302083302084306a2185302084302085" + "306a2186302085302086306a2187302086302087306a2188302087302088306a2189302088302089306a218a302089" + "30208a306a218b30208a30208b306a218c30208b30208c306a218d30208c30208d306a218e30208d30208e306a218f" + "30208e30208f306a219030208f302090306a2191302090302091306a2192302091302092306a219330209230209330" + "6a2194302093302094306a2195302094302095306a2196302095302096306a2197302096302097306a219830209730" + "2098306a2199302098302099306a219a30209930209a306a219b30209a30209b306a219c30209b30209c306a219d30" + "209c30209d306a219e30209d30209e306a219f30209e30209f306a21a030209f3020a0306a21a13020a03020a1306a" + "21a23020a13020a2306a21a33020a23020a3306a21a43020a33020a4306a21a53020a43020a5306a21a63020a53020" + "a6306a21a73020a63020a7306a21a83020a73020a8306a21a93020a83020a9306a21aa3020a93020aa306a21ab3020" + "aa3020ab306a21ac3020ab3020ac306a21ad3020ac3020ad306a21ae3020ad3020ae306a21af3020ae3020af306a21" + "b03020af3020b0306a21b13020b03020b1306a21b23020b13020b2306a21b33020b23020b3306a21b43020b33020b4" + "306a21b53020b43020b5306a21b63020b53020b6306a21b73020b63020b7306a21b83020b73020b8306a21b93020b8" + "3020b9306a21ba3020b93020ba306a21bb3020ba3020bb306a21bc3020bb3020bc306a21bd3020bc3020bd306a21be" + "3020bd3020be306a21bf3020be3020bf306a21c03020bf3020c0306a21c13020c03020c1306a21c23020c13020c230" + "6a21c33020c23020c3306a21c43020c33020c4306a21c53020c43020c5306a21c63020c53020c6306a21c73020c630" + "20c7306a21c83020c73020c8306a21c93020c83020c9306a21ca3020c93020ca306a21cb3020ca3020cb306a21cc30" + "20cb3020cc306a21cd3020cc3020cd306a21ce3020cd3020ce306a21cf3020ce3020cf306a21d03020cf3020d0306a" + "21d13020d03020d1306a21d23020d13020d2306a21d33020d23020d3306a21d43020d33020d4306a21d53020d43020" + "d5306a21d63020d53020d6306a21d73020d63020d7306a21d83020d73020d8306a21d93020d83020d9306a21da3020" + "d93020da306a21db3020da3020db306a21dc3020db3020dc306a21dd3020dc3020dd306a21de3020dd3020de306a21" + "df3020de3020df306a21e03020df3020e0306a21e13020e03020e1306a21e23020e13020e2306a21e33020e23020e3" + "306a21e43020e33020e4306a21e53020e43020e5306a21e63020e53020e6306a21e73020e63020e7306a21e83020e7" + "3020e8306a21e93020e83020e9306a21ea3020e93020ea306a21eb3020ea3020eb306a21ec3020eb3020ec306a21ed" + "3020ec3020ed306a21ee3020ed3020ee306a21ef3020ee3020ef306a21f03020ef3020f0306a21f13020f03020f130" + "6a21f23020f13020f2306a21f33020f23020f3306a21f43020f33020f4306a21f53020f43020f5306a21f63020f530" + "20f6306a21f73020f63020f7306a21f83020f73020f8306a21f93020f83020f9306a21fa3020f93020fa306a21fb30" + "20fa3020fb306a21fc3020fb3020fc306a21fd3020fc3020fd306a21fe3020fd3020fe306a21ff3020fe3020ff306a" + "21803120ff302080316a2181312080312081316a2182312081312082316a2183312082312083316a21843120833120" + "84316a2185312084312085316a2186312085312086316a2187312086312087316a2188312087312088316a21893120" + "88312089316a218a31208931208a316a218b31208a31208b316a218c31208b31208c316a218d31208c31208d316a21" + "8e31208d31208e316a218f31208e31208f316a219031208f312090316a2191312090312091316a2192312091312092" + "316a2193312092312093316a2194312093312094316a2195312094312095316a2196312095312096316a2197312096" + "312097316a2198312097312098316a2199312098312099316a219a31209931209a316a219b31209a31209b316a219c" + "31209b31209c316a219d31209c31209d316a219e31209d31209e316a219f31209e31209f316a21a031209f3120a031" + "6a21a13120a03120a1316a21a23120a13120a2316a21a33120a23120a3316a21a43120a33120a4316a21a53120a431" + "20a5316a21a63120a53120a6316a21a73120a63120a7316a21a83120a73120a8316a21a93120a83120a9316a21aa31" + "20a93120aa316a21ab3120aa3120ab316a21ac3120ab3120ac316a21ad3120ac3120ad316a21ae3120ad3120ae316a" + "21af3120ae3120af316a21b03120af3120b0316a21b13120b03120b1316a21b23120b13120b2316a21b33120b23120" + "b3316a21b43120b33120b4316a21b53120b43120b5316a21b63120b53120b6316a21b73120b63120b7316a21b83120" + "b73120b8316a21b93120b83120b9316a21ba3120b93120ba316a21bb3120ba3120bb316a21bc3120bb3120bc316a21" + "bd3120bc3120bd316a21be3120bd3120be316a21bf3120be3120bf316a21c03120bf3120c0316a21c13120c03120c1" + "316a21c23120c13120c2316a21c33120c23120c3316a21c43120c33120c4316a21c53120c43120c5316a21c63120c5" + "3120c6316a21c73120c63120c7316a21c83120c73120c8316a21c93120c83120c9316a21ca3120c93120ca316a21cb" + "3120ca3120cb316a21cc3120cb3120cc316a21cd3120cc3120cd316a21ce3120cd3120ce316a21cf3120ce3120cf31" + "6a21d03120cf3120d0316a21d13120d03120d1316a21d23120d13120d2316a21d33120d23120d3316a21d43120d331" + "20d4316a21d53120d43120d5316a21d63120d53120d6316a21d73120d63120d7316a21d83120d73120d8316a21d931" + "20d83120d9316a21da3120d93120da316a21db3120da3120db316a21dc3120db3120dc316a21dd3120dc3120dd316a" + "21de3120dd3120de316a21df3120de3120df316a21e03120df3120e0316a21e13120e03120e1316a21e23120e13120" + "e2316a21e33120e23120e3316a21e43120e33120e4316a21e53120e43120e5316a21e63120e53120e6316a21e73120" + "e63120e7316a21e83120e73120e8316a21e93120e83120e9316a21ea3120e93120ea316a21eb3120ea3120eb316a21" + "ec3120eb3120ec316a21ed3120ec3120ed316a21ee3120ed3120ee316a21ef3120ee3120ef316a21f03120ef3120f0" + "316a21f13120f03120f1316a21f23120f13120f2316a21f33120f23120f3316a21f43120f33120f4316a21f53120f4" + "3120f5316a21f63120f53120f6316a21f73120f63120f7316a21f83120f73120f8316a21f93120f83120f9316a21fa" + "3120f93120fa316a21fb3120fa3120fb316a21fc3120fb3120fc316a21fd3120fc3120fd316a21fe3120fd3120fe31" + "6a21ff3120fe3120ff316a21803220ff312080326a2181322080322081326a2182322081322082326a218332208232" + "2083326a2184322083322084326a2185322084322085326a2186322085322086326a2187322086322087326a218832" + "2087322088326a2189322088322089326a218a32208932208a326a218b32208a32208b326a218c32208b32208c326a" + "218d32208c32208d326a218e32208d32208e326a218f32208e32208f326a219032208f322090326a21913220903220" + "91326a2192322091322092326a2193322092322093326a2194322093322094326a2195322094322095326a21963220" + "95322096326a2197322096322097326a2198322097322098326a2199322098322099326a219a32209932209a326a21" + "9b32209a32209b326a219c32209b32209c326a219d32209c32209d326a219e32209d32209e326a219f32209e32209f" + "326a21a032209f3220a0326a21a13220a03220a1326a21a23220a13220a2326a21a33220a23220a3326a21a43220a3" + "3220a4326a21a53220a43220a5326a21a63220a53220a6326a21a73220a63220a7326a21a83220a73220a8326a21a9" + "3220a83220a9326a21aa3220a93220aa326a21ab3220aa3220ab326a21ac3220ab3220ac326a21ad3220ac3220ad32" + "6a21ae3220ad3220ae326a21af3220ae3220af326a21b03220af3220b0326a21b13220b03220b1326a21b23220b132" + "20b2326a21b33220b23220b3326a21b43220b33220b4326a21b53220b43220b5326a21b63220b53220b6326a21b732" + "20b63220b7326a21b83220b73220b8326a21b93220b83220b9326a21ba3220b93220ba326a21bb3220ba3220bb326a" + "21bc3220bb3220bc326a21bd3220bc3220bd326a21be3220bd3220be326a21bf3220be3220bf326a21c03220bf3220" + "c0326a21c13220c03220c1326a21c23220c13220c2326a21c33220c23220c3326a21c43220c33220c4326a21c53220" + "c43220c5326a21c63220c53220c6326a21c73220c63220c7326a21c83220c73220c8326a21c93220c83220c9326a21" + "ca3220c93220ca326a21cb3220ca3220cb326a21cc3220cb3220cc326a21cd3220cc3220cd326a21ce3220cd3220ce" + "326a21cf3220ce3220cf326a21d03220cf3220d0326a21d13220d03220d1326a21d23220d13220d2326a21d33220d2" + "3220d3326a21d43220d33220d4326a21d53220d43220d5326a21d63220d53220d6326a21d73220d63220d7326a21d8" + "3220d73220d8326a21d93220d83220d9326a21da3220d93220da326a21db3220da3220db326a21dc3220db3220dc32" + "6a21dd3220dc3220dd326a21de3220dd3220de326a21df3220de3220df326a21e03220df3220e0326a21e13220e032" + "20e1326a21e23220e13220e2326a21e33220e23220e3326a21e43220e33220e4326a21e53220e43220e5326a21e632" + "20e53220e6326a21e73220e63220e7326a21e83220e73220e8326a21e93220e83220e9326a21ea3220e93220ea326a" + "21eb3220ea3220eb326a21ec3220eb3220ec326a21ed3220ec3220ed326a21ee3220ed3220ee326a21ef3220ee3220" + "ef326a21f03220ef3220f0326a21f13220f03220f1326a21f23220f13220f2326a21f33220f23220f3326a21f43220" + "f33220f4326a21f53220f43220f5326a21f63220f53220f6326a21f73220f63220f7326a21f83220f73220f8326a21" + "f93220f83220f9326a21fa3220f93220fa326a21fb3220fa3220fb326a21fc3220fb3220fc326a21fd3220fc3220fd" + "326a21fe3220fd3220fe326a21ff3220fe3220ff326a21803320ff322080336a2181332080332081336a2182332081" + "332082336a2183332082332083336a2184332083332084336a2185332084332085336a2186332085332086336a2187" + "332086332087336a2188332087332088336a2189332088332089336a218a33208933208a336a218b33208a33208b33" + "6a218c33208b33208c336a218d33208c33208d336a218e33208d33208e336a218f33208e33208f336a219033208f33" + "2090336a2191332090332091336a2192332091332092336a2193332092332093336a2194332093332094336a219533" + "2094332095336a2196332095332096336a2197332096332097336a2198332097332098336a2199332098332099336a" + "219a33209933209a336a219b33209a33209b336a219c33209b33209c336a219d33209c33209d336a219e33209d3320" + "9e336a219f33209e33209f336a21a033209f3320a0336a21a13320a03320a1336a21a23320a13320a2336a21a33320" + "a23320a3336a21a43320a33320a4336a21a53320a43320a5336a21a63320a53320a6336a21a73320a63320a7336a21" + "a83320a73320a8336a21a93320a83320a9336a21aa3320a93320aa336a21ab3320aa3320ab336a21ac3320ab3320ac" + "336a21ad3320ac3320ad336a21ae3320ad3320ae336a21af3320ae3320af336a21b03320af3320b0336a21b13320b0" + "3320b1336a21b23320b13320b2336a21b33320b23320b3336a21b43320b33320b4336a21b53320b43320b5336a21b6" + "3320b53320b6336a21b73320b63320b7336a21b83320b73320b8336a21b93320b83320b9336a21ba3320b93320ba33" + "6a21bb3320ba3320bb336a21bc3320bb3320bc336a21bd3320bc3320bd336a21be3320bd3320be336a21bf3320be33" + "20bf336a21c03320bf3320c0336a21c13320c03320c1336a21c23320c13320c2336a21c33320c23320c3336a21c433" + "20c33320c4336a21c53320c43320c5336a21c63320c53320c6336a21c73320c63320c7336a21c83320c73320c8336a" + "21c93320c83320c9336a21ca3320c93320ca336a21cb3320ca3320cb336a21cc3320cb3320cc336a21cd3320cc3320" + "cd336a21ce3320cd3320ce336a21cf3320ce3320cf336a21d03320cf3320d0336a21d13320d03320d1336a21d23320" + "d13320d2336a21d33320d23320d3336a21d43320d33320d4336a21d53320d43320d5336a21d63320d53320d6336a21" + "d73320d63320d7336a21d83320d73320d8336a21d93320d83320d9336a21da3320d93320da336a21db3320da3320db" + "336a21dc3320db3320dc336a21dd3320dc3320dd336a21de3320dd3320de336a21df3320de3320df336a21e03320df" + "3320e0336a21e13320e03320e1336a21e23320e13320e2336a21e33320e23320e3336a21e43320e33320e4336a21e5" + "3320e43320e5336a21e63320e53320e6336a21e73320e63320e7336a21e83320e73320e8336a21e93320e83320e933" + "6a21ea3320e93320ea336a21eb3320ea3320eb336a21ec3320eb3320ec336a21ed3320ec3320ed336a21ee3320ed33" + "20ee336a21ef3320ee3320ef336a21f03320ef3320f0336a21f13320f03320f1336a21f23320f13320f2336a21f333" + "20f23320f3336a21f43320f33320f4336a21f53320f43320f5336a21f63320f53320f6336a21f73320f63320f7336a" + "21f83320f73320f8336a21f93320f83320f9336a21fa3320f93320fa336a21fb3320fa3320fb336a21fc3320fb3320" + "fc336a21fd3320fc3320fd336a21fe3320fd3320fe336a21ff3320fe3320ff336a21803420ff332080346a21813420" + "80342081346a2182342081342082346a2183342082342083346a2184342083342084346a2185342084342085346a21" + "86342085342086346a2187342086342087346a2188342087342088346a2189342088342089346a218a34208934208a" + "346a218b34208a34208b346a218c34208b34208c346a218d34208c34208d346a218e34208d34208e346a218f34208e" + "34208f346a219034208f342090346a2191342090342091346a2192342091342092346a2193342092342093346a2194" + "342093342094346a2195342094342095346a2196342095342096346a2197342096342097346a219834209734209834" + "6a2199342098342099346a219a34209934209a346a219b34209a34209b346a219c34209b34209c346a219d34209c34" + "209d346a219e34209d34209e346a219f34209e34209f346a21a034209f3420a0346a21a13420a03420a1346a21a234" + "20a13420a2346a21a33420a23420a3346a21a43420a33420a4346a21a53420a43420a5346a21a63420a53420a6346a" + "21a73420a63420a7346a21a83420a73420a8346a21a93420a83420a9346a21aa3420a93420aa346a21ab3420aa3420" + "ab346a21ac3420ab3420ac346a21ad3420ac3420ad346a21ae3420ad3420ae346a21af3420ae3420af346a21b03420" + "af3420b0346a21b13420b03420b1346a21b23420b13420b2346a21b33420b23420b3346a21b43420b33420b4346a21" + "b53420b43420b5346a21b63420b53420b6346a21b73420b63420b7346a21b83420b73420b8346a21b93420b83420b9" + "346a21ba3420b93420ba346a21bb3420ba3420bb346a21bc3420bb3420bc346a21bd3420bc3420bd346a21be3420bd" + "3420be346a21bf3420be3420bf346a21c03420bf3420c0346a21c13420c03420c1346a21c23420c13420c2346a21c3" + "3420c23420c3346a21c43420c33420c4346a21c53420c43420c5346a21c63420c53420c6346a21c73420c63420c734" + "6a21c83420c73420c8346a21c93420c83420c9346a21ca3420c93420ca346a21cb3420ca3420cb346a21cc3420cb34" + "20cc346a21cd3420cc3420cd346a21ce3420cd3420ce346a21cf3420ce3420cf346a21d03420cf3420d0346a21d134" + "20d03420d1346a21d23420d13420d2346a21d33420d23420d3346a21d43420d33420d4346a21d53420d43420d5346a" + "21d63420d53420d6346a21d73420d63420d7346a21d83420d73420d8346a21d93420d83420d9346a21da3420d93420" + "da346a21db3420da3420db346a21dc3420db3420dc346a21dd3420dc3420dd346a21de3420dd3420de346a21df3420" + "de3420df346a21e03420df3420e0346a21e13420e03420e1346a21e23420e13420e2346a21e33420e23420e3346a21" + "e43420e33420e4346a21e53420e43420e5346a21e63420e53420e6346a21e73420e63420e7346a21e83420e73420e8" + "346a21e93420e83420e9346a21ea3420e93420ea346a21eb3420ea3420eb346a21ec3420eb3420ec346a21ed3420ec" + "3420ed346a21ee3420ed3420ee346a21ef3420ee3420ef346a21f03420ef3420f0346a21f13420f03420f1346a21f2" + "3420f13420f2346a21f33420f23420f3346a21f43420f33420f4346a21f53420f43420f5346a21f63420f53420f634" + "6a21f73420f63420f7346a21f83420f73420f8346a21f93420f83420f9346a21fa3420f93420fa346a21fb3420fa34" + "20fb346a21fc3420fb3420fc346a21fd3420fc3420fd346a21fe3420fd3420fe346a21ff3420fe3420ff346a218035" + "20ff342080356a2181352080352081356a2182352081352082356a2183352082352083356a2184352083352084356a" + "2185352084352085356a2186352085352086356a2187352086352087356a2188352087352088356a21893520883520" + "89356a218a35208935208a356a218b35208a35208b356a218c35208b35208c356a218d35208c35208d356a218e3520" + "8d35208e356a218f35208e35208f356a219035208f352090356a2191352090352091356a2192352091352092356a21" + "93352092352093356a2194352093352094356a2195352094352095356a2196352095352096356a2197352096352097" + "356a2198352097352098356a2199352098352099356a219a35209935209a356a219b35209a35209b356a219c35209b" + "35209c356a219d35209c35209d356a219e35209d35209e356a219f35209e35209f356a21a035209f3520a0356a21a1" + "3520a03520a1356a21a23520a13520a2356a21a33520a23520a3356a21a43520a33520a4356a21a53520a43520a535" + "6a21a63520a53520a6356a21a73520a63520a7356a21a83520a73520a8356a21a93520a83520a9356a21aa3520a935" + "20aa356a21ab3520aa3520ab356a21ac3520ab3520ac356a21ad3520ac3520ad356a21ae3520ad3520ae356a21af35" + "20ae3520af356a21b03520af3520b0356a21b13520b03520b1356a21b23520b13520b2356a21b33520b23520b3356a" + "21b43520b33520b4356a21b53520b43520b5356a21b63520b53520b6356a21b73520b63520b7356a21b83520b73520" + "b8356a21b93520b83520b9356a21ba3520b93520ba356a21bb3520ba3520bb356a21bc3520bb3520bc356a21bd3520" + "bc3520bd356a21be3520bd3520be356a21bf3520be3520bf356a21c03520bf3520c0356a21c13520c03520c1356a21" + "c23520c13520c2356a21c33520c23520c3356a21c43520c33520c4356a21c53520c43520c5356a21c63520c53520c6" + "356a21c73520c63520c7356a21c83520c73520c8356a21c93520c83520c9356a21ca3520c93520ca356a21cb3520ca" + "3520cb356a21cc3520cb3520cc356a21cd3520cc3520cd356a21ce3520cd3520ce356a21cf3520ce3520cf356a21d0" + "3520cf3520d0356a21d13520d03520d1356a21d23520d13520d2356a21d33520d23520d3356a21d43520d33520d435" + "6a21d53520d43520d5356a21d63520d53520d6356a21d73520d63520d7356a21d83520d73520d8356a21d93520d835" + "20d9356a21da3520d93520da356a21db3520da3520db356a21dc3520db3520dc356a21dd3520dc3520dd356a21de35" + "20dd3520de356a21df3520de3520df356a21e03520df3520e0356a21e13520e03520e1356a21e23520e13520e2356a" + "21e33520e23520e3356a21e43520e33520e4356a21e53520e43520e5356a21e63520e53520e6356a21e73520e63520" + "e7356a21e83520e73520e8356a21e93520e83520e9356a21ea3520e93520ea356a21eb3520ea3520eb356a21ec3520" + "eb3520ec356a21ed3520ec3520ed356a21ee3520ed3520ee356a21ef3520ee3520ef356a21f03520ef3520f0356a21" + "f13520f03520f1356a21f23520f13520f2356a21f33520f23520f3356a21f43520f33520f4356a21f53520f43520f5" + "356a21f63520f53520f6356a21f73520f63520f7356a21f83520f73520f8356a21f93520f83520f9356a21fa3520f9" + "3520fa356a21fb3520fa3520fb356a21fc3520fb3520fc356a21fd3520fc3520fd356a21fe3520fd3520fe356a21ff" + "3520fe3520ff356a21803620ff352080366a2181362080362081366a2182362081362082366a218336208236208336" + "6a2184362083362084366a2185362084362085366a2186362085362086366a2187362086362087366a218836208736" + "2088366a2189362088362089366a218a36208936208a366a218b36208a36208b366a218c36208b36208c366a218d36" + "208c36208d366a218e36208d36208e366a218f36208e36208f366a219036208f362090366a2191362090362091366a" + "2192362091362092366a2193362092362093366a2194362093362094366a2195362094362095366a21963620953620" + "96366a2197362096362097366a2198362097362098366a2199362098362099366a219a36209936209a366a219b3620" + "9a36209b366a219c36209b36209c366a219d36209c36209d366a219e36209d36209e366a219f36209e36209f366a21" + "a036209f3620a0366a21a13620a03620a1366a21a23620a13620a2366a21a33620a23620a3366a21a43620a33620a4" + "366a21a53620a43620a5366a21a63620a53620a6366a21a73620a63620a7366a21a83620a73620a8366a21a93620a8" + "3620a9366a21aa3620a93620aa366a21ab3620aa3620ab366a21ac3620ab3620ac366a21ad3620ac3620ad366a21ae" + "3620ad3620ae366a21af3620ae3620af366a21b03620af3620b0366a21b13620b03620b1366a21b23620b13620b236" + "6a21b33620b23620b3366a21b43620b33620b4366a21b53620b43620b5366a21b63620b53620b6366a21b73620b636" + "20b7366a21b83620b73620b8366a21b93620b83620b9366a21ba3620b93620ba366a21bb3620ba3620bb366a21bc36" + "20bb3620bc366a21bd3620bc3620bd366a21be3620bd3620be366a21bf3620be3620bf366a21c03620bf3620c0366a" + "21c13620c03620c1366a21c23620c13620c2366a21c33620c23620c3366a21c43620c33620c4366a21c53620c43620" + "c5366a21c63620c53620c6366a21c73620c63620c7366a21c83620c73620c8366a21c93620c83620c9366a21ca3620" + "c93620ca366a21cb3620ca3620cb366a21cc3620cb3620cc366a21cd3620cc3620cd366a21ce3620cd3620ce366a21" + "cf3620ce3620cf366a21d03620cf3620d0366a21d13620d03620d1366a21d23620d13620d2366a21d33620d23620d3" + "366a21d43620d33620d4366a21d53620d43620d5366a21d63620d53620d6366a21d73620d63620d7366a21d83620d7" + "3620d8366a21d93620d83620d9366a21da3620d93620da366a21db3620da3620db366a21dc3620db3620dc366a21dd" + "3620dc3620dd366a21de3620dd3620de366a21df3620de3620df366a21e03620df3620e0366a21e13620e03620e136" + "6a21e23620e13620e2366a21e33620e23620e3366a21e43620e33620e4366a21e53620e43620e5366a21e63620e536" + "20e6366a21e73620e63620e7366a21e83620e73620e8366a21e93620e83620e9366a21ea3620e93620ea366a21eb36" + "20ea3620eb366a21ec3620eb3620ec366a21ed3620ec3620ed366a21ee3620ed3620ee366a21ef3620ee3620ef366a" + "21f03620ef3620f0366a21f13620f03620f1366a21f23620f13620f2366a21f33620f23620f3366a21f43620f33620" + "f4366a21f53620f43620f5366a21f63620f53620f6366a21f73620f63620f7366a21f83620f73620f8366a21f93620" + "f83620f9366a21fa3620f93620fa366a21fb3620fa3620fb366a21fc3620fb3620fc366a21fd3620fc3620fd366a21" + "fe3620fd3620fe366a21ff3620fe3620ff366a21803720ff362080376a2181372080372081376a2182372081372082" + "376a2183372082372083376a2184372083372084376a2185372084372085376a2186372085372086376a2187372086" + "372087376a2188372087372088376a2189372088372089376a218a37208937208a376a218b37208a37208b376a218c" + "37208b37208c376a218d37208c37208d376a218e37208d37208e376a218f37208e37208f376a219037208f37209037" + "6a2191372090372091376a2192372091372092376a2193372092372093376a2194372093372094376a219537209437" + "2095376a2196372095372096376a2197372096372097376a2198372097372098376a2199372098372099376a219a37" + "209937209a376a219b37209a37209b376a219c37209b37209c376a219d37209c37209d376a219e37209d37209e376a" + "219f37209e37209f376a21a037209f3720a0376a21a13720a03720a1376a21a23720a13720a2376a21a33720a23720" + "a3376a21a43720a33720a4376a21a53720a43720a5376a21a63720a53720a6376a21a73720a63720a7376a21a83720" + "a73720a8376a21a93720a83720a9376a21aa3720a93720aa376a21ab3720aa3720ab376a21ac3720ab3720ac376a21" + "ad3720ac3720ad376a21ae3720ad3720ae376a21af3720ae3720af376a21b03720af3720b0376a21b13720b03720b1" + "376a21b23720b13720b2376a21b33720b23720b3376a21b43720b33720b4376a21b53720b43720b5376a21b63720b5" + "3720b6376a21b73720b63720b7376a21b83720b73720b8376a21b93720b83720b9376a21ba3720b93720ba376a21bb" + "3720ba3720bb376a21bc3720bb3720bc376a21bd3720bc3720bd376a21be3720bd3720be376a21bf3720be3720bf37" + "6a21c03720bf3720c0376a21c13720c03720c1376a21c23720c13720c2376a21c33720c23720c3376a21c43720c337" + "20c4376a21c53720c43720c5376a21c63720c53720c6376a21c73720c63720c7376a21c83720c73720c8376a21c937" + "20c83720c9376a21ca3720c93720ca376a21cb3720ca3720cb376a21cc3720cb3720cc376a21cd3720cc3720cd376a" + "21ce3720cd3720ce376a21cf3720ce3720cf376a21d03720cf3720d0376a21d13720d03720d1376a21d23720d13720" + "d2376a21d33720d23720d3376a21d43720d33720d4376a21d53720d43720d5376a21d63720d53720d6376a21d73720" + "d63720d7376a21d83720d73720d8376a21d93720d83720d9376a21da3720d93720da376a21db3720da3720db376a21" + "dc3720db3720dc376a21dd3720dc3720dd376a21de3720dd3720de376a21df3720de3720df376a21e03720df3720e0" + "376a21e13720e03720e1376a21e23720e13720e2376a21e33720e23720e3376a21e43720e33720e4376a21e53720e4" + "3720e5376a21e63720e53720e6376a21e73720e63720e7376a21e83720e73720e8376a21e93720e83720e9376a21ea" + "3720e93720ea376a21eb3720ea3720eb376a21ec3720eb3720ec376a21ed3720ec3720ed376a21ee3720ed3720ee37" + "6a21ef3720ee3720ef376a21f03720ef3720f0376a21f13720f03720f1376a21f23720f13720f2376a21f33720f237" + "20f3376a21f43720f33720f4376a21f53720f43720f5376a21f63720f53720f6376a21f73720f63720f7376a21f837" + "20f73720f8376a21f93720f83720f9376a21fa3720f93720fa376a21fb3720fa3720fb376a21fc3720fb3720fc376a" + "21fd3720fc3720fd376a21fe3720fd3720fe376a21ff3720fe3720ff376a21803820ff372080386a21813820803820" + "81386a2182382081382082386a2183382082382083386a2184382083382084386a2185382084382085386a21863820" + "85382086386a2187382086382087386a2188382087382088386a2189382088382089386a218a38208938208a386a21" + "8b38208a38208b386a218c38208b38208c386a218d38208c38208d386a218e38208d38208e386a218f38208e38208f" + "386a219038208f382090386a2191382090382091386a2192382091382092386a2193382092382093386a2194382093" + "382094386a2195382094382095386a2196382095382096386a2197382096382097386a2198382097382098386a2199" + "382098382099386a219a38209938209a386a219b38209a38209b386a219c38209b38209c386a219d38209c38209d38" + "6a219e38209d38209e386a219f38209e38209f386a21a038209f3820a0386a21a13820a03820a1386a21a23820a138" + "20a2386a21a33820a23820a3386a21a43820a33820a4386a21a53820a43820a5386a21a63820a53820a6386a21a738" + "20a63820a7386a21a83820a73820a8386a21a93820a83820a9386a21aa3820a93820aa386a21ab3820aa3820ab386a" + "21ac3820ab3820ac386a21ad3820ac3820ad386a21ae3820ad3820ae386a21af3820ae3820af386a21b03820af3820" + "b0386a21b13820b03820b1386a21b23820b13820b2386a21b33820b23820b3386a21b43820b33820b4386a21b53820" + "b43820b5386a21b63820b53820b6386a21b73820b63820b7386a21b83820b73820b8386a21b93820b83820b9386a21" + "ba3820b93820ba386a21bb3820ba3820bb386a21bc3820bb3820bc386a21bd3820bc3820bd386a21be3820bd3820be" + "386a21bf3820be3820bf386a21c03820bf3820c0386a21c13820c03820c1386a21c23820c13820c2386a21c33820c2" + "3820c3386a21c43820c33820c4386a21c53820c43820c5386a21c63820c53820c6386a21c73820c63820c7386a21c8" + "3820c73820c8386a21c93820c83820c9386a21ca3820c93820ca386a21cb3820ca3820cb386a21cc3820cb3820cc38" + "6a21cd3820cc3820cd386a21ce3820cd3820ce386a21cf3820ce3820cf386a21d03820cf3820d0386a21d13820d038" + "20d1386a21d23820d13820d2386a21d33820d23820d3386a21d43820d33820d4386a21d53820d43820d5386a21d638" + "20d53820d6386a21d73820d63820d7386a21d83820d73820d8386a21d93820d83820d9386a21da3820d93820da386a" + "21db3820da3820db386a21dc3820db3820dc386a21dd3820dc3820dd386a21de3820dd3820de386a21df3820de3820" + "df386a21e03820df3820e0386a21e13820e03820e1386a21e23820e13820e2386a21e33820e23820e3386a21e43820" + "e33820e4386a21e53820e43820e5386a21e63820e53820e6386a21e73820e63820e7386a21e83820e73820e8386a21" + "e93820e83820e9386a21ea3820e93820ea386a21eb3820ea3820eb386a21ec3820eb3820ec386a21ed3820ec3820ed" + "386a21ee3820ed3820ee386a21ef3820ee3820ef386a21f03820ef3820f0386a21f13820f03820f1386a21f23820f1" + "3820f2386a21f33820f23820f3386a21f43820f33820f4386a21f53820f43820f5386a21f63820f53820f6386a21f7" + "3820f63820f7386a21f83820f73820f8386a21f93820f83820f9386a21fa3820f93820fa386a21fb3820fa3820fb38" + "6a21fc3820fb3820fc386a21fd3820fc3820fd386a21fe3820fd3820fe386a21ff3820fe3820ff386a21803920ff38" + "2080396a2181392080392081396a2182392081392082396a2183392082392083396a2184392083392084396a218539" + "2084392085396a2186392085392086396a2187392086392087396a2188392087392088396a2189392088392089396a" + "218a39208939208a396a218b39208a39208b396a218c39208b39208c396a218d39208c39208d396a218e39208d3920" + "8e396a218f39208e39208f396a219039208f392090396a2191392090392091396a2192392091392092396a21933920" + "92392093396a2194392093392094396a2195392094392095396a2196392095392096396a2197392096392097396a21" + "98392097392098396a2199392098392099396a219a39209939209a396a219b39209a39209b396a219c39209b39209c" + "396a219d39209c39209d396a219e39209d39209e396a219f39209e39209f396a21a039209f3920a0396a21a13920a0" + "3920a1396a21a23920a13920a2396a21a33920a23920a3396a21a43920a33920a4396a21a53920a43920a5396a21a6" + "3920a53920a6396a21a73920a63920a7396a21a83920a73920a8396a21a93920a83920a9396a21aa3920a93920aa39" + "6a21ab3920aa3920ab396a21ac3920ab3920ac396a21ad3920ac3920ad396a21ae3920ad3920ae396a21af3920ae39" + "20af396a21b03920af3920b0396a21b13920b03920b1396a21b23920b13920b2396a21b33920b23920b3396a21b439" + "20b33920b4396a21b53920b43920b5396a21b63920b53920b6396a21b73920b63920b7396a21b83920b73920b8396a" + "21b93920b83920b9396a21ba3920b93920ba396a21bb3920ba3920bb396a21bc3920bb3920bc396a21bd3920bc3920" + "bd396a21be3920bd3920be396a21bf3920be3920bf396a21c03920bf3920c0396a21c13920c03920c1396a21c23920" + "c13920c2396a21c33920c23920c3396a21c43920c33920c4396a21c53920c43920c5396a21c63920c53920c6396a21" + "c73920c63920c7396a21c83920c73920c8396a21c93920c83920c9396a21ca3920c93920ca396a21cb3920ca3920cb" + "396a21cc3920cb3920cc396a21cd3920cc3920cd396a21ce3920cd3920ce396a21cf3920ce3920cf396a21d03920cf" + "3920d0396a21d13920d03920d1396a21d23920d13920d2396a21d33920d23920d3396a21d43920d33920d4396a21d5" + "3920d43920d5396a21d63920d53920d6396a21d73920d63920d7396a21d83920d73920d8396a21d93920d83920d939" + "6a21da3920d93920da396a21db3920da3920db396a21dc3920db3920dc396a21dd3920dc3920dd396a21de3920dd39" + "20de396a21df3920de3920df396a21e03920df3920e0396a21e13920e03920e1396a21e23920e13920e2396a21e339" + "20e23920e3396a21e43920e33920e4396a21e53920e43920e5396a21e63920e53920e6396a21e73920e63920e7396a" + "21e83920e73920e8396a21e93920e83920e9396a21ea3920e93920ea396a21eb3920ea3920eb396a21ec3920eb3920" + "ec396a21ed3920ec3920ed396a21ee3920ed3920ee396a21ef3920ee3920ef396a21f03920ef3920f0396a21f13920" + "f03920f1396a21f23920f13920f2396a21f33920f23920f3396a21f43920f33920f4396a21f53920f43920f5396a21" + "f63920f53920f6396a21f73920f63920f7396a21f83920f73920f8396a21f93920f83920f9396a21fa3920f93920fa" + "396a21fb3920fa3920fb396a21fc3920fb3920fc396a21fd3920fc3920fd396a21fe3920fd3920fe396a21ff3920fe" + "3920ff396a21803a20ff3920803a6a21813a20803a20813a6a21823a20813a20823a6a21833a20823a20833a6a2184" + "3a20833a20843a6a21853a20843a20853a6a21863a20853a20863a6a21873a20863a20873a6a21883a20873a20883a" + "6a21893a20883a20893a6a218a3a20893a208a3a6a218b3a208a3a208b3a6a218c3a208b3a208c3a6a218d3a208c3a" + "208d3a6a218e3a208d3a208e3a6a218f3a208e3a208f3a6a21903a208f3a20903a6a21913a20903a20913a6a21923a" + "20913a20923a6a21933a20923a20933a6a21943a20933a20943a6a21953a20943a20953a6a21963a20953a20963a6a" + "21973a20963a20973a6a21983a20973a20983a6a21993a20983a20993a6a219a3a20993a209a3a6a219b3a209a3a20" + "9b3a6a219c3a209b3a209c3a6a219d3a209c3a209d3a6a219e3a209d3a209e3a6a219f3a209e3a209f3a6a21a03a20" + "9f3a20a03a6a21a13a20a03a20a13a6a21a23a20a13a20a23a6a21a33a20a23a20a33a6a21a43a20a33a20a43a6a21" + "a53a20a43a20a53a6a21a63a20a53a20a63a6a21a73a20a63a20a73a6a21a83a20a73a20a83a6a21a93a20a83a20a9" + "3a6a21aa3a20a93a20aa3a6a21ab3a20aa3a20ab3a6a21ac3a20ab3a20ac3a6a21ad3a20ac3a20ad3a6a21ae3a20ad" + "3a20ae3a6a21af3a20ae3a20af3a6a21b03a20af3a20b03a6a21b13a20b03a20b13a6a21b23a20b13a20b23a6a21b3" + "3a20b23a20b33a6a21b43a20b33a20b43a6a21b53a20b43a20b53a6a21b63a20b53a20b63a6a21b73a20b63a20b73a" + "6a21b83a20b73a20b83a6a21b93a20b83a20b93a6a21ba3a20b93a20ba3a6a21bb3a20ba3a20bb3a6a21bc3a20bb3a" + "20bc3a6a21bd3a20bc3a20bd3a6a21be3a20bd3a20be3a6a21bf3a20be3a20bf3a6a21c03a20bf3a20c03a6a21c13a" + "20c03a20c13a6a21c23a20c13a20c23a6a21c33a20c23a20c33a6a21c43a20c33a20c43a6a21c53a20c43a20c53a6a" + "21c63a20c53a20c63a6a21c73a20c63a20c73a6a21c83a20c73a20c83a6a21c93a20c83a20c93a6a21ca3a20c93a20" + "ca3a6a21cb3a20ca3a20cb3a6a21cc3a20cb3a20cc3a6a21cd3a20cc3a20cd3a6a21ce3a20cd3a20ce3a6a21cf3a20" + "ce3a20cf3a6a21d03a20cf3a20d03a6a21d13a20d03a20d13a6a21d23a20d13a20d23a6a21d33a20d23a20d33a6a21" + "d43a20d33a20d43a6a21d53a20d43a20d53a6a21d63a20d53a20d63a6a21d73a20d63a20d73a6a21d83a20d73a20d8" + "3a6a21d93a20d83a20d93a6a21da3a20d93a20da3a6a21db3a20da3a20db3a6a21dc3a20db3a20dc3a6a21dd3a20dc" + "3a20dd3a6a21de3a20dd3a20de3a6a21df3a20de3a20df3a6a21e03a20df3a20e03a6a21e13a20e03a20e13a6a21e2" + "3a20e13a20e23a6a21e33a20e23a20e33a6a21e43a20e33a20e43a6a21e53a20e43a20e53a6a21e63a20e53a20e63a" + "6a21e73a20e63a20e73a6a21e83a20e73a20e83a6a21e93a20e83a20e93a6a21ea3a20e93a20ea3a6a21eb3a20ea3a" + "20eb3a6a21ec3a20eb3a20ec3a6a21ed3a20ec3a20ed3a6a21ee3a20ed3a20ee3a6a21ef3a20ee3a20ef3a6a21f03a" + "20ef3a20f03a6a21f13a20f03a20f13a6a21f23a20f13a20f23a6a21f33a20f23a20f33a6a21f43a20f33a20f43a6a" + "21f53a20f43a20f53a6a21f63a20f53a20f63a6a21f73a20f63a20f73a6a21f83a20f73a20f83a6a21f93a20f83a20" + "f93a6a21fa3a20f93a20fa3a6a21fb3a20fa3a20fb3a6a21fc3a20fb3a20fc3a6a21fd3a20fc3a20fd3a6a21fe3a20" + "fd3a20fe3a6a21ff3a20fe3a20ff3a6a21803b20ff3a20803b6a21813b20803b20813b6a21823b20813b20823b6a21" + "833b20823b20833b6a21843b20833b20843b6a21853b20843b20853b6a21863b20853b20863b6a21873b20863b2087" + "3b6a21883b20873b20883b6a21893b20883b20893b6a218a3b20893b208a3b6a218b3b208a3b208b3b6a218c3b208b" + "3b208c3b6a218d3b208c3b208d3b6a218e3b208d3b208e3b6a218f3b208e3b208f3b6a21903b208f3b20903b6a2191" + "3b20903b20913b6a21923b20913b20923b6a21933b20923b20933b6a21943b20933b20943b6a21953b20943b20953b" + "6a21963b20953b20963b6a21973b20963b20973b6a21983b20973b20983b6a21993b20983b20993b6a219a3b20993b" + "209a3b6a219b3b209a3b209b3b6a219c3b209b3b209c3b6a219d3b209c3b209d3b6a219e3b209d3b209e3b6a219f3b" + "209e3b209f3b6a21a03b209f3b20a03b6a21a13b20a03b20a13b6a21a23b20a13b20a23b6a21a33b20a23b20a33b6a" + "21a43b20a33b20a43b6a21a53b20a43b20a53b6a21a63b20a53b20a63b6a21a73b20a63b20a73b6a21a83b20a73b20" + "a83b6a21a93b20a83b20a93b6a21aa3b20a93b20aa3b6a21ab3b20aa3b20ab3b6a21ac3b20ab3b20ac3b6a21ad3b20" + "ac3b20ad3b6a21ae3b20ad3b20ae3b6a21af3b20ae3b20af3b6a21b03b20af3b20b03b6a21b13b20b03b20b13b6a21" + "b23b20b13b20b23b6a21b33b20b23b20b33b6a21b43b20b33b20b43b6a21b53b20b43b20b53b6a21b63b20b53b20b6" + "3b6a21b73b20b63b20b73b6a21b83b20b73b20b83b6a21b93b20b83b20b93b6a21ba3b20b93b20ba3b6a21bb3b20ba" + "3b20bb3b6a21bc3b20bb3b20bc3b6a21bd3b20bc3b20bd3b6a21be3b20bd3b20be3b6a21bf3b20be3b20bf3b6a21c0" + "3b20bf3b20c03b6a21c13b20c03b20c13b6a21c23b20c13b20c23b6a21c33b20c23b20c33b6a21c43b20c33b20c43b" + "6a21c53b20c43b20c53b6a21c63b20c53b20c63b6a21c73b20c63b20c73b6a21c83b20c73b20c83b6a21c93b20c83b" + "20c93b6a21ca3b20c93b20ca3b6a21cb3b20ca3b20cb3b6a21cc3b20cb3b20cc3b6a21cd3b20cc3b20cd3b6a21ce3b" + "20cd3b20ce3b6a21cf3b20ce3b20cf3b6a21d03b20cf3b20d03b6a21d13b20d03b20d13b6a21d23b20d13b20d23b6a" + "21d33b20d23b20d33b6a21d43b20d33b20d43b6a21d53b20d43b20d53b6a21d63b20d53b20d63b6a21d73b20d63b20" + "d73b6a21d83b20d73b20d83b6a21d93b20d83b20d93b6a21da3b20d93b20da3b6a21db3b20da3b20db3b6a21dc3b20" + "db3b20dc3b6a21dd3b20dc3b20dd3b6a21de3b20dd3b20de3b6a21df3b20de3b20df3b6a21e03b20df3b20e03b6a21" + "e13b20e03b20e13b6a21e23b20e13b20e23b6a21e33b20e23b20e33b6a21e43b20e33b20e43b6a21e53b20e43b20e5" + "3b6a21e63b20e53b20e63b6a21e73b20e63b20e73b6a21e83b20e73b20e83b6a21e93b20e83b20e93b6a21ea3b20e9" + "3b20ea3b6a21eb3b20ea3b20eb3b6a21ec3b20eb3b20ec3b6a21ed3b20ec3b20ed3b6a21ee3b20ed3b20ee3b6a21ef" + "3b20ee3b20ef3b6a21f03b20ef3b20f03b6a21f13b20f03b20f13b6a21f23b20f13b20f23b6a21f33b20f23b20f33b" + "6a21f43b20f33b20f43b6a21f53b20f43b20f53b6a21f63b20f53b20f63b6a21f73b20f63b20f73b6a21f83b20f73b" + "20f83b6a21f93b20f83b20f93b6a21fa3b20f93b20fa3b6a21fb3b20fa3b20fb3b6a21fc3b20fb3b20fc3b6a21fd3b" + "20fc3b20fd3b6a21fe3b20fd3b20fe3b6a21ff3b20fe3b20ff3b6a21803c20ff3b20803c6a21813c20803c20813c6a" + "21823c20813c20823c6a21833c20823c20833c6a21843c20833c20843c6a21853c20843c20853c6a21863c20853c20" + "863c6a21873c20863c20873c6a21883c20873c20883c6a21893c20883c20893c6a218a3c20893c208a3c6a218b3c20" + "8a3c208b3c6a218c3c208b3c208c3c6a218d3c208c3c208d3c6a218e3c208d3c208e3c6a218f3c208e3c208f3c6a21" + "903c208f3c20903c6a21913c20903c20913c6a21923c20913c20923c6a21933c20923c20933c6a21943c20933c2094" + "3c6a21953c20943c20953c6a21963c20953c20963c6a21973c20963c20973c6a21983c20973c20983c6a21993c2098" + "3c20993c6a219a3c20993c209a3c6a219b3c209a3c209b3c6a219c3c209b3c209c3c6a219d3c209c3c209d3c6a219e" + "3c209d3c209e3c6a219f3c209e3c209f3c6a21a03c209f3c20a03c6a21a13c20a03c20a13c6a21a23c20a13c20a23c" + "6a21a33c20a23c20a33c6a21a43c20a33c20a43c6a21a53c20a43c20a53c6a21a63c20a53c20a63c6a21a73c20a63c" + "20a73c6a21a83c20a73c20a83c6a21a93c20a83c20a93c6a21aa3c20a93c20aa3c6a21ab3c20aa3c20ab3c6a21ac3c" + "20ab3c20ac3c6a21ad3c20ac3c20ad3c6a21ae3c20ad3c20ae3c6a21af3c20ae3c20af3c6a21b03c20af3c20b03c6a" + "21b13c20b03c20b13c6a21b23c20b13c20b23c6a21b33c20b23c20b33c6a21b43c20b33c20b43c6a21b53c20b43c20" + "b53c6a21b63c20b53c20b63c6a21b73c20b63c20b73c6a21b83c20b73c20b83c6a21b93c20b83c20b93c6a21ba3c20" + "b93c20ba3c6a21bb3c20ba3c20bb3c6a21bc3c20bb3c20bc3c6a21bd3c20bc3c20bd3c6a21be3c20bd3c20be3c6a21" + "bf3c20be3c20bf3c6a21c03c20bf3c20c03c6a21c13c20c03c20c13c6a21c23c20c13c20c23c6a21c33c20c23c20c3" + "3c6a21c43c20c33c20c43c6a21c53c20c43c20c53c6a21c63c20c53c20c63c6a21c73c20c63c20c73c6a21c83c20c7" + "3c20c83c6a21c93c20c83c20c93c6a21ca3c20c93c20ca3c6a21cb3c20ca3c20cb3c6a21cc3c20cb3c20cc3c6a21cd" + "3c20cc3c20cd3c6a21ce3c20cd3c20ce3c6a21cf3c20ce3c20cf3c6a21d03c20cf3c20d03c6a21d13c20d03c20d13c" + "6a21d23c20d13c20d23c6a21d33c20d23c20d33c6a21d43c20d33c20d43c6a21d53c20d43c20d53c6a21d63c20d53c" + "20d63c6a21d73c20d63c20d73c6a21d83c20d73c20d83c6a21d93c20d83c20d93c6a21da3c20d93c20da3c6a21db3c" + "20da3c20db3c6a21dc3c20db3c20dc3c6a21dd3c20dc3c20dd3c6a21de3c20dd3c20de3c6a21df3c20de3c20df3c6a" + "21e03c20df3c20e03c6a21e13c20e03c20e13c6a21e23c20e13c20e23c6a21e33c20e23c20e33c6a21e43c20e33c20" + "e43c6a21e53c20e43c20e53c6a21e63c20e53c20e63c6a21e73c20e63c20e73c6a21e83c20e73c20e83c6a21e93c20" + "e83c20e93c6a21ea3c20e93c20ea3c6a21eb3c20ea3c20eb3c6a21ec3c20eb3c20ec3c6a21ed3c20ec3c20ed3c6a21" + "ee3c20ed3c20ee3c6a21ef3c20ee3c20ef3c6a21f03c20ef3c20f03c6a21f13c20f03c20f13c6a21f23c20f13c20f2" + "3c6a21f33c20f23c20f33c6a21f43c20f33c20f43c6a21f53c20f43c20f53c6a21f63c20f53c20f63c6a21f73c20f6" + "3c20f73c6a21f83c20f73c20f83c6a21f93c20f83c20f93c6a21fa3c20f93c20fa3c6a21fb3c20fa3c20fb3c6a21fc" + "3c20fb3c20fc3c6a21fd3c20fc3c20fd3c6a21fe3c20fd3c20fe3c6a21ff3c20fe3c20ff3c6a21803d20ff3c20803d" + "6a21813d20803d20813d6a21823d20813d20823d6a21833d20823d20833d6a21843d20833d20843d6a21853d20843d" + "20853d6a21863d20853d20863d6a21873d20863d20873d6a21883d20873d20883d6a21893d20883d20893d6a218a3d" + "20893d208a3d6a218b3d208a3d208b3d6a218c3d208b3d208c3d6a218d3d208c3d208d3d6a218e3d208d3d208e3d6a" + "218f3d208e3d208f3d6a21903d208f3d20903d6a21913d20903d20913d6a21923d20913d20923d6a21933d20923d20" + "933d6a21943d20933d20943d6a21953d20943d20953d6a21963d20953d20963d6a21973d20963d20973d6a21983d20" + "973d20983d6a21993d20983d20993d6a219a3d20993d209a3d6a219b3d209a3d209b3d6a219c3d209b3d209c3d6a21" + "9d3d209c3d209d3d6a219e3d209d3d209e3d6a219f3d209e3d209f3d6a21a03d209f3d20a03d6a21a13d20a03d20a1" + "3d6a21a23d20a13d20a23d6a21a33d20a23d20a33d6a21a43d20a33d20a43d6a21a53d20a43d20a53d6a21a63d20a5" + "3d20a63d6a21a73d20a63d20a73d6a21a83d20a73d20a83d6a21a93d20a83d20a93d6a21aa3d20a93d20aa3d6a21ab" + "3d20aa3d20ab3d6a21ac3d20ab3d20ac3d6a21ad3d20ac3d20ad3d6a21ae3d20ad3d20ae3d6a21af3d20ae3d20af3d" + "6a21b03d20af3d20b03d6a21b13d20b03d20b13d6a21b23d20b13d20b23d6a21b33d20b23d20b33d6a21b43d20b33d" + "20b43d6a21b53d20b43d20b53d6a21b63d20b53d20b63d6a21b73d20b63d20b73d6a21b83d20b73d20b83d6a21b93d" + "20b83d20b93d6a21ba3d20b93d20ba3d6a21bb3d20ba3d20bb3d6a21bc3d20bb3d20bc3d6a21bd3d20bc3d20bd3d6a" + "21be3d20bd3d20be3d6a21bf3d20be3d20bf3d6a21c03d20bf3d20c03d6a21c13d20c03d20c13d6a21c23d20c13d20" + "c23d6a21c33d20c23d20c33d6a21c43d20c33d20c43d6a21c53d20c43d20c53d6a21c63d20c53d20c63d6a21c73d20" + "c63d20c73d6a21c83d20c73d20c83d6a21c93d20c83d20c93d6a21ca3d20c93d20ca3d6a21cb3d20ca3d20cb3d6a21" + "cc3d20cb3d20cc3d6a21cd3d20cc3d20cd3d6a21ce3d20cd3d20ce3d6a21cf3d20ce3d20cf3d6a21d03d20cf3d20d0" + "3d6a21d13d20d03d20d13d6a21d23d20d13d20d23d6a21d33d20d23d20d33d6a21d43d20d33d20d43d6a21d53d20d4" + "3d20d53d6a21d63d20d53d20d63d6a21d73d20d63d20d73d6a21d83d20d73d20d83d6a21d93d20d83d20d93d6a21da" + "3d20d93d20da3d6a21db3d20da3d20db3d6a21dc3d20db3d20dc3d6a21dd3d20dc3d20dd3d6a21de3d20dd3d20de3d" + "6a21df3d20de3d20df3d6a21e03d20df3d20e03d6a21e13d20e03d20e13d6a21e23d20e13d20e23d6a21e33d20e23d" + "20e33d6a21e43d20e33d20e43d6a21e53d20e43d20e53d6a21e63d20e53d20e63d6a21e73d20e63d20e73d6a21e83d" + "20e73d20e83d6a21e93d20e83d20e93d6a21ea3d20e93d20ea3d6a21eb3d20ea3d20eb3d6a21ec3d20eb3d20ec3d6a" + "21ed3d20ec3d20ed3d6a21ee3d20ed3d20ee3d6a21ef3d20ee3d20ef3d6a21f03d20ef3d20f03d6a21f13d20f03d20" + "f13d6a21f23d20f13d20f23d6a21f33d20f23d20f33d6a21f43d20f33d20f43d6a21f53d20f43d20f53d6a21f63d20" + "f53d20f63d6a21f73d20f63d20f73d6a21f83d20f73d20f83d6a21f93d20f83d20f93d6a21fa3d20f93d20fa3d6a21" + "fb3d20fa3d20fb3d6a21fc3d20fb3d20fc3d6a21fd3d20fc3d20fd3d6a21fe3d20fd3d20fe3d6a21ff3d20fe3d20ff" + "3d6a21803e20ff3d20803e6a21813e20803e20813e6a21823e20813e20823e6a21833e20823e20833e6a21843e2083" + "3e20843e6a21853e20843e20853e6a21863e20853e20863e6a21873e20863e20873e6a21883e20873e20883e6a2189" + "3e20883e20893e6a218a3e20893e208a3e6a218b3e208a3e208b3e6a218c3e208b3e208c3e6a218d3e208c3e208d3e" + "6a218e3e208d3e208e3e6a218f3e208e3e208f3e6a21903e208f3e20903e6a21913e20903e20913e6a21923e20913e" + "20923e6a21933e20923e20933e6a21943e20933e20943e6a21953e20943e20953e6a21963e20953e20963e6a21973e" + "20963e20973e6a21983e20973e20983e6a21993e20983e20993e6a219a3e20993e209a3e6a219b3e209a3e209b3e6a" + "219c3e209b3e209c3e6a219d3e209c3e209d3e6a219e3e209d3e209e3e6a219f3e209e3e209f3e6a21a03e209f3e20" + "a03e6a21a13e20a03e20a13e6a21a23e20a13e20a23e6a21a33e20a23e20a33e6a21a43e20a33e20a43e6a21a53e20" + "a43e20a53e6a21a63e20a53e20a63e6a21a73e20a63e20a73e6a21a83e20a73e20a83e6a21a93e20a83e20a93e6a21" + "aa3e20a93e20aa3e6a21ab3e20aa3e20ab3e6a21ac3e20ab3e20ac3e6a21ad3e20ac3e20ad3e6a21ae3e20ad3e20ae" + "3e6a21af3e20ae3e20af3e6a21b03e20af3e20b03e6a21b13e20b03e20b13e6a21b23e20b13e20b23e6a21b33e20b2" + "3e20b33e6a21b43e20b33e20b43e6a21b53e20b43e20b53e6a21b63e20b53e20b63e6a21b73e20b63e20b73e6a21b8" + "3e20b73e20b83e6a21b93e20b83e20b93e6a21ba3e20b93e20ba3e6a21bb3e20ba3e20bb3e6a21bc3e20bb3e20bc3e" + "6a21bd3e20bc3e20bd3e6a21be3e20bd3e20be3e6a21bf3e20be3e20bf3e6a21c03e20bf3e20c03e6a21c13e20c03e" + "20c13e6a21c23e20c13e20c23e6a21c33e20c23e20c33e6a21c43e20c33e20c43e6a21c53e20c43e20c53e6a21c63e" + "20c53e20c63e6a21c73e20c63e20c73e6a21c83e20c73e20c83e6a21c93e20c83e20c93e6a21ca3e20c93e20ca3e6a" + "21cb3e20ca3e20cb3e6a21cc3e20cb3e20cc3e6a21cd3e20cc3e20cd3e6a21ce3e20cd3e20ce3e6a21cf3e20ce3e20" + "cf3e6a21d03e20cf3e20d03e6a21d13e20d03e20d13e6a21d23e20d13e20d23e6a21d33e20d23e20d33e6a21d43e20" + "d33e20d43e6a21d53e20d43e20d53e6a21d63e20d53e20d63e6a21d73e20d63e20d73e6a21d83e20d73e20d83e6a21" + "d93e20d83e20d93e6a21da3e20d93e20da3e6a21db3e20da3e20db3e6a21dc3e20db3e20dc3e6a21dd3e20dc3e20dd" + "3e6a21de3e20dd3e20de3e6a21df3e20de3e20df3e6a21e03e20df3e20e03e6a21e13e20e03e20e13e6a21e23e20e1" + "3e20e23e6a21e33e20e23e20e33e6a21e43e20e33e20e43e6a21e53e20e43e20e53e6a21e63e20e53e20e63e6a21e7" + "3e20e63e20e73e6a21e83e20e73e20e83e6a21e93e20e83e20e93e6a21ea3e20e93e20ea3e6a21eb3e20ea3e20eb3e" + "6a21ec3e20eb3e20ec3e6a21ed3e20ec3e20ed3e6a21ee3e20ed3e20ee3e6a21ef3e20ee3e20ef3e6a21f03e20ef3e" + "20f03e6a21f13e20f03e20f13e6a21f23e20f13e20f23e6a21f33e20f23e20f33e6a21f43e20f33e20f43e6a21f53e" + "20f43e20f53e6a21f63e20f53e20f63e6a21f73e20f63e20f73e6a21f83e20f73e20f83e6a21f93e20f83e20f93e6a" + "21fa3e20f93e20fa3e6a21fb3e20fa3e20fb3e6a21fc3e20fb3e20fc3e6a21fd3e20fc3e20fd3e6a21fe3e20fd3e20" + "fe3e6a21ff3e20fe3e20ff3e6a21803f20ff3e20803f6a21813f20803f20813f6a21823f20813f20823f6a21833f20" + "823f20833f6a21843f20833f20843f6a21853f20843f20853f6a21863f20853f20863f6a21873f20863f20873f6a21" + "883f20873f20883f6a21893f20883f20893f6a218a3f20893f208a3f6a218b3f208a3f208b3f6a218c3f208b3f208c" + "3f6a218d3f208c3f208d3f6a218e3f208d3f208e3f6a218f3f208e3f208f3f6a21903f208f3f20903f6a21913f2090" + "3f20913f6a21923f20913f20923f6a21933f20923f20933f6a21943f20933f20943f6a21953f20943f20953f6a2196" + "3f20953f20963f6a21973f20963f20973f6a21983f20973f20983f6a21993f20983f20993f6a219a3f20993f209a3f" + "6a219b3f209a3f209b3f6a219c3f209b3f209c3f6a219d3f209c3f209d3f6a219e3f209d3f209e3f6a219f3f209e3f" + "209f3f6a21a03f209f3f20a03f6a21a13f20a03f20a13f6a21a23f20a13f20a23f6a21a33f20a23f20a33f6a21a43f" + "20a33f20a43f6a21a53f20a43f20a53f6a21a63f20a53f20a63f6a21a73f20a63f20a73f6a21a83f20a73f20a83f6a" + "21a93f20a83f20a93f6a21aa3f20a93f20aa3f6a21ab3f20aa3f20ab3f6a21ac3f20ab3f20ac3f6a21ad3f20ac3f20" + "ad3f6a21ae3f20ad3f20ae3f6a21af3f20ae3f20af3f6a21b03f20af3f20b03f6a21b13f20b03f20b13f6a21b23f20" + "b13f20b23f6a21b33f20b23f20b33f6a21b43f20b33f20b43f6a21b53f20b43f20b53f6a21b63f20b53f20b63f6a21" + "b73f20b63f20b73f6a21b83f20b73f20b83f6a21b93f20b83f20b93f6a21ba3f20b93f20ba3f6a21bb3f20ba3f20bb" + "3f6a21bc3f20bb3f20bc3f6a21bd3f20bc3f20bd3f6a21be3f20bd3f20be3f6a21bf3f20be3f20bf3f6a21c03f20bf" + "3f20c03f6a21c13f20c03f20c13f6a21c23f20c13f20c23f6a21c33f20c23f20c33f6a21c43f20c33f20c43f6a21c5" + "3f20c43f20c53f6a21c63f20c53f20c63f6a21c73f20c63f20c73f6a21c83f20c73f20c83f6a21c93f20c83f20c93f" + "6a21ca3f20c93f20ca3f6a21cb3f20ca3f20cb3f6a21cc3f20cb3f20cc3f6a21cd3f20cc3f20cd3f6a21ce3f20cd3f" + "20ce3f6a21cf3f20ce3f20cf3f6a21d03f20cf3f20d03f6a21d13f20d03f20d13f6a21d23f20d13f20d23f6a21d33f" + "20d23f20d33f6a21d43f20d33f20d43f6a21d53f20d43f20d53f6a21d63f20d53f20d63f6a21d73f20d63f20d73f6a" + "21d83f20d73f20d83f6a21d93f20d83f20d93f6a21da3f20d93f20da3f6a21db3f20da3f20db3f6a21dc3f20db3f20" + "dc3f6a21dd3f20dc3f20dd3f6a21de3f20dd3f20de3f6a21df3f20de3f20df3f6a21e03f20df3f20e03f6a21e13f20" + "e03f20e13f6a21e23f20e13f20e23f6a21e33f20e23f20e33f6a21e43f20e33f20e43f6a21e53f20e43f20e53f6a21" + "e63f20e53f20e63f6a21e73f20e63f20e73f6a21e83f20e73f20e83f6a21e93f20e83f20e93f6a21ea3f20e93f20ea" + "3f6a21eb3f20ea3f20eb3f6a21ec3f20eb3f20ec3f6a21ed3f20ec3f20ed3f6a21ee3f20ed3f20ee3f6a21ef3f20ee" + "3f20ef3f6a21f03f20ef3f20f03f6a21f13f20f03f20f13f6a21f23f20f13f20f23f6a21f33f20f23f20f33f6a21f4" + "3f20f33f20f43f6a21f53f20f43f20f53f6a21f63f20f53f20f63f6a21f73f20f63f20f73f6a21f83f20f73f20f83f" + "6a21f93f20f83f20f93f6a21fa3f20f93f20fa3f6a21fb3f20fa3f20fb3f6a21fc3f20fb3f20fc3f6a21fd3f20fc3f" + "20fd3f6a21fe3f20fd3f20fe3f6a21ff3f20fe3f20ff3f6a21804020ff3f2080406a2181402080402081406a218240" + "2081402082406a2183402082402083406a2184402083402084406a2185402084402085406a2186402085402086406a" + "2187402086402087406a2188402087402088406a2189402088402089406a218a40208940208a406a218b40208a4020" + "8b406a218c40208b40208c406a218d40208c40208d406a218e40208d40208e406a218f40208e40208f406a21904020" + "8f402090406a2191402090402091406a2192402091402092406a2193402092402093406a2194402093402094406a21" + "95402094402095406a2196402095402096406a2197402096402097406a2198402097402098406a2199402098402099" + "406a219a40209940209a406a219b40209a40209b406a219c40209b40209c406a219d40209c40209d406a219e40209d" + "40209e406a219f40209e40209f406a21a040209f4020a0406a21a14020a04020a1406a21a24020a14020a2406a21a3" + "4020a24020a3406a21a44020a34020a4406a21a54020a44020a5406a21a64020a54020a6406a21a74020a64020a740" + "6a21a84020a74020a8406a21a94020a84020a9406a21aa4020a94020aa406a21ab4020aa4020ab406a21ac4020ab40" + "20ac406a21ad4020ac4020ad406a21ae4020ad4020ae406a21af4020ae4020af406a21b04020af4020b0406a21b140" + "20b04020b1406a21b24020b14020b2406a21b34020b24020b3406a21b44020b34020b4406a21b54020b44020b5406a" + "21b64020b54020b6406a21b74020b64020b7406a21b84020b74020b8406a21b94020b84020b9406a21ba4020b94020" + "ba406a21bb4020ba4020bb406a21bc4020bb4020bc406a21bd4020bc4020bd406a21be4020bd4020be406a21bf4020" + "be4020bf406a21c04020bf4020c0406a21c14020c04020c1406a21c24020c14020c2406a21c34020c24020c3406a21" + "c44020c34020c4406a21c54020c44020c5406a21c64020c54020c6406a21c74020c64020c7406a21c84020c74020c8" + "406a21c94020c84020c9406a21ca4020c94020ca406a21cb4020ca4020cb406a21cc4020cb4020cc406a21cd4020cc" + "4020cd406a21ce4020cd4020ce406a21cf4020ce4020cf406a21d04020cf4020d0406a21d14020d04020d1406a21d2" + "4020d14020d2406a21d34020d24020d3406a21d44020d34020d4406a21d54020d44020d5406a21d64020d54020d640" + "6a21d74020d64020d7406a21d84020d74020d8406a21d94020d84020d9406a21da4020d94020da406a21db4020da40" + "20db406a21dc4020db4020dc406a21dd4020dc4020dd406a21de4020dd4020de406a21df4020de4020df406a21e040" + "20df4020e0406a21e14020e04020e1406a21e24020e14020e2406a21e34020e24020e3406a21e44020e34020e4406a" + "21e54020e44020e5406a21e64020e54020e6406a21e74020e64020e7406a21e84020e74020e8406a21e94020e84020" + "e9406a21ea4020e94020ea406a21eb4020ea4020eb406a21ec4020eb4020ec406a21ed4020ec4020ed406a21ee4020" + "ed4020ee406a21ef4020ee4020ef406a21f04020ef4020f0406a21f14020f04020f1406a21f24020f14020f2406a21" + "f34020f24020f3406a21f44020f34020f4406a21f54020f44020f5406a21f64020f54020f6406a21f74020f64020f7" + "406a21f84020f74020f8406a21f94020f84020f9406a21fa4020f94020fa406a21fb4020fa4020fb406a21fc4020fb" + "4020fc406a21fd4020fc4020fd406a21fe4020fd4020fe406a21ff4020fe4020ff406a21804120ff402080416a2181" + "412080412081416a2182412081412082416a2183412082412083416a2184412083412084416a218541208441208541" + "6a2186412085412086416a2187412086412087416a2188412087412088416a2189412088412089416a218a41208941" + "208a416a218b41208a41208b416a218c41208b41208c416a218d41208c41208d416a218e41208d41208e416a218f41" + "208e41208f416a219041208f412090416a2191412090412091416a2192412091412092416a2193412092412093416a" + "2194412093412094416a2195412094412095416a2196412095412096416a2197412096412097416a21984120974120" + "98416a2199412098412099416a219a41209941209a416a219b41209a41209b416a219c41209b41209c416a219d4120" + "9c41209d416a219e41209d41209e416a219f41209e41209f416a21a041209f4120a0416a21a14120a04120a1416a21" + "a24120a14120a2416a21a34120a24120a3416a21a44120a34120a4416a21a54120a44120a5416a21a64120a54120a6" + "416a21a74120a64120a7416a21a84120a74120a8416a21a94120a84120a9416a21aa4120a94120aa416a21ab4120aa" + "4120ab416a21ac4120ab4120ac416a21ad4120ac4120ad416a21ae4120ad4120ae416a21af4120ae4120af416a21b0" + "4120af4120b0416a21b14120b04120b1416a21b24120b14120b2416a21b34120b24120b3416a21b44120b34120b441" + "6a21b54120b44120b5416a21b64120b54120b6416a21b74120b64120b7416a21b84120b74120b8416a21b94120b841" + "20b9416a21ba4120b94120ba416a21bb4120ba4120bb416a21bc4120bb4120bc416a21bd4120bc4120bd416a21be41" + "20bd4120be416a21bf4120be4120bf416a21c04120bf4120c0416a21c14120c04120c1416a21c24120c14120c2416a" + "21c34120c24120c3416a21c44120c34120c4416a21c54120c44120c5416a21c64120c54120c6416a21c74120c64120" + "c7416a21c84120c74120c8416a21c94120c84120c9416a21ca4120c94120ca416a21cb4120ca4120cb416a21cc4120" + "cb4120cc416a21cd4120cc4120cd416a21ce4120cd4120ce416a21cf4120ce4120cf416a21d04120cf4120d0416a21" + "d14120d04120d1416a21d24120d14120d2416a21d34120d24120d3416a21d44120d34120d4416a21d54120d44120d5" + "416a21d64120d54120d6416a21d74120d64120d7416a21d84120d74120d8416a21d94120d84120d9416a21da4120d9" + "4120da416a21db4120da4120db416a21dc4120db4120dc416a21dd4120dc4120dd416a21de4120dd4120de416a21df" + "4120de4120df416a21e04120df4120e0416a21e14120e04120e1416a21e24120e14120e2416a21e34120e24120e341" + "6a21e44120e34120e4416a21e54120e44120e5416a21e64120e54120e6416a21e74120e64120e7416a21e84120e741" + "20e8416a21e94120e84120e9416a21ea4120e94120ea416a21eb4120ea4120eb416a21ec4120eb4120ec416a21ed41" + "20ec4120ed416a21ee4120ed4120ee416a21ef4120ee4120ef416a21f04120ef4120f0416a21f14120f04120f1416a" + "21f24120f14120f2416a21f34120f24120f3416a21f44120f34120f4416a21f54120f44120f5416a21f64120f54120" + "f6416a21f74120f64120f7416a21f84120f74120f8416a21f94120f84120f9416a21fa4120f94120fa416a21fb4120" + "fa4120fb416a21fc4120fb4120fc416a21fd4120fc4120fd416a21fe4120fd4120fe416a21ff4120fe4120ff416a21" + "804220ff412080426a2181422080422081426a2182422081422082426a2183422082422083426a2184422083422084" + "426a2185422084422085426a2186422085422086426a2187422086422087426a2188422087422088426a2189422088" + "422089426a218a42208942208a426a218b42208a42208b426a218c42208b42208c426a218d42208c42208d426a218e" + "42208d42208e426a218f42208e42208f426a219042208f422090426a2191422090422091426a219242209142209242" + "6a2193422092422093426a2194422093422094426a2195422094422095426a2196422095422096426a219742209642" + "2097426a2198422097422098426a2199422098422099426a219a42209942209a426a219b42209a42209b426a219c42" + "209b42209c426a219d42209c42209d426a219e42209d42209e426a219f42209e42209f426a21a042209f4220a0426a" + "21a14220a04220a1426a21a24220a14220a2426a21a34220a24220a3426a21a44220a34220a4426a21a54220a44220" + "a5426a21a64220a54220a6426a21a74220a64220a7426a21a84220a74220a8426a21a94220a84220a9426a21aa4220" + "a94220aa426a21ab4220aa4220ab426a21ac4220ab4220ac426a21ad4220ac4220ad426a21ae4220ad4220ae426a21" + "af4220ae4220af426a21b04220af4220b0426a21b14220b04220b1426a21b24220b14220b2426a21b34220b24220b3" + "426a21b44220b34220b4426a21b54220b44220b5426a21b64220b54220b6426a21b74220b64220b7426a21b84220b7" + "4220b8426a21b94220b84220b9426a21ba4220b94220ba426a21bb4220ba4220bb426a21bc4220bb4220bc426a21bd" + "4220bc4220bd426a21be4220bd4220be426a21bf4220be4220bf426a21c04220bf4220c0426a21c14220c04220c142" + "6a21c24220c14220c2426a21c34220c24220c3426a21c44220c34220c4426a21c54220c44220c5426a21c64220c542" + "20c6426a21c74220c64220c7426a21c84220c74220c8426a21c94220c84220c9426a21ca4220c94220ca426a21cb42" + "20ca4220cb426a21cc4220cb4220cc426a21cd4220cc4220cd426a21ce4220cd4220ce426a21cf4220ce4220cf426a" + "21d04220cf4220d0426a21d14220d04220d1426a21d24220d14220d2426a21d34220d24220d3426a21d44220d34220" + "d4426a21d54220d44220d5426a21d64220d54220d6426a21d74220d64220d7426a21d84220d74220d8426a21d94220" + "d84220d9426a21da4220d94220da426a21db4220da4220db426a21dc4220db4220dc426a21dd4220dc4220dd426a21" + "de4220dd4220de426a21df4220de4220df426a21e04220df4220e0426a21e14220e04220e1426a21e24220e14220e2" + "426a21e34220e24220e3426a21e44220e34220e4426a21e54220e44220e5426a21e64220e54220e6426a21e74220e6" + "4220e7426a21e84220e74220e8426a21e94220e84220e9426a21ea4220e94220ea426a21eb4220ea4220eb426a21ec" + "4220eb4220ec426a21ed4220ec4220ed426a21ee4220ed4220ee426a21ef4220ee4220ef426a21f04220ef4220f042" + "6a21f14220f04220f1426a21f24220f14220f2426a21f34220f24220f3426a21f44220f34220f4426a21f54220f442" + "20f5426a21f64220f54220f6426a21f74220f64220f7426a21f84220f74220f8426a21f94220f84220f9426a21fa42" + "20f94220fa426a21fb4220fa4220fb426a21fc4220fb4220fc426a21fd4220fc4220fd426a21fe4220fd4220fe426a" + "21ff4220fe4220ff426a21804320ff422080436a2181432080432081436a2182432081432082436a21834320824320" + "83436a2184432083432084436a2185432084432085436a2186432085432086436a2187432086432087436a21884320" + "87432088436a2189432088432089436a218a43208943208a436a218b43208a43208b436a218c43208b43208c436a21" + "8d43208c43208d436a218e43208d43208e436a218f43208e43208f436a219043208f432090436a2191432090432091" + "436a2192432091432092436a2193432092432093436a2194432093432094436a2195432094432095436a2196432095" + "432096436a2197432096432097436a2198432097432098436a2199432098432099436a219a43209943209a436a219b" + "43209a43209b436a219c43209b43209c436a219d43209c43209d436a219e43209d43209e436a219f43209e43209f43" + "6a21a043209f4320a0436a21a14320a04320a1436a21a24320a14320a2436a21a34320a24320a3436a21a44320a343" + "20a4436a21a54320a44320a5436a21a64320a54320a6436a21a74320a64320a7436a21a84320a74320a8436a21a943" + "20a84320a9436a21aa4320a94320aa436a21ab4320aa4320ab436a21ac4320ab4320ac436a21ad4320ac4320ad436a" + "21ae4320ad4320ae436a21af4320ae4320af436a21b04320af4320b0436a21b14320b04320b1436a21b24320b14320" + "b2436a21b34320b24320b3436a21b44320b34320b4436a21b54320b44320b5436a21b64320b54320b6436a21b74320" + "b64320b7436a21b84320b74320b8436a21b94320b84320b9436a21ba4320b94320ba436a21bb4320ba4320bb436a21" + "bc4320bb4320bc436a21bd4320bc4320bd436a21be4320bd4320be436a21bf4320be4320bf436a21c04320bf4320c0" + "436a21c14320c04320c1436a21c24320c14320c2436a21c34320c24320c3436a21c44320c34320c4436a21c54320c4" + "4320c5436a21c64320c54320c6436a21c74320c64320c7436a21c84320c74320c8436a21c94320c84320c9436a21ca" + "4320c94320ca436a21cb4320ca4320cb436a21cc4320cb4320cc436a21cd4320cc4320cd436a21ce4320cd4320ce43" + "6a21cf4320ce4320cf436a21d04320cf4320d0436a21d14320d04320d1436a21d24320d14320d2436a21d34320d243" + "20d3436a21d44320d34320d4436a21d54320d44320d5436a21d64320d54320d6436a21d74320d64320d7436a21d843" + "20d74320d8436a21d94320d84320d9436a21da4320d94320da436a21db4320da4320db436a21dc4320db4320dc436a" + "21dd4320dc4320dd436a21de4320dd4320de436a21df4320de4320df436a21e04320df4320e0436a21e14320e04320" + "e1436a21e24320e14320e2436a21e34320e24320e3436a21e44320e34320e4436a21e54320e44320e5436a21e64320" + "e54320e6436a21e74320e64320e7436a21e84320e74320e8436a21e94320e84320e9436a21ea4320e94320ea436a21" + "eb4320ea4320eb436a21ec4320eb4320ec436a21ed4320ec4320ed436a21ee4320ed4320ee436a21ef4320ee4320ef" + "436a21f04320ef4320f0436a21f14320f04320f1436a21f24320f14320f2436a21f34320f24320f3436a21f44320f3" + "4320f4436a21f54320f44320f5436a21f64320f54320f6436a21f74320f64320f7436a21f84320f74320f8436a21f9" + "4320f84320f9436a21fa4320f94320fa436a21fb4320fa4320fb436a21fc4320fb4320fc436a21fd4320fc4320fd43" + "6a21fe4320fd4320fe436a21ff4320fe4320ff436a21804420ff432080446a2181442080442081446a218244208144" + "2082446a2183442082442083446a2184442083442084446a2185442084442085446a2186442085442086446a218744" + "2086442087446a2188442087442088446a2189442088442089446a218a44208944208a446a218b44208a44208b446a" + "218c44208b44208c446a218d44208c44208d446a218e44208d44208e446a218f44208e44208f446a219044208f4420" + "90446a2191442090442091446a2192442091442092446a2193442092442093446a2194442093442094446a21954420" + "94442095446a2196442095442096446a2197442096442097446a2198442097442098446a2199442098442099446a21" + "9a44209944209a446a219b44209a44209b446a219c44209b44209c446a219d44209c44209d446a219e44209d44209e" + "446a219f44209e44209f446a21a044209f4420a0446a21a14420a04420a1446a21a24420a14420a2446a21a34420a2" + "4420a3446a21a44420a34420a4446a21a54420a44420a5446a21a64420a54420a6446a21a74420a64420a7446a21a8" + "4420a74420a8446a21a94420a84420a9446a21aa4420a94420aa446a21ab4420aa4420ab446a21ac4420ab4420ac44" + "6a21ad4420ac4420ad446a21ae4420ad4420ae446a21af4420ae4420af446a21b04420af4420b0446a21b14420b044" + "20b1446a21b24420b14420b2446a21b34420b24420b3446a21b44420b34420b4446a21b54420b44420b5446a21b644" + "20b54420b6446a21b74420b64420b7446a21b84420b74420b8446a21b94420b84420b9446a21ba4420b94420ba446a" + "21bb4420ba4420bb446a21bc4420bb4420bc446a21bd4420bc4420bd446a21be4420bd4420be446a21bf4420be4420" + "bf446a21c04420bf4420c0446a21c14420c04420c1446a21c24420c14420c2446a21c34420c24420c3446a21c44420" + "c34420c4446a21c54420c44420c5446a21c64420c54420c6446a21c74420c64420c7446a21c84420c74420c8446a21" + "c94420c84420c9446a21ca4420c94420ca446a21cb4420ca4420cb446a21cc4420cb4420cc446a21cd4420cc4420cd" + "446a21ce4420cd4420ce446a21cf4420ce4420cf446a21d04420cf4420d0446a21d14420d04420d1446a21d24420d1" + "4420d2446a21d34420d24420d3446a21d44420d34420d4446a21d54420d44420d5446a21d64420d54420d6446a21d7" + "4420d64420d7446a21d84420d74420d8446a21d94420d84420d9446a21da4420d94420da446a21db4420da4420db44" + "6a21dc4420db4420dc446a21dd4420dc4420dd446a21de4420dd4420de446a21df4420de4420df446a21e04420df44" + "20e0446a21e14420e04420e1446a21e24420e14420e2446a21e34420e24420e3446a21e44420e34420e4446a21e544" + "20e44420e5446a21e64420e54420e6446a21e74420e64420e7446a21e84420e74420e8446a21e94420e84420e9446a" + "21ea4420e94420ea446a21eb4420ea4420eb446a21ec4420eb4420ec446a21ed4420ec4420ed446a21ee4420ed4420" + "ee446a21ef4420ee4420ef446a21f04420ef4420f0446a21f14420f04420f1446a21f24420f14420f2446a21f34420" + "f24420f3446a21f44420f34420f4446a21f54420f44420f5446a21f64420f54420f6446a21f74420f64420f7446a21" + "f84420f74420f8446a21f94420f84420f9446a21fa4420f94420fa446a21fb4420fa4420fb446a21fc4420fb4420fc" + "446a21fd4420fc4420fd446a21fe4420fd4420fe446a21ff4420fe4420ff446a21804520ff442080456a2181452080" + "452081456a2182452081452082456a2183452082452083456a2184452083452084456a2185452084452085456a2186" + "452085452086456a2187452086452087456a2188452087452088456a2189452088452089456a218a45208945208a45" + "6a218b45208a45208b456a218c45208b45208c456a218d45208c45208d456a218e45208d45208e456a218f45208e45" + "208f456a219045208f452090456a2191452090452091456a2192452091452092456a2193452092452093456a219445" + "2093452094456a2195452094452095456a2196452095452096456a2197452096452097456a2198452097452098456a" + "2199452098452099456a219a45209945209a456a219b45209a45209b456a219c45209b45209c456a219d45209c4520" + "9d456a219e45209d45209e456a219f45209e45209f456a21a045209f4520a0456a21a14520a04520a1456a21a24520" + "a14520a2456a21a34520a24520a3456a21a44520a34520a4456a21a54520a44520a5456a21a64520a54520a6456a21" + "a74520a64520a7456a21a84520a74520a8456a21a94520a84520a9456a21aa4520a94520aa456a21ab4520aa4520ab" + "456a21ac4520ab4520ac456a21ad4520ac4520ad456a21ae4520ad4520ae456a21af4520ae4520af456a21b04520af" + "4520b0456a21b14520b04520b1456a21b24520b14520b2456a21b34520b24520b3456a21b44520b34520b4456a21b5" + "4520b44520b5456a21b64520b54520b6456a21b74520b64520b7456a21b84520b74520b8456a21b94520b84520b945" + "6a21ba4520b94520ba456a21bb4520ba4520bb456a21bc4520bb4520bc456a21bd4520bc4520bd456a21be4520bd45" + "20be456a21bf4520be4520bf456a21c04520bf4520c0456a21c14520c04520c1456a21c24520c14520c2456a21c345" + "20c24520c3456a21c44520c34520c4456a21c54520c44520c5456a21c64520c54520c6456a21c74520c64520c7456a" + "21c84520c74520c8456a21c94520c84520c9456a21ca4520c94520ca456a21cb4520ca4520cb456a21cc4520cb4520" + "cc456a21cd4520cc4520cd456a21ce4520cd4520ce456a21cf4520ce4520cf456a21d04520cf4520d0456a21d14520" + "d04520d1456a21d24520d14520d2456a21d34520d24520d3456a21d44520d34520d4456a21d54520d44520d5456a21" + "d64520d54520d6456a21d74520d64520d7456a21d84520d74520d8456a21d94520d84520d9456a21da4520d94520da" + "456a21db4520da4520db456a21dc4520db4520dc456a21dd4520dc4520dd456a21de4520dd4520de456a21df4520de" + "4520df456a21e04520df4520e0456a21e14520e04520e1456a21e24520e14520e2456a21e34520e24520e3456a21e4" + "4520e34520e4456a21e54520e44520e5456a21e64520e54520e6456a21e74520e64520e7456a21e84520e74520e845" + "6a21e94520e84520e9456a21ea4520e94520ea456a21eb4520ea4520eb456a21ec4520eb4520ec456a21ed4520ec45" + "20ed456a21ee4520ed4520ee456a21ef4520ee4520ef456a21f04520ef4520f0456a21f14520f04520f1456a21f245" + "20f14520f2456a21f34520f24520f3456a21f44520f34520f4456a21f54520f44520f5456a21f64520f54520f6456a" + "21f74520f64520f7456a21f84520f74520f8456a21f94520f84520f9456a21fa4520f94520fa456a21fb4520fa4520" + "fb456a21fc4520fb4520fc456a21fd4520fc4520fd456a21fe4520fd4520fe456a21ff4520fe4520ff456a21804620" + "ff452080466a2181462080462081466a2182462081462082466a2183462082462083466a2184462083462084466a21" + "85462084462085466a2186462085462086466a2187462086462087466a2188462087462088466a2189462088462089" + "466a218a46208946208a466a218b46208a46208b466a218c46208b46208c466a218d46208c46208d466a218e46208d" + "46208e466a218f46208e46208f466a219046208f462090466a2191462090462091466a2192462091462092466a2193" + "462092462093466a2194462093462094466a2195462094462095466a2196462095462096466a219746209646209746" + "6a2198462097462098466a2199462098462099466a219a46209946209a466a219b46209a46209b466a219c46209b46" + "209c466a219d46209c46209d466a219e46209d46209e466a219f46209e46209f466a21a046209f4620a0466a21a146" + "20a04620a1466a21a24620a14620a2466a21a34620a24620a3466a21a44620a34620a4466a21a54620a44620a5466a" + "21a64620a54620a6466a21a74620a64620a7466a21a84620a74620a8466a21a94620a84620a9466a21aa4620a94620" + "aa466a21ab4620aa4620ab466a21ac4620ab4620ac466a21ad4620ac4620ad466a21ae4620ad4620ae466a21af4620" + "ae4620af466a21b04620af4620b0466a21b14620b04620b1466a21b24620b14620b2466a21b34620b24620b3466a21" + "b44620b34620b4466a21b54620b44620b5466a21b64620b54620b6466a21b74620b64620b7466a21b84620b74620b8" + "466a21b94620b84620b9466a21ba4620b94620ba466a21bb4620ba4620bb466a21bc4620bb4620bc466a21bd4620bc" + "4620bd466a21be4620bd4620be466a21bf4620be4620bf466a21c04620bf4620c0466a21c14620c04620c1466a21c2" + "4620c14620c2466a21c34620c24620c3466a21c44620c34620c4466a21c54620c44620c5466a21c64620c54620c646" + "6a21c74620c64620c7466a21c84620c74620c8466a21c94620c84620c9466a21ca4620c94620ca466a21cb4620ca46" + "20cb466a21cc4620cb4620cc466a21cd4620cc4620cd466a21ce4620cd4620ce466a21cf4620ce4620cf466a21d046" + "20cf4620d0466a21d14620d04620d1466a21d24620d14620d2466a21d34620d24620d3466a21d44620d34620d4466a" + "21d54620d44620d5466a21d64620d54620d6466a21d74620d64620d7466a21d84620d74620d8466a21d94620d84620" + "d9466a21da4620d94620da466a21db4620da4620db466a21dc4620db4620dc466a21dd4620dc4620dd466a21de4620" + "dd4620de466a21df4620de4620df466a21e04620df4620e0466a21e14620e04620e1466a21e24620e14620e2466a21" + "e34620e24620e3466a21e44620e34620e4466a21e54620e44620e5466a21e64620e54620e6466a21e74620e64620e7" + "466a21e84620e74620e8466a21e94620e84620e9466a21ea4620e94620ea466a21eb4620ea4620eb466a21ec4620eb" + "4620ec466a21ed4620ec4620ed466a21ee4620ed4620ee466a21ef4620ee4620ef466a21f04620ef4620f0466a21f1" + "4620f04620f1466a21f24620f14620f2466a21f34620f24620f3466a21f44620f34620f4466a21f54620f44620f546" + "6a21f64620f54620f6466a21f74620f64620f7466a21f84620f74620f8466a21f94620f84620f9466a21fa4620f946" + "20fa466a21fb4620fa4620fb466a21fc4620fb4620fc466a21fd4620fc4620fd466a21fe4620fd4620fe466a21ff46" + "20fe4620ff466a21804720ff462080476a2181472080472081476a2182472081472082476a2183472082472083476a" + "2184472083472084476a2185472084472085476a2186472085472086476a2187472086472087476a21884720874720" + "88476a2189472088472089476a218a47208947208a476a218b47208a47208b476a218c47208b47208c476a218d4720" + "8c47208d476a218e47208d47208e476a218f47208e47208f476a219047208f472090476a2191472090472091476a21" + "92472091472092476a2193472092472093476a2194472093472094476a2195472094472095476a2196472095472096" + "476a2197472096472097476a2198472097472098476a2199472098472099476a219a47209947209a476a219b47209a" + "47209b476a219c47209b47209c476a219d47209c47209d476a219e47209d47209e476a219f47209e47209f476a21a0" + "47209f4720a0476a21a14720a04720a1476a21a24720a14720a2476a21a34720a24720a3476a21a44720a34720a447" + "6a21a54720a44720a5476a21a64720a54720a6476a21a74720a64720a7476a21a84720a74720a8476a21a94720a847" + "20a9476a21aa4720a94720aa476a21ab4720aa4720ab476a21ac4720ab4720ac476a21ad4720ac4720ad476a21ae47" + "20ad4720ae476a21af4720ae4720af476a21b04720af4720b0476a21b14720b04720b1476a21b24720b14720b2476a" + "21b34720b24720b3476a21b44720b34720b4476a21b54720b44720b5476a21b64720b54720b6476a21b74720b64720" + "b7476a21b84720b74720b8476a21b94720b84720b9476a21ba4720b94720ba476a21bb4720ba4720bb476a21bc4720" + "bb4720bc476a21bd4720bc4720bd476a21be4720bd4720be476a21bf4720be4720bf476a21c04720bf4720c0476a21" + "c14720c04720c1476a21c24720c14720c2476a21c34720c24720c3476a21c44720c34720c4476a21c54720c44720c5" + "476a21c64720c54720c6476a21c74720c64720c7476a21c84720c74720c8476a21c94720c84720c9476a21ca4720c9" + "4720ca476a21cb4720ca4720cb476a21cc4720cb4720cc476a21cd4720cc4720cd476a21ce4720cd4720ce476a21cf" + "4720ce4720cf476a21d04720cf4720d0476a21d14720d04720d1476a21d24720d14720d2476a21d34720d24720d347" + "6a21d44720d34720d4476a21d54720d44720d5476a21d64720d54720d6476a21d74720d64720d7476a21d84720d747" + "20d8476a21d94720d84720d9476a21da4720d94720da476a21db4720da4720db476a21dc4720db4720dc476a21dd47" + "20dc4720dd476a21de4720dd4720de476a21df4720de4720df476a21e04720df4720e0476a21e14720e04720e1476a" + "21e24720e14720e2476a21e34720e24720e3476a21e44720e34720e4476a21e54720e44720e5476a21e64720e54720" + "e6476a21e74720e64720e7476a21e84720e74720e8476a21e94720e84720e9476a21ea4720e94720ea476a21eb4720" + "ea4720eb476a21ec4720eb4720ec476a21ed4720ec4720ed476a21ee4720ed4720ee476a21ef4720ee4720ef476a21" + "f04720ef4720f0476a21f14720f04720f1476a21f24720f14720f2476a21f34720f24720f3476a21f44720f34720f4" + "476a21f54720f44720f5476a21f64720f54720f6476a21f74720f64720f7476a21f84720f74720f8476a21f94720f8" + "4720f9476a21fa4720f94720fa476a21fb4720fa4720fb476a21fc4720fb4720fc476a21fd4720fc4720fd476a21fe" + "4720fd4720fe476a21ff4720fe4720ff476a21804820ff472080486a2181482080482081486a218248208148208248" + "6a2183482082482083486a2184482083482084486a2185482084482085486a2186482085482086486a218748208648" + "2087486a2188482087482088486a2189482088482089486a218a48208948208a486a218b48208a48208b486a218c48" + "208b48208c486a218d48208c48208d486a218e48208d48208e486a218f48208e48208f486a219048208f482090486a" + "2191482090482091486a2192482091482092486a2193482092482093486a2194482093482094486a21954820944820" + "95486a2196482095482096486a2197482096482097486a2198482097482098486a2199482098482099486a219a4820" + "9948209a486a219b48209a48209b486a219c48209b48209c486a219d48209c48209d486a219e48209d48209e486a21" + "9f48209e48209f486a21a048209f4820a0486a21a14820a04820a1486a21a24820a14820a2486a21a34820a24820a3" + "486a21a44820a34820a4486a21a54820a44820a5486a21a64820a54820a6486a21a74820a64820a7486a21a84820a7" + "4820a8486a21a94820a84820a9486a21aa4820a94820aa486a21ab4820aa4820ab486a21ac4820ab4820ac486a21ad" + "4820ac4820ad486a21ae4820ad4820ae486a21af4820ae4820af486a21b04820af4820b0486a21b14820b04820b148" + "6a21b24820b14820b2486a21b34820b24820b3486a21b44820b34820b4486a21b54820b44820b5486a21b64820b548" + "20b6486a21b74820b64820b7486a21b84820b74820b8486a21b94820b84820b9486a21ba4820b94820ba486a21bb48" + "20ba4820bb486a21bc4820bb4820bc486a21bd4820bc4820bd486a21be4820bd4820be486a21bf4820be4820bf486a" + "21c04820bf4820c0486a21c14820c04820c1486a21c24820c14820c2486a21c34820c24820c3486a21c44820c34820" + "c4486a21c54820c44820c5486a21c64820c54820c6486a21c74820c64820c7486a21c84820c74820c8486a21c94820" + "c84820c9486a21ca4820c94820ca486a21cb4820ca4820cb486a21cc4820cb4820cc486a21cd4820cc4820cd486a21" + "ce4820cd4820ce486a21cf4820ce4820cf486a21d04820cf4820d0486a21d14820d04820d1486a21d24820d14820d2" + "486a21d34820d24820d3486a21d44820d34820d4486a21d54820d44820d5486a21d64820d54820d6486a21d74820d6" + "4820d7486a21d84820d74820d8486a21d94820d84820d9486a21da4820d94820da486a21db4820da4820db486a21dc" + "4820db4820dc486a21dd4820dc4820dd486a21de4820dd4820de486a21df4820de4820df486a21e04820df4820e048" + "6a21e14820e04820e1486a21e24820e14820e2486a21e34820e24820e3486a21e44820e34820e4486a21e54820e448" + "20e5486a21e64820e54820e6486a21e74820e64820e7486a21e84820e74820e8486a21e94820e84820e9486a21ea48" + "20e94820ea486a21eb4820ea4820eb486a21ec4820eb4820ec486a21ed4820ec4820ed486a21ee4820ed4820ee486a" + "21ef4820ee4820ef486a21f04820ef4820f0486a21f14820f04820f1486a21f24820f14820f2486a21f34820f24820" + "f3486a21f44820f34820f4486a21f54820f44820f5486a21f64820f54820f6486a21f74820f64820f7486a21f84820" + "f74820f8486a21f94820f84820f9486a21fa4820f94820fa486a21fb4820fa4820fb486a21fc4820fb4820fc486a21" + "fd4820fc4820fd486a21fe4820fd4820fe486a21ff4820fe4820ff486a21804920ff482080496a2181492080492081" + "496a2182492081492082496a2183492082492083496a2184492083492084496a2185492084492085496a2186492085" + "492086496a2187492086492087496a2188492087492088496a2189492088492089496a218a49208949208a496a218b" + "49208a49208b496a218c49208b49208c496a218d49208c49208d496a218e49208d49208e496a218f49208e49208f49" + "6a219049208f492090496a2191492090492091496a2192492091492092496a2193492092492093496a219449209349" + "2094496a2195492094492095496a2196492095492096496a2197492096492097496a2198492097492098496a219949" + "2098492099496a219a49209949209a496a219b49209a49209b496a219c49209b49209c496a219d49209c49209d496a" + "219e49209d49209e496a219f49209e49209f496a21a049209f4920a0496a21a14920a04920a1496a21a24920a14920" + "a2496a21a34920a24920a3496a21a44920a34920a4496a21a54920a44920a5496a21a64920a54920a6496a21a74920" + "a64920a7496a21a84920a74920a8496a21a94920a84920a9496a21aa4920a94920aa496a21ab4920aa4920ab496a21" + "ac4920ab4920ac496a21ad4920ac4920ad496a21ae4920ad4920ae496a21af4920ae4920af496a21b04920af4920b0" + "496a21b14920b04920b1496a21b24920b14920b2496a21b34920b24920b3496a21b44920b34920b4496a21b54920b4" + "4920b5496a21b64920b54920b6496a21b74920b64920b7496a21b84920b74920b8496a21b94920b84920b9496a21ba" + "4920b94920ba496a21bb4920ba4920bb496a21bc4920bb4920bc496a21bd4920bc4920bd496a21be4920bd4920be49" + "6a21bf4920be4920bf496a21c04920bf4920c0496a21c14920c04920c1496a21c24920c14920c2496a21c34920c249" + "20c3496a21c44920c34920c4496a21c54920c44920c5496a21c64920c54920c6496a21c74920c64920c7496a21c849" + "20c74920c8496a21c94920c84920c9496a21ca4920c94920ca496a21cb4920ca4920cb496a21cc4920cb4920cc496a" + "21cd4920cc4920cd496a21ce4920cd4920ce496a21cf4920ce4920cf496a21d04920cf4920d0496a21d14920d04920" + "d1496a21d24920d14920d2496a21d34920d24920d3496a21d44920d34920d4496a21d54920d44920d5496a21d64920" + "d54920d6496a21d74920d64920d7496a21d84920d74920d8496a21d94920d84920d9496a21da4920d94920da496a21" + "db4920da4920db496a21dc4920db4920dc496a21dd4920dc4920dd496a21de4920dd4920de496a21df4920de4920df" + "496a21e04920df4920e0496a21e14920e04920e1496a21e24920e14920e2496a21e34920e24920e3496a21e44920e3" + "4920e4496a21e54920e44920e5496a21e64920e54920e6496a21e74920e64920e7496a21e84920e74920e8496a21e9" + "4920e84920e9496a21ea4920e94920ea496a21eb4920ea4920eb496a21ec4920eb4920ec496a21ed4920ec4920ed49" + "6a21ee4920ed4920ee496a21ef4920ee4920ef496a21f04920ef4920f0496a21f14920f04920f1496a21f24920f149" + "20f2496a21f34920f24920f3496a21f44920f34920f4496a21f54920f44920f5496a21f64920f54920f6496a21f749" + "20f64920f7496a21f84920f74920f8496a21f94920f84920f9496a21fa4920f94920fa496a21fb4920fa4920fb496a" + "21fc4920fb4920fc496a21fd4920fc4920fd496a21fe4920fd4920fe496a21ff4920fe4920ff496a21804a20ff4920" + "804a6a21814a20804a20814a6a21824a20814a20824a6a21834a20824a20834a6a21844a20834a20844a6a21854a20" + "844a20854a6a21864a20854a20864a6a21874a20864a20874a6a21884a20874a20884a6a21894a20884a20894a6a21" + "8a4a20894a208a4a6a218b4a208a4a208b4a6a218c4a208b4a208c4a6a218d4a208c4a208d4a6a218e4a208d4a208e" + "4a6a218f4a208e4a208f4a6a21904a208f4a20904a6a21914a20904a20914a6a21924a20914a20924a6a21934a2092" + "4a20934a6a21944a20934a20944a6a21954a20944a20954a6a21964a20954a20964a6a21974a20964a20974a6a2198" + "4a20974a20984a6a21994a20984a20994a6a219a4a20994a209a4a6a219b4a209a4a209b4a6a219c4a209b4a209c4a" + "6a219d4a209c4a209d4a6a219e4a209d4a209e4a6a219f4a209e4a209f4a6a21a04a209f4a20a04a6a21a14a20a04a" + "20a14a6a21a24a20a14a20a24a6a21a34a20a24a20a34a6a21a44a20a34a20a44a6a21a54a20a44a20a54a6a21a64a" + "20a54a20a64a6a21a74a20a64a20a74a6a21a84a20a74a20a84a6a21a94a20a84a20a94a6a21aa4a20a94a20aa4a6a" + "21ab4a20aa4a20ab4a6a21ac4a20ab4a20ac4a6a21ad4a20ac4a20ad4a6a21ae4a20ad4a20ae4a6a21af4a20ae4a20" + "af4a6a21b04a20af4a20b04a6a21b14a20b04a20b14a6a21b24a20b14a20b24a6a21b34a20b24a20b34a6a21b44a20" + "b34a20b44a6a21b54a20b44a20b54a6a21b64a20b54a20b64a6a21b74a20b64a20b74a6a21b84a20b74a20b84a6a21" + "b94a20b84a20b94a6a21ba4a20b94a20ba4a6a21bb4a20ba4a20bb4a6a21bc4a20bb4a20bc4a6a21bd4a20bc4a20bd" + "4a6a21be4a20bd4a20be4a6a21bf4a20be4a20bf4a6a21c04a20bf4a20c04a6a21c14a20c04a20c14a6a21c24a20c1" + "4a20c24a6a21c34a20c24a20c34a6a21c44a20c34a20c44a6a21c54a20c44a20c54a6a21c64a20c54a20c64a6a21c7" + "4a20c64a20c74a6a21c84a20c74a20c84a6a21c94a20c84a20c94a6a21ca4a20c94a20ca4a6a21cb4a20ca4a20cb4a" + "6a21cc4a20cb4a20cc4a6a21cd4a20cc4a20cd4a6a21ce4a20cd4a20ce4a6a21cf4a20ce4a20cf4a6a21d04a20cf4a" + "20d04a6a21d14a20d04a20d14a6a21d24a20d14a20d24a6a21d34a20d24a20d34a6a21d44a20d34a20d44a6a21d54a" + "20d44a20d54a6a21d64a20d54a20d64a6a21d74a20d64a20d74a6a21d84a20d74a20d84a6a21d94a20d84a20d94a6a" + "21da4a20d94a20da4a6a21db4a20da4a20db4a6a21dc4a20db4a20dc4a6a21dd4a20dc4a20dd4a6a21de4a20dd4a20" + "de4a6a21df4a20de4a20df4a6a21e04a20df4a20e04a6a21e14a20e04a20e14a6a21e24a20e14a20e24a6a21e34a20" + "e24a20e34a6a21e44a20e34a20e44a6a21e54a20e44a20e54a6a21e64a20e54a20e64a6a21e74a20e64a20e74a6a21" + "e84a20e74a20e84a6a21e94a20e84a20e94a6a21ea4a20e94a20ea4a6a21eb4a20ea4a20eb4a6a21ec4a20eb4a20ec" + "4a6a21ed4a20ec4a20ed4a6a21ee4a20ed4a20ee4a6a21ef4a20ee4a20ef4a6a21f04a20ef4a20f04a6a21f14a20f0" + "4a20f14a6a21f24a20f14a20f24a6a21f34a20f24a20f34a6a21f44a20f34a20f44a6a21f54a20f44a20f54a6a21f6" + "4a20f54a20f64a6a21f74a20f64a20f74a6a21f84a20f74a20f84a6a21f94a20f84a20f94a6a21fa4a20f94a20fa4a" + "6a21fb4a20fa4a20fb4a6a21fc4a20fb4a20fc4a6a21fd4a20fc4a20fd4a6a21fe4a20fd4a20fe4a6a21ff4a20fe4a" + "20ff4a6a21804b20ff4a20804b6a21814b20804b20814b6a21824b20814b20824b6a21834b20824b20834b6a21844b" + "20834b20844b6a21854b20844b20854b6a21864b20854b20864b6a21874b20864b20874b6a21884b20874b20884b6a" + "21894b20884b20894b6a218a4b20894b208a4b6a218b4b208a4b208b4b6a218c4b208b4b208c4b6a218d4b208c4b20" + "8d4b6a218e4b208d4b208e4b6a218f4b208e4b208f4b6a21904b208f4b20904b6a21914b20904b20914b6a21924b20" + "914b20924b6a21934b20924b20934b6a21944b20934b20944b6a21954b20944b20954b6a21964b20954b20964b6a21" + "974b20964b20974b6a21984b20974b20984b6a21994b20984b20994b6a219a4b20994b209a4b6a219b4b209a4b209b" + "4b6a219c4b209b4b209c4b6a219d4b209c4b209d4b6a219e4b209d4b209e4b6a219f4b209e4b209f4b6a21a04b209f" + "4b20a04b6a21a14b20a04b20a14b6a21a24b20a14b20a24b6a21a34b20a24b20a34b6a21a44b20a34b20a44b6a21a5" + "4b20a44b20a54b6a21a64b20a54b20a64b6a21a74b20a64b20a74b6a21a84b20a74b20a84b6a21a94b20a84b20a94b" + "6a21aa4b20a94b20aa4b6a21ab4b20aa4b20ab4b6a21ac4b20ab4b20ac4b6a21ad4b20ac4b20ad4b6a21ae4b20ad4b" + "20ae4b6a21af4b20ae4b20af4b6a21b04b20af4b20b04b6a21b14b20b04b20b14b6a21b24b20b14b20b24b6a21b34b" + "20b24b20b34b6a21b44b20b34b20b44b6a21b54b20b44b20b54b6a21b64b20b54b20b64b6a21b74b20b64b20b74b6a" + "21b84b20b74b20b84b6a21b94b20b84b20b94b6a21ba4b20b94b20ba4b6a21bb4b20ba4b20bb4b6a21bc4b20bb4b20" + "bc4b6a21bd4b20bc4b20bd4b6a21be4b20bd4b20be4b6a21bf4b20be4b20bf4b6a21c04b20bf4b20c04b6a21c14b20" + "c04b20c14b6a21c24b20c14b20c24b6a21c34b20c24b20c34b6a21c44b20c34b20c44b6a21c54b20c44b20c54b6a21" + "c64b20c54b20c64b6a21c74b20c64b20c74b6a21c84b20c74b20c84b6a21c94b20c84b20c94b6a21ca4b20c94b20ca" + "4b6a21cb4b20ca4b20cb4b6a21cc4b20cb4b20cc4b6a21cd4b20cc4b20cd4b6a21ce4b20cd4b20ce4b6a21cf4b20ce" + "4b20cf4b6a21d04b20cf4b20d04b6a21d14b20d04b20d14b6a21d24b20d14b20d24b6a21d34b20d24b20d34b6a21d4" + "4b20d34b20d44b6a21d54b20d44b20d54b6a21d64b20d54b20d64b6a21d74b20d64b20d74b6a21d84b20d74b20d84b" + "6a21d94b20d84b20d94b6a21da4b20d94b20da4b6a21db4b20da4b20db4b6a21dc4b20db4b20dc4b6a21dd4b20dc4b" + "20dd4b6a21de4b20dd4b20de4b6a21df4b20de4b20df4b6a21e04b20df4b20e04b6a21e14b20e04b20e14b6a21e24b" + "20e14b20e24b6a21e34b20e24b20e34b6a21e44b20e34b20e44b6a21e54b20e44b20e54b6a21e64b20e54b20e64b6a" + "21e74b20e64b20e74b6a21e84b20e74b20e84b6a21e94b20e84b20e94b6a21ea4b20e94b20ea4b6a21eb4b20ea4b20" + "eb4b6a21ec4b20eb4b20ec4b6a21ed4b20ec4b20ed4b6a21ee4b20ed4b20ee4b6a21ef4b20ee4b20ef4b6a21f04b20" + "ef4b20f04b6a21f14b20f04b20f14b6a21f24b20f14b20f24b6a21f34b20f24b20f34b6a21f44b20f34b20f44b6a21" + "f54b20f44b20f54b6a21f64b20f54b20f64b6a21f74b20f64b20f74b6a21f84b20f74b20f84b6a21f94b20f84b20f9" + "4b6a21fa4b20f94b20fa4b6a21fb4b20fa4b20fb4b6a21fc4b20fb4b20fc4b6a21fd4b20fc4b20fd4b6a21fe4b20fd" + "4b20fe4b6a21ff4b20fe4b20ff4b6a21804c20ff4b20804c6a21814c20804c20814c6a21824c20814c20824c6a2183" + "4c20824c20834c6a21844c20834c20844c6a21854c20844c20854c6a21864c20854c20864c6a21874c20864c20874c" + "6a21884c20874c20884c6a21894c20884c20894c6a218a4c20894c208a4c6a218b4c208a4c208b4c6a218c4c208b4c" + "208c4c6a218d4c208c4c208d4c6a218e4c208d4c208e4c6a218f4c208e4c208f4c6a21904c208f4c20904c6a21914c" + "20904c20914c6a21924c20914c20924c6a21934c20924c20934c6a21944c20934c20944c6a21954c20944c20954c6a" + "21964c20954c20964c6a21974c20964c20974c6a21984c20974c20984c6a21994c20984c20994c6a219a4c20994c20" + "9a4c6a219b4c209a4c209b4c6a219c4c209b4c209c4c6a219d4c209c4c209d4c6a219e4c209d4c209e4c6a219f4c20" + "9e4c209f4c6a21a04c209f4c20a04c6a21a14c20a04c20a14c6a21a24c20a14c20a24c6a21a34c20a24c20a34c6a21" + "a44c20a34c20a44c6a21a54c20a44c20a54c6a21a64c20a54c20a64c6a21a74c20a64c20a74c6a21a84c20a74c20a8" + "4c6a21a94c20a84c20a94c6a21aa4c20a94c20aa4c6a21ab4c20aa4c20ab4c6a21ac4c20ab4c20ac4c6a21ad4c20ac" + "4c20ad4c6a21ae4c20ad4c20ae4c6a21af4c20ae4c20af4c6a21b04c20af4c20b04c6a21b14c20b04c20b14c6a21b2" + "4c20b14c20b24c6a21b34c20b24c20b34c6a21b44c20b34c20b44c6a21b54c20b44c20b54c6a21b64c20b54c20b64c" + "6a21b74c20b64c20b74c6a21b84c20b74c20b84c6a21b94c20b84c20b94c6a21ba4c20b94c20ba4c6a21bb4c20ba4c" + "20bb4c6a21bc4c20bb4c20bc4c6a21bd4c20bc4c20bd4c6a21be4c20bd4c20be4c6a21bf4c20be4c20bf4c6a21c04c" + "20bf4c20c04c6a21c14c20c04c20c14c6a21c24c20c14c20c24c6a21c34c20c24c20c34c6a21c44c20c34c20c44c6a" + "21c54c20c44c20c54c6a21c64c20c54c20c64c6a21c74c20c64c20c74c6a21c84c20c74c20c84c6a21c94c20c84c20" + "c94c6a21ca4c20c94c20ca4c6a21cb4c20ca4c20cb4c6a21cc4c20cb4c20cc4c6a21cd4c20cc4c20cd4c6a21ce4c20" + "cd4c20ce4c6a21cf4c20ce4c20cf4c6a21d04c20cf4c20d04c6a21d14c20d04c20d14c6a21d24c20d14c20d24c6a21" + "d34c20d24c20d34c6a21d44c20d34c20d44c6a21d54c20d44c20d54c6a21d64c20d54c20d64c6a21d74c20d64c20d7" + "4c6a21d84c20d74c20d84c6a21d94c20d84c20d94c6a21da4c20d94c20da4c6a21db4c20da4c20db4c6a21dc4c20db" + "4c20dc4c6a21dd4c20dc4c20dd4c6a21de4c20dd4c20de4c6a21df4c20de4c20df4c6a21e04c20df4c20e04c6a21e1" + "4c20e04c20e14c6a21e24c20e14c20e24c6a21e34c20e24c20e34c6a21e44c20e34c20e44c6a21e54c20e44c20e54c" + "6a21e64c20e54c20e64c6a21e74c20e64c20e74c6a21e84c20e74c20e84c6a21e94c20e84c20e94c6a21ea4c20e94c" + "20ea4c6a21eb4c20ea4c20eb4c6a21ec4c20eb4c20ec4c6a21ed4c20ec4c20ed4c6a21ee4c20ed4c20ee4c6a21ef4c" + "20ee4c20ef4c6a21f04c20ef4c20f04c6a21f14c20f04c20f14c6a21f24c20f14c20f24c6a21f34c20f24c20f34c6a" + "21f44c20f34c20f44c6a21f54c20f44c20f54c6a21f64c20f54c20f64c6a21f74c20f64c20f74c6a21f84c20f74c20" + "f84c6a21f94c20f84c20f94c6a21fa4c20f94c20fa4c6a21fb4c20fa4c20fb4c6a21fc4c20fb4c20fc4c6a21fd4c20" + "fc4c20fd4c6a21fe4c20fd4c20fe4c6a21ff4c20fe4c20ff4c6a21804d20ff4c20804d6a21814d20804d20814d6a21" + "824d20814d20824d6a21834d20824d20834d6a21844d20834d20844d6a21854d20844d20854d6a21864d20854d2086" + "4d6a21874d20864d20874d6a21884d20874d20884d6a21894d20884d20894d6a218a4d20894d208a4d6a218b4d208a" + "4d208b4d6a218c4d208b4d208c4d6a218d4d208c4d208d4d6a218e4d208d4d208e4d6a218f4d208e4d208f4d6a2190" + "4d208f4d20904d6a21914d20904d20914d6a21924d20914d20924d6a21934d20924d20934d6a21944d20934d20944d" + "6a21954d20944d20954d6a21964d20954d20964d6a21974d20964d20974d6a21984d20974d20984d6a21994d20984d" + "20994d6a219a4d20994d209a4d6a219b4d209a4d209b4d6a219c4d209b4d209c4d6a219d4d209c4d209d4d6a219e4d" + "209d4d209e4d6a219f4d209e4d209f4d6a21a04d209f4d20a04d6a21a14d20a04d20a14d6a21a24d20a14d20a24d6a" + "21a34d20a24d20a34d6a21a44d20a34d20a44d6a21a54d20a44d20a54d6a21a64d20a54d20a64d6a21a74d20a64d20" + "a74d6a21a84d20a74d20a84d6a21a94d20a84d20a94d6a21aa4d20a94d20aa4d6a21ab4d20aa4d20ab4d6a21ac4d20" + "ab4d20ac4d6a21ad4d20ac4d20ad4d6a21ae4d20ad4d20ae4d6a21af4d20ae4d20af4d6a21b04d20af4d20b04d6a21" + "b14d20b04d20b14d6a21b24d20b14d20b24d6a21b34d20b24d20b34d6a21b44d20b34d20b44d6a21b54d20b44d20b5" + "4d6a21b64d20b54d20b64d6a21b74d20b64d20b74d6a21b84d20b74d20b84d6a21b94d20b84d20b94d6a21ba4d20b9" + "4d20ba4d6a21bb4d20ba4d20bb4d6a21bc4d20bb4d20bc4d6a21bd4d20bc4d20bd4d6a21be4d20bd4d20be4d6a21bf" + "4d20be4d20bf4d6a21c04d20bf4d20c04d6a21c14d20c04d20c14d6a21c24d20c14d20c24d6a21c34d20c24d20c34d" + "6a21c44d20c34d20c44d6a21c54d20c44d20c54d6a21c64d20c54d20c64d6a21c74d20c64d20c74d6a21c84d20c74d" + "20c84d6a21c94d20c84d20c94d6a21ca4d20c94d20ca4d6a21cb4d20ca4d20cb4d6a21cc4d20cb4d20cc4d6a21cd4d" + "20cc4d20cd4d6a21ce4d20cd4d20ce4d6a21cf4d20ce4d20cf4d6a21d04d20cf4d20d04d6a21d14d20d04d20d14d6a" + "21d24d20d14d20d24d6a21d34d20d24d20d34d6a21d44d20d34d20d44d6a21d54d20d44d20d54d6a21d64d20d54d20" + "d64d6a21d74d20d64d20d74d6a21d84d20d74d20d84d6a21d94d20d84d20d94d6a21da4d20d94d20da4d6a21db4d20" + "da4d20db4d6a21dc4d20db4d20dc4d6a21dd4d20dc4d20dd4d6a21de4d20dd4d20de4d6a21df4d20de4d20df4d6a21" + "e04d20df4d20e04d6a21e14d20e04d20e14d6a21e24d20e14d20e24d6a21e34d20e24d20e34d6a21e44d20e34d20e4" + "4d6a21e54d20e44d20e54d6a21e64d20e54d20e64d6a21e74d20e64d20e74d6a21e84d20e74d20e84d6a21e94d20e8" + "4d20e94d6a21ea4d20e94d20ea4d6a21eb4d20ea4d20eb4d6a21ec4d20eb4d20ec4d6a21ed4d20ec4d20ed4d6a21ee" + "4d20ed4d20ee4d6a21ef4d20ee4d20ef4d6a21f04d20ef4d20f04d6a21f14d20f04d20f14d6a21f24d20f14d20f24d" + "6a21f34d20f24d20f34d6a21f44d20f34d20f44d6a21f54d20f44d20f54d6a21f64d20f54d20f64d6a21f74d20f64d" + "20f74d6a21f84d20f74d20f84d6a21f94d20f84d20f94d6a21fa4d20f94d20fa4d6a21fb4d20fa4d20fb4d6a21fc4d" + "20fb4d20fc4d6a21fd4d20fc4d20fd4d6a21fe4d20fd4d20fe4d6a21ff4d20fe4d20ff4d6a21804e20ff4d20804e6a" + "21814e20804e20814e6a21824e20814e20824e6a21834e20824e20834e6a21844e20834e20844e6a21854e20844e20" + "854e6a21864e20854e20864e6a21874e20864e20874e6a21884e20874e20884e6a21894e20884e20894e6a218a4e20" + "894e208a4e6a218b4e208a4e208b4e6a218c4e208b4e208c4e6a218d4e208c4e208d4e6a218e4e208d4e208e4e6a21" + "8f4e208f4e0b"; diff --git a/src/test/app/wasm_fixtures/fixtures.cpp b/src/test/app/wasm_fixtures/fixtures.cpp new file mode 100644 index 00000000000..ab86194ce78 --- /dev/null +++ b/src/test/app/wasm_fixtures/fixtures.cpp @@ -0,0 +1,1451 @@ +// TODO: consider moving these to separate files (and figure out the build) + +#include + +#include + +namespace wasm_constants { + +namespace { + +// Helper: Variable-length integer encoding (LEB128) +void +pushLeb128(std::vector& buf, uint32_t val) +{ + do + { + uint8_t byte = val & 0x7F; + val >>= 7; + if (val != 0) + byte |= 0x80; + buf.push_back(byte); + } while (val != 0); +} + +// Helper: append bytes from a C-style array to a vector +template +void +appendBytes(std::vector& buf, uint8_t const (&arr)[N]) +{ + buf.insert(buf.end(), &arr[0], &arr[N]); +} + +// Helper: append bytes from a vector to a vector +void +appendBytes(std::vector& buf, std::vector const& src) +{ + buf.insert(buf.end(), src.begin(), src.end()); +} + +// Helper: append a WASM section (ID + LEB128 size + content) +// extraSize is added to the encoded size (for trailing fill bytes) +void +appendSection( + std::vector& wasm, + uint8_t sectionId, + std::vector const& content, + uint32_t extraSize = 0) +{ + wasm.push_back(sectionId); + pushLeb128(wasm, static_cast(content.size() + extraSize)); + appendBytes(wasm, content); +} + +} // namespace + +std::vector +generateCodeBlob(uint32_t num_instructions) +{ + std::vector wasm; + wasm.reserve( + sizeof(WASM_HEADER) + sizeof(TYPE_EMPTY_FUNC) + sizeof(FUNC_TYPE0) + sizeof(EXPORT_FINISH)); + appendBytes(wasm, WASM_HEADER); + appendBytes(wasm, TYPE_EMPTY_FUNC); + appendBytes(wasm, FUNC_TYPE0); + appendBytes(wasm, EXPORT_FINISH); + + std::vector body; + pushLeb128(body, 0); // No locals + body.insert(body.end(), num_instructions, INSTR_NOP); + body.push_back(INSTR_END); + + std::vector section; + pushLeb128(section, 1); // 1 function + pushLeb128(section, static_cast(body.size())); + appendBytes(section, body); + + appendSection(wasm, SECTION_CODE, section); + return wasm; +} + +std::vector +generateDataBlob(uint32_t data_size) +{ + std::vector wasm; + wasm.reserve(sizeof(WASM_HEADER) + sizeof(TYPE_EMPTY_FUNC) + sizeof(FUNC_TYPE0)); + appendBytes(wasm, WASM_HEADER); + appendBytes(wasm, TYPE_EMPTY_FUNC); + appendBytes(wasm, FUNC_TYPE0); + + // Memory Section: must be large enough for data_size + uint32_t const pages = (data_size + 65535) / 65536; + std::vector mem_p; + pushLeb128(mem_p, 1); // 1 memory defined + mem_p.push_back(0x00); // Flags (minimum only) + pushLeb128(mem_p, pages); // Page count + appendSection(wasm, SECTION_MEMORY, mem_p); + + appendBytes(wasm, EXPORT_FINISH); + + // Code Section: MUST come before Data Section per WASM spec + std::vector code_p; + pushLeb128(code_p, 1); // 1 function body + pushLeb128(code_p, static_cast(std::size(EMPTY_BODY))); + appendBytes(code_p, EMPTY_BODY); + appendSection(wasm, SECTION_CODE, code_p); + + // Data Section: the actual bloat + std::vector data_seg; + data_seg.push_back(0x00); // Memory index 0 + appendBytes(data_seg, DATA_OFFSET_ZERO); + pushLeb128(data_seg, data_size); + + std::vector data_p; + pushLeb128(data_p, 1); // 1 data segment + appendBytes(data_p, data_seg); + + appendSection(wasm, SECTION_DATA, data_p, data_size); + wasm.insert(wasm.end(), data_size, DATA_FILL_BYTE); + + return wasm; +} + +} // namespace wasm_constants + +extern std::string const fibWasmHex = + "0061736d0100000001090260000060017f017f0303020001071b02115f5f7761736d5f63616c6c5f63746f72730000" + "0366696200010a440202000b3f01017f200045044041000f0b2000410348044041010f0b200041026a210003402000" + "41036b100120016a2101200041026b220041044a0d000b200141016a0b"; + +extern std::string const ledgerSqnWasmHex = + "0061736d01000000010e0360027f7f017f6000006000017f02160103656e760e6765745f6c65646765725f73716e00" + "0003030201020503010002063f0a7f01418088040b7f004180080b7f004180080b7f004180080b7f00418088040b7f" + "004180080b7f00418088040b7f00418080080b7f0041000b7f0041010b07aa010c066d656d6f72790200115f5f7761" + "736d5f63616c6c5f63746f727300010666696e69736800020c5f5f64736f5f68616e646c6503010a5f5f646174615f" + "656e6403020b5f5f737461636b5f6c6f7703030c5f5f737461636b5f6869676803040d5f5f676c6f62616c5f626173" + "6503050b5f5f686561705f6261736503060a5f5f686561705f656e6403070d5f5f6d656d6f72795f6261736503080c" + "5f5f7461626c655f6261736503090a3d0202000b3801037f230041106b220024002000410c6a410410002101200028" + "020c2102200041106a2400200141054100200241054f1b20014100481b0b007f0970726f647563657273010c70726f" + "6365737365642d62790105636c616e675f31392e312e352d776173692d73646b202868747470733a2f2f6769746875" + "622e636f6d2f6c6c766d2f6c6c766d2d70726f6a656374206162346235613264623538323935386166316565333038" + "61373930636664623432626432343732302900490f7461726765745f6665617475726573042b0f6d757461626c652d" + "676c6f62616c732b087369676e2d6578742b0f7265666572656e63652d74797065732b0a6d756c746976616c7565"; + +extern std::string const allHostFunctionsWasmHex = + "0061736d0100000001540c60027f7f017f60037f7f7f017f60047f7f7f7f017f60017f017f60067f7f7f7f7f7f017f" + "60037f7f7f0060057f7f7f7f7f017f60037f7f7e017f60087f7f7f7f7f7f7f7f017f60017f0060027f7f006000017f" + "02ae061a08686f73745f6c69620c6765745f74785f6669656c64000108686f73745f6c69620974726163655f6e756d" + "000708686f73745f6c6962057472616365000608686f73745f6c69620e6765745f6c65646765725f73716e00000868" + "6f73745f6c6962166765745f706172656e745f6c65646765725f74696d65000008686f73745f6c6962166765745f70" + "6172656e745f6c65646765725f68617368000008686f73745f6c6962136765745f74785f6e65737465645f6669656c" + "64000208686f73745f6c6962106765745f74785f61727261795f6c656e000308686f73745f6c6962176765745f7478" + "5f6e65737465645f61727261795f6c656e000008686f73745f6c69621c6765745f63757272656e745f6c6564676572" + "5f6f626a5f6669656c64000108686f73745f6c6962236765745f63757272656e745f6c65646765725f6f626a5f6e65" + "737465645f6669656c64000208686f73745f6c6962206765745f63757272656e745f6c65646765725f6f626a5f6172" + "7261795f6c656e000308686f73745f6c6962276765745f63757272656e745f6c65646765725f6f626a5f6e65737465" + "645f61727261795f6c656e000008686f73745f6c69621063616368655f6c65646765725f6f626a000108686f73745f" + "6c69621163726564656e7469616c5f6b65796c6574000808686f73745f6c69620d657363726f775f6b65796c657400" + "0408686f73745f6c69620d6f7261636c655f6b65796c6574000408686f73745f6c696213636f6d707574655f736861" + "3531325f68616c66000208686f73745f6c6962076765745f6e6674000408686f73745f6c69620b7570646174655f64" + "617461000008686f73745f6c6962146765745f6c65646765725f6f626a5f6669656c64000208686f73745f6c69621b" + "6765745f6c65646765725f6f626a5f6e65737465645f6669656c64000608686f73745f6c6962186765745f6c656467" + "65725f6f626a5f61727261795f6c656e000008686f73745f6c69621f6765745f6c65646765725f6f626a5f6e657374" + "65645f61727261795f6c656e000108686f73745f6c69620e6163636f756e745f6b65796c6574000208686f73745f6c" + "69620d74726163655f6163636f756e740002030c0b090a05050b05000101030005030100110619037f01418080c000" + "0b7f0041af99c0000b7f0041b099c0000b072e04066d656d6f727902000666696e697368001e0a5f5f646174615f65" + "6e6403010b5f5f686561705f6261736503020ac61d0b990101027f230041306b220124002000027f41818020200141" + "1c6a4114100022024114470440417f20022002417f4e1b210241010c010b200020012f001c3b0001200041036a2001" + "411e6a2d00003a0000200120012900233703082001200141286a29000037000d200128001f21022000410d6a200129" + "000d3700002000200129030837020841000b3a000020002002360204200141306a24000b460020012d000041014604" + "40418080c000410b20013402041001000b20002001290001370000200041106a200141116a28000036000020004108" + "6a200141096a2900003700000b1900200241094f0440000b20002002360204200020013602000b1900200241214f04" + "40000b20002002360204200020013602000bde1a01097f230041b0036b22002400418b80c000411b41014100410010" + "021a41a680c000411941014100410010021a41e780c000412b41014100410010021a20004100360270024002400240" + "02400240024002400240200041f0006a220741041003220141004a0440419281c00041172000280270220141187420" + "014180fe03714108747220014108764180fe037120014118767272ad10011a200041003602900120004190016a2203" + "41041004220141004c0d0141a981c0004113200028029001220141187420014180fe03714108747220014108764180" + "fe037120014118767272ad10011a200041c8016a22024200370300200041c0016a22054200370300200041b8016a22" + "044200370300200042003703b001200041b0016a22064120100522014120470d0241bc81c000411320064120410110" + "021a41cf81c000412041014100410010021a41dc82c000412e41014100410010021a200041a0016a41003602002000" + "4198016a420037030020004200370390014181802020034114100022014114470d03418a83c00041142003101f2000" + "42003703704188801820074108100022014108470d04419e83c0004117420810011a41b583c0004128200741084101" + "10021a2000410036024841848008200041c8006a22034104100022014104470d0541dd83c000411520034104410110" + "021a200041013b0034200242003703002005420037030020044200370300200042003703b0010240200041346a4102" + "200641201006220141004e044041f283c00041142001ad10011a200041286a20062001101d418684c000410d200028" + "0228200028022c410110021a0c010b419384c00041292001ac10011a0b41bc84c00041154183803c1007ac10011a41" + "d184c00041134189803c1007ac10011a0240200041346a41021008220141004e044041e484c00041142001ad10011a" + "0c010b41f884c000412d2001ac10011a0b41a585c000412341014100410010021a41de86c000413341014100410010" + "021a2000420037037041828018200041f0006a220141081009220341004c0d0620034108460440419187c000412b42" + "0810011a41bc87c000412f20014108410110021a0c080b41eb87c000412f2003ad10011a200041206a200041f0006a" + "2003101c419a88c000411720002802202000280224410110021a0c070b41bf82c000411d2001ac10011a419b7f2102" + "0c070b419a82c00041252001ac10011a419a7f21020c060b41ef81c000412b2001ac10011a41997f21020c050b41b4" + "86c000412a2001ac10011a41b77e21020c040b41f385c00041c1002001ac10011a41b67e21020c030b41c885c00041" + "2b2001ac10011a41b57e21020c020b41b188c00041c5002003ac10011a0b200041a0016a410036020020004198016a" + "4200370300200042003703900102404181802020004190016a220341141009220141004a044041f688c000411e2003" + "101f0c010b419489c00041332001ac10011a0b200041013b0048200041c8016a4200370300200041c0016a42003703" + "00200041b8016a4200370300200042003703b0010240200041c8006a4102200041b0016a22014120100a220341004e" + "044041c789c000411c2003ad10011a200041186a20012003101d41e389c00041152000280218200028021c41011002" + "1a0c010b41f889c00041392003ac10011a0b41b18ac00041244183803c100bac10011a0240200041c8006a4102100c" + "220141004e044041d58ac000411c2001ad10011a0c010b41f18ac000413d2001ac10011a0b41ae8bc0004128410141" + "00410010021a41d68bc000412f41014100410010021a200041b0016a2203101a200041f0006a22012003101b200041" + "a8016a4200370300200041a0016a420037030020004198016a42003703002000420037039001024002400240024002" + "40200120004190016a2203102022014120460440200341204100100d220441004a044041858cc00041232004ad1001" + "1a200042003703482004200041c8006a220141081021220341004c0d022003410846044041a88cc000412a42081001" + "1a41d28cc000412e20014108410110021a0c060b41808dc000412e2003ad10011a200041106a200041c8006a200310" + "1c41ae8dc000411620002802102000280214410110021a0c050b41e68fc000413c2004ac10011a200041c8016a4200" + "370300200041c0016a4200370300200041b8016a4200370300200042003703b0014101200041b0016a412010212201" + "4100480d020c030b41ba92c000412e2001ac10011a41ef7c21020c050b41c48dc000412b2003ac10011a0c020b41a2" + "90c00041c1002001ac10011a0b200041013b00484101200041c8006a200041b0016a10222201410048044041e390c0" + "0041352001ac10011a0b4101102322014100480440419891c00041322001ac10011a0b4101200041c8006a10242201" + "410048044041ca91c00041392001ac10011a0b418392c000413741014100410010021a0c010b200041013b00342000" + "41c8016a4200370300200041c0016a4200370300200041b8016a4200370300200042003703b0010240200420004134" + "6a200041b0016a22011022220341004e044041ef8dc000411b2003ad10011a200041086a20012003101d418a8ec000" + "41142000280208200028020c410110021a0c010b419e8ec00041312003ac10011a0b41cf8ec000412320041023ac10" + "011a02402004200041346a1024220141004e044041f28ec000411b2001ad10011a0c010b418d8fc00041352001ac10" + "011a0b41c28fc000412441014100410010021a0b41e892c000412f41014100410010021a200041b0016a2201101a20" + "0041346a22042001101b200041e0006a4200370300200041d8006a4200370300200041d0006a420037030020004200" + "370348024002400240024002402004200041c8006a2203102022014120460440419793c000410f2003412041011002" + "1a20004188016a420037030020004180016a4200370300200041f8006a420037030020004200370370024020044114" + "2004411441a693c0004109200041f0006a22014120100e220341004a0440200020012003101d41ae93c00041122000" + "2802002000280204410110021a0c010b41c093c000413c2003ac10011a0b200041a8016a22064200370300200041a0" + "016a2202420037030020004198016a22054200370300200042003703900120004180808cc07e360268200041346a22" + "034114200041e8006a410420004190016a22084120100f22014120470d0141fc93c000410e20084120410110021a20" + "0041c8016a4200370300200041c0016a4200370300200041b8016a4200370300200042003703b001200041808080d0" + "0236026c20034114200041ec006a4104200041b0016a22044120101022014120470d02418a94c000410e2004412041" + "0110021a419894c000412441014100410010021a419195c000412541014100410010021a20004188016a4200370300" + "20004180016a4200370300200041f8006a42003703002000420037037041b695c0004117200041f0006a2203412010" + "1122014120470d0341cd95c000410b41b695c0004117410110021a41d895c000411120034120410110021a2004101a" + "200041c8006a22072004101b2006420037030020024200370300200542003703002000420037039001024041002004" + "22026b410371220320026a220520024d0d0020030440200321010340200241003a0000200241016a2102200141016b" + "22010d000b0b200341016b4107490d000340200241003a0000200241076a41003a0000200241066a41003a00002002" + "41056a41003a0000200241046a41003a0000200241036a41003a0000200241026a41003a0000200241016a41003a00" + "00200241086a22022005470d000b0b200541800220036b2201417c716a220220054b04400340200541003602002005" + "41046a22052002490d000b0b024020022001410371220120026a22034f0d002001220504400340200241003a000020" + "0241016a2102200541016b22050d000b0b200141016b4107490d000340200241003a0000200241076a41003a000020" + "0241066a41003a0000200241056a41003a0000200241046a41003a0000200241036a41003a0000200241026a41003a" + "0000200241016a41003a0000200241086a22022003470d000b0b024020074114200841202004418002101222014100" + "4a044041e995c00041102001ad10011a20014181024f0d0641f995c000410920042001410110021a0c010b418296c0" + "00412e2001ac10011a0b41b096c000411241c296c00041074101100222014100480d0541c996c000411d2001ad1001" + "1a41e696c0004111422a1001410048044041ad97c000411a42a47b10011a41a47b21020c070b41f796c000411c4200" + "10011a41012102419397c000411a41014100410010021a41ff97c000412941014100410010021a41a898c000412810" + "132201412846044041d098c000412741a898c0004128410110021a41f798c000411e41014100410010021a41bf80c0" + "00412841014100410010021a0c070b419599c000411a2001ac10011a41c37a21020c060b41f494c000411d2001ac10" + "011a418b7c21020c050b41d894c000411c2001ac10011a41897c21020c040b41bc94c000411c2001ac10011a41887c" + "21020c030b41dd97c00041222001ac10011a41a77b21020c020b000b41c797c00041162001ac10011a41a57b21020b" + "200041b0036a240020020b0d00200020012002411410191a0b0c00200041142001412010180b0e0020004182801820" + "01200210140b0e002000200141022002412010150b0a0020004183803c10160b0a0020002001410210170b0bb91901" + "00418080c0000baf196572726f725f636f64653d3d3d3d20484f53542046554e4354494f4e532054455354203d3d3d" + "54657374696e6720323620686f73742066756e6374696f6e73535543434553533a20416c6c20686f73742066756e63" + "74696f6e20746573747320706173736564212d2d2d2043617465676f727920313a204c656467657220486561646572" + "2046756e6374696f6e73202d2d2d4c65646765722073657175656e6365206e756d6265723a506172656e74206c6564" + "6765722074696d653a506172656e74206c656467657220686173683a535543434553533a204c656467657220686561" + "6465722066756e6374696f6e734552524f523a206765745f706172656e745f6c65646765725f686173682077726f6e" + "67206c656e6774683a4552524f523a206765745f706172656e745f6c65646765725f74696d65206661696c65643a45" + "52524f523a206765745f6c65646765725f73716e206661696c65643a2d2d2d2043617465676f727920323a20547261" + "6e73616374696f6e20446174612046756e6374696f6e73202d2d2d5472616e73616374696f6e204163636f756e743a" + "5472616e73616374696f6e20466565206c656e6774683a5472616e73616374696f6e20466565202873657269616c69" + "7a65642058525020616d6f756e74293a5472616e73616374696f6e2053657175656e63653a4e657374656420666965" + "6c64206c656e6774683a4e6573746564206669656c643a494e464f3a206765745f74785f6e65737465645f6669656c" + "64206e6f74206170706c696361626c653a5369676e657273206172726179206c656e6774683a4d656d6f7320617272" + "6179206c656e6774683a4e6573746564206172726179206c656e6774683a494e464f3a206765745f74785f6e657374" + "65645f61727261795f6c656e206e6f74206170706c696361626c653a535543434553533a205472616e73616374696f" + "6e20646174612066756e6374696f6e734552524f523a206765745f74785f6669656c642853657175656e6365292077" + "726f6e67206c656e6774683a4552524f523a206765745f74785f6669656c6428466565292077726f6e67206c656e67" + "746820286578706563746564203820627974657320666f7220585250293a4552524f523a206765745f74785f666965" + "6c64284163636f756e74292077726f6e67206c656e6774683a2d2d2d2043617465676f727920333a2043757272656e" + "74204c6564676572204f626a6563742046756e6374696f6e73202d2d2d43757272656e74206f626a6563742062616c" + "616e6365206c656e677468202858525020616d6f756e74293a43757272656e74206f626a6563742062616c616e6365" + "202873657269616c697a65642058525020616d6f756e74293a43757272656e74206f626a6563742062616c616e6365" + "206c656e67746820286e6f6e2d58525020616d6f756e74293a43757272656e74206f626a6563742062616c616e6365" + "3a494e464f3a206765745f63757272656e745f6c65646765725f6f626a5f6669656c642842616c616e636529206661" + "696c656420286d6179206265206578706563746564293a43757272656e74206c6564676572206f626a656374206163" + "636f756e743a494e464f3a206765745f63757272656e745f6c65646765725f6f626a5f6669656c64284163636f756e" + "7429206661696c65643a43757272656e74206e6573746564206669656c64206c656e6774683a43757272656e74206e" + "6573746564206669656c643a494e464f3a206765745f63757272656e745f6c65646765725f6f626a5f6e6573746564" + "5f6669656c64206e6f74206170706c696361626c653a43757272656e74206f626a656374205369676e657273206172" + "726179206c656e6774683a43757272656e74206e6573746564206172726179206c656e6774683a494e464f3a206765" + "745f63757272656e745f6c65646765725f6f626a5f6e65737465645f61727261795f6c656e206e6f74206170706c69" + "6361626c653a535543434553533a2043757272656e74206c6564676572206f626a6563742066756e6374696f6e732d" + "2d2d2043617465676f727920343a20416e79204c6564676572204f626a6563742046756e6374696f6e73202d2d2d53" + "75636365737366756c6c7920636163686564206f626a65637420696e20736c6f743a436163686564206f626a656374" + "2062616c616e6365206c656e677468202858525020616d6f756e74293a436163686564206f626a6563742062616c61" + "6e6365202873657269616c697a65642058525020616d6f756e74293a436163686564206f626a6563742062616c616e" + "6365206c656e67746820286e6f6e2d58525020616d6f756e74293a436163686564206f626a6563742062616c616e63" + "653a494e464f3a206765745f6c65646765725f6f626a5f6669656c642842616c616e636529206661696c65643a4361" + "63686564206e6573746564206669656c64206c656e6774683a436163686564206e6573746564206669656c643a494e" + "464f3a206765745f6c65646765725f6f626a5f6e65737465645f6669656c64206e6f74206170706c696361626c653a" + "436163686564206f626a656374205369676e657273206172726179206c656e6774683a436163686564206e65737465" + "64206172726179206c656e6774683a494e464f3a206765745f6c65646765725f6f626a5f6e65737465645f61727261" + "795f6c656e206e6f74206170706c696361626c653a535543434553533a20416e79206c6564676572206f626a656374" + "2066756e6374696f6e73494e464f3a2063616368655f6c65646765725f6f626a206661696c65642028657870656374" + "656420776974682074657374206669787475726573293a494e464f3a206765745f6c65646765725f6f626a5f666965" + "6c64206661696c656420617320657870656374656420286e6f20636163686564206f626a656374293a494e464f3a20" + "6765745f6c65646765725f6f626a5f6e65737465645f6669656c64206661696c65642061732065787065637465643a" + "494e464f3a206765745f6c65646765725f6f626a5f61727261795f6c656e206661696c656420617320657870656374" + "65643a494e464f3a206765745f6c65646765725f6f626a5f6e65737465645f61727261795f6c656e206661696c6564" + "2061732065787065637465643a535543434553533a20416e79206c6564676572206f626a6563742066756e6374696f" + "6e732028696e7465726661636520746573746564294552524f523a206163636f756e745f6b65796c6574206661696c" + "656420666f722063616368696e6720746573743a2d2d2d2043617465676f727920353a204b65796c65742047656e65" + "726174696f6e2046756e6374696f6e73202d2d2d4163636f756e74206b65796c65743a546573745479706543726564" + "656e7469616c206b65796c65743a494e464f3a2063726564656e7469616c5f6b65796c6574206661696c6564202865" + "78706563746564202d20696e74657266616365206973737565293a457363726f77206b65796c65743a4f7261636c65" + "206b65796c65743a535543434553533a204b65796c65742067656e65726174696f6e2066756e6374696f6e73455252" + "4f523a206f7261636c655f6b65796c6574206661696c65643a4552524f523a20657363726f775f6b65796c65742066" + "61696c65643a4552524f523a206163636f756e745f6b65796c6574206661696c65643a2d2d2d2043617465676f7279" + "20363a205574696c6974792046756e6374696f6e73202d2d2d48656c6c6f2c205852504c205741534d20776f726c64" + "21496e70757420646174613a5348413531322068616c6620686173683a4e46542064617461206c656e6774683a4e46" + "5420646174613a494e464f3a206765745f6e6674206661696c656420286578706563746564202d206e6f2073756368" + "204e4654293a54657374207472616365206d6573736167657061796c6f616454726163652066756e6374696f6e2062" + "79746573207772697474656e3a54657374206e756d62657220747261636554726163655f6e756d2066756e6374696f" + "6e20737563636565646564535543434553533a205574696c6974792066756e6374696f6e734552524f523a20747261" + "63655f6e756d2829206661696c65643a4552524f523a2074726163652829206661696c65643a4552524f523a20636f" + "6d707574655f7368613531325f68616c66206661696c65643a2d2d2d2043617465676f727920373a20446174612055" + "70646174652046756e6374696f6e73202d2d2d55706461746564206c656467657220656e7472792064617461206672" + "6f6d205741534d20746573745375636365737366756c6c792075706461746564206c656467657220656e7472792077" + "6974683a535543434553533a2044617461207570646174652066756e6374696f6e734552524f523a20757064617465" + "5f64617461206661696c65643a004d0970726f64756365727302086c616e6775616765010452757374000c70726f63" + "65737365642d6279010572757374631d312e38372e30202831373036376539616320323032352d30352d303929002c" + "0f7461726765745f6665617475726573022b0f6d757461626c652d676c6f62616c732b087369676e2d657874"; + +extern std::string const deepRecursionHex = + "0061736d010000000105016000017f030201000608017f0141c0843d0b070a010666696e69736800000a1601140023" + "0045044041010f0b230041016b240010000b"; + +extern std::string const allKeyletsWasmHex = + "0061736d0100000001480960067f7f7f7f7f7f017f60047f7f7f7f017f60087f7f7f7f7f7f7f7f017f60037f7f7f01" + "7f60037f7f7e017f60057f7f7f7f7f017f60057f7f7f7f7f006000017f60037f7f7f000284051808686f73745f6c69" + "620974726163655f6e756d000408686f73745f6c6962057472616365000508686f73745f6c69621063616368655f6c" + "65646765725f6f626a000308686f73745f6c6962146765745f6c65646765725f6f626a5f6669656c64000108686f73" + "745f6c69621c6765745f63757272656e745f6c65646765725f6f626a5f6669656c64000308686f73745f6c69620d74" + "726163655f6163636f756e74000108686f73745f6c69620e6163636f756e745f6b65796c6574000108686f73745f6c" + "69620b6c696e655f6b65796c6574000208686f73745f6c69620a616d6d5f6b65796c6574000008686f73745f6c6962" + "0c636865636b5f6b65796c6574000008686f73745f6c69621163726564656e7469616c5f6b65796c6574000208686f" + "73745f6c69620f64656c65676174655f6b65796c6574000008686f73745f6c6962166465706f7369745f7072656175" + "74685f6b65796c6574000008686f73745f6c69620a6469645f6b65796c6574000108686f73745f6c69620d65736372" + "6f775f6b65796c6574000008686f73745f6c6962136d70745f69737375616e63655f6b65796c6574000008686f7374" + "5f6c69620e6d70746f6b656e5f6b65796c6574000008686f73745f6c6962106e66745f6f666665725f6b65796c6574" + "000008686f73745f6c69620c6f666665725f6b65796c6574000008686f73745f6c69620e7061796368616e5f6b6579" + "6c6574000208686f73745f6c69621a7065726d697373696f6e65645f646f6d61696e5f6b65796c6574000008686f73" + "745f6c69620e7369676e6572735f6b65796c6574000108686f73745f6c69620d7469636b65745f6b65796c65740000" + "08686f73745f6c69620c7661756c745f6b65796c6574000003040306070805030100110619037f01418080c0000b7f" + "0041ad8ac0000b7f0041b08ac0000b073e05066d656d6f727902000d6f626a6563745f65786973747300180666696e" + "69736800190a5f5f646174615f656e6403010b5f5f686561705f6261736503020a953403a80502017f017e230041a0" + "016b22052400024020012d0000410146044041c280c000411620012802042201ac10001a200041013a000020002001" + "3602040c010b200541186a200141196a290000370300200541106a200141116a290000370300200541086a20014109" + "6a290000370300200520012900013703002002200320054120410110011a02402005412041001002220141004a0440" + "2004450440418b80c000410f4285801410001a20014185801420054180016a412010032201412047044041a680c000" + "4115417f20012001417f4e1b2201ac10001a200041013a0000200020013602040c040b200541c2006a20054182016a" + "2d00003a0000200541f0006a20054197016a2900002206370300200541286a22012005418f016a2900003703002005" + "41306a22022006370300200541386a22032005419f016a2d00003a0000200520052f0080013b014020052005290087" + "013703202005200528008301360043200541df006a20032d00003a0000200541d7006a2002290300370000200541cf" + "006a200129030037000020052005290320370047419a80c000410c200541406b4120410110011a0c020b418b80c000" + "410f2004ac10001a2001200420054180016a411410032201411447044041a680c0004115417f20012001417f4e1b22" + "01ac10001a200041013a0000200020013602040c030b200541c2006a20054182016a2d00003a000020052005290087" + "0137036020052005418c016a290000370065200520052f0080013b0140200520052903603703202005200529006537" + "00252005200528008301360043200541cc006a200529002537000020052005290320370047419a80c000410c200541" + "406b4114410110011a0c010b41bb80c00041072001ac10001a200041013a0000200020013602040c010b2000418002" + "3b01000b200541a0016a24000be92902087f027e23004180076b2200240041d880c000412341014100410010011a02" + "402000027f02404181802020004188016a22024114100422014114460440200041066a2000418a016a22032d00003a" + "00002000200029008f013703e001200020004194016a22042900003700e501200020002f0088013b01042000200029" + "03e0013703d806200020002900e5013700dd062000200028008b01360007200041106a20002900dd06370000200020" + "002903d80637000b41fb80c0004108200041046a2205411410051a4183802020024114100422014114470d03200041" + "1a6a20032d00003a00002000200029008f013703e001200020042900003700e501200020002f0088013b0118200020" + "002903e0013703d806200020002900e5013700dd062000200028008b0136001b200041246a20002900dd0637000020" + "0020002903d80637001f418381c000410c200041186a411410051a200041a0016a2203420037030020004198016a22" + "04420037030020004190016a420037030020004200370388012005411420024120100622014120460d010240200141" + "00480440200020013602300c010b2000417f3602300b41010c020b0c020b200041c5006a2003290300370000200041" + "3d6a2004290300370000200041356a20004190016a290300370000200020002903880137002d41000b3a002c200041" + "88016a2000412c6a418f81c0004107418180201018024020002d0088014101460440200028028c01210141878ac000" + "4112420510001a0c010b41002101419681c000413541014100410010011a200041de006a41c4003a0000200041d800" + "6a4100360200200041e3006a41003a0000200041d5a6013b015c200042003703502000410036005f200041a0016a22" + "03420037030020004198016a2204420037030020004190016a2205420037030020004200370388010240200041046a" + "4114200041186a4114200041d0006a411420004188016a412010072202412047044002402002410048044020002002" + "3602680c010b2000417f3602680b410121010c010b200041fd006a2003290300370000200041f5006a200429030037" + "0000200041ed006a200529030037000020002000290388013700650b200020013a006420004188016a200041e4006a" + "41cb81c00041094100101820002d0088014101460440200028028c01210141878ac0004112420510001a0c010b41d4" + "81c000413741014100410010011a200041f0016a200041286a2203280100360200200041e8016a200041206a220429" + "0100370300200041fc016a200041d8006a290300220837020020004184026a200041e0006a28020022023602002000" + "20002901183703e0012000200029035022093702f401200041e8066a22012002360200200041e0066a220220083703" + "00200020093703d806200041f4066a2004290100370200200041fc066a2003280100360200200020002901183702ec" + "0620004188026a200041d8066a22034128101a2000418c016a200041e0016a41d000101a2000410136028801200041" + "f0066a220442003703002001420037030020024200370300200042003703d8062000027f41998ac0004114200041b4" + "016a412820034120100822034120470440024020034100480440200020033602e4010c010b2000417f3602e4010b41" + "010c010b200041f9016a2004290300370000200041f1016a2001290300370000200041e9016a200229030037000020" + "0020002903d8063700e10141000b3a00e001200041b4026a200041e0016a418b82c000410341818020101820002d00" + "b402410146044020002802b802210141878ac0004112420610001a0c010b41002101418e82c0004131410141004100" + "10011a200041063602d806200041f8016a22034200370300200041f0016a22044200370300200041e8016a22054200" + "370300200042003703e0010240200041046a4114200041d8066a4104200041e0016a41201009220241204704400240" + "20024100480440200020023602c0020c010b2000417f3602c0020b410121010c010b200041d5026a20032903003700" + "00200041cd026a2004290300370000200041c5026a2005290300370000200020002903e0013700bd020b200020013a" + "00bc02200041e0016a200041bc026a41bf82c000410541818020101820002d00e001410146044020002802e4012101" + "41878ac0004112420610001a0c010b4100210141c482c000413341014100410010011a200041f8016a220342003703" + "00200041f0016a22044200370300200041e8016a22054200370300200042003703e0010240200041046a2202411420" + "02411441f782c0004112200041e0016a4120100a22024120470440024020024100480440200020023602e4020c010b" + "2000417f3602e4020b410121010c010b200041f9026a2003290300370000200041f1026a2004290300370000200041" + "e9026a2005290300370000200020002903e0013700e1020b200020013a00e002200041e0016a200041e0026a418983" + "c000410a41988020101820002d00e001410146044020002802e401210141878ac0004112420710001a0c010b410021" + "01419383c000413841014100410010011a200041f8016a22034200370300200041f0016a22044200370300200041e8" + "016a22054200370300200042003703e0010240200041046a4114200041186a4114200041e0016a4120100b22024120" + "47044002402002410048044020002002360288030c010b2000417f360288030b410121010c010b2000419d036a2003" + "29030037000020004195036a20042903003700002000418d036a2005290300370000200020002903e001370085030b" + "200020013a008403200041e0016a20004184036a41cb83c000410841818020101820002d00e0014101460440200028" + "02e401210141878ac0004112420810001a0c010b41d383c000413641014100410010011a230041206b220124002001" + "41186a22044200370300200141106a22054200370300200141086a2206420037030020014200370300200041a8036a" + "2202027f200041046a4114200041186a411420014120100c2203412047044002402003410048044020022003360204" + "0c010b2002417f3602040b41010c010b20022001290300370001200241196a2004290300370000200241116a200529" + "0300370000200241096a200629030037000041000b3a0000200141206a2400200041e0016a2002418984c000410e41" + "818020101820002d00e001410146044020002802e401210141878ac0004112420910001a0c010b419784c000413c41" + "014100410010011a230041206b22012400200141186a22044200370300200141106a22054200370300200141086a22" + "06420037030020014200370300200041cc036a2202027f200041046a411420014120100d2203412047044002402003" + "4100480440200220033602040c010b2002417f3602040b41010c010b20022001290300370001200241196a20042903" + "00370000200241116a2005290300370000200241096a200629030037000041000b3a0000200141206a2400200041e0" + "016a200241d384c000410341818020101820002d00e001410146044020002802e401210141878ac0004112420a1000" + "1a0c010b41d684c000413141014100410010011a230041306b220124002001410b36020c200141286a220442003703" + "00200141206a22054200370300200141186a2206420037030020014200370310200041f0036a2202027f200041046a" + "41142001410c6a4104200141106a4120100e22034120470440024020034100480440200220033602040c010b200241" + "7f3602040b41010c010b20022001290310370001200241196a2004290300370000200241116a200529030037000020" + "0241096a200629030037000041000b3a0000200141306a2400200041e0016a2002418785c000410641818020101820" + "002d00e001410146044020002802e401210141878ac0004112420b10001a0c010b418d85c000413441014100410010" + "011a230041306b220124002001410c36020c200141286a22044200370300200141206a22054200370300200141186a" + "220642003703002001420037031020004194046a2202027f200041046a41142001410c6a4104200141106a4120100f" + "22034120470440024020034100480440200220033602040c010b2002417f3602040b41010c010b2002200129031037" + "0001200241196a2004290300370000200241116a2005290300370000200241096a200629030037000041000b3a0000" + "200141306a2400200041f4016a200041146a280100360200200041ec016a2000410c6a290100370200200020002901" + "043702e401200041808080e0003602e001200041d8066a200241c185c000410b41848020101820002d00d806410146" + "044020002802dc06210141878ac0004112420c10001a0c010b41cc85c000413941014100410010011a230041206b22" + "012400200141186a22044200370300200141106a22054200370300200141086a220642003703002001420037030020" + "0041b8046a2202027f200041e0016a4118200041186a41142001412010102203412047044002402003410048044020" + "0220033602040c010b2002417f3602040b41010c010b20022001290300370001200241196a20042903003700002002" + "41116a2005290300370000200241096a200629030037000041000b3a0000200141206a2400200041d8066a20024185" + "86c000410741818020101820002d00d806410146044020002802dc06210141878ac0004112420d10001a0c010b418c" + "86c000413541014100410010011a230041306b220124002001410636020c200141286a22044200370300200141206a" + "22054200370300200141186a2206420037030020014200370310200041dc046a2202027f200041186a41142001410c" + "6a4104200141106a4120101122034120470440024020034100480440200220033602040c010b2002417f3602040b41" + "010c010b20022001290310370001200241196a2004290300370000200241116a2005290300370000200241096a2006" + "29030037000041000b3a0000200141306a2400200041d8066a200241c186c000410c41828020101820002d00d80641" + "0146044020002802dc06210141878ac0004112420d10001a0c010b41cd86c000413a41014100410010011a23004130" + "6b220124002001410d36020c200141286a22044200370300200141206a22054200370300200141186a220642003703" + "002001420037031020004180056a2202027f200041046a41142001410c6a4104200141106a41201012220341204704" + "40024020034100480440200220033602040c010b2002417f3602040b41010c010b2002200129031037000120024119" + "6a2004290300370000200241116a2005290300370000200241096a200629030037000041000b3a0000200141306a24" + "00200041d8066a2002418787c000410541818020101820002d00d806410146044020002802dc06210141878ac00041" + "12420d10001a0c010b418c87c000413341014100410010011a230041306b220124002001410e36020c200141286a22" + "044200370300200141206a22054200370300200141186a2206420037030020014200370310200041a4056a2202027f" + "200041046a4114200041186a41142001410c6a4104200141106a412010132203412047044002402003410048044020" + "0220033602040c010b2002417f3602040b41010c010b20022001290310370001200241196a20042903003700002002" + "41116a2005290300370000200241096a200629030037000041000b3a0000200141306a2400200041d8066a200241bf" + "87c000410a41818020101820002d00d806410146044020002802dc06210141878ac0004112420e10001a0c010b41c9" + "87c000413841014100410010011a230041306b220124002001410f36020c200141286a22044200370300200141206a" + "22054200370300200141186a2206420037030020014200370310200041c8056a2202027f200041046a41142001410c" + "6a4104200141106a4120101422034120470440024020034100480440200220033602040c010b2002417f3602040b41" + "010c010b20022001290310370001200241196a2004290300370000200241116a2005290300370000200241096a2006" + "29030037000041000b3a0000200141306a2400200041d8066a2002418188c000411241828020101820002d00d80641" + "0146044020002802dc06210141878ac0004112420f10001a0c010b419388c00041c00041014100410010011a230041" + "206b22012400200141186a22044200370300200141106a22054200370300200141086a220642003703002001420037" + "0300200041ec056a2202027f200041046a411420014120101522034120470440024020034100480440200220033602" + "040c010b2002417f3602040b41010c010b20022001290300370001200241196a2004290300370000200241116a2005" + "290300370000200241096a200629030037000041000b3a0000200141206a2400200041d8066a200241d388c000410a" + "4100101820002d00d806410146044020002802dc06210141878ac0004112421010001a0c010b41dd88c00041384101" + "4100410010011a230041306b220124002001411236020c200141286a22044200370300200141206a22054200370300" + "200141186a220642003703002001420037031020004190066a2202027f200041046a41142001410c6a410420014110" + "6a4120101622034120470440024020034100480440200220033602040c010b2002417f3602040b41010c010b200220" + "01290310370001200241196a2004290300370000200241116a2005290300370000200241096a200629030037000041" + "000b3a0000200141306a2400200041d8066a2002419589c000410641818020101820002d00d8064101460440200028" + "02dc06210141878ac0004112421210001a0c010b41012101419b89c000413441014100410010011a230041306b2202" + "24002002411336020c200241286a22054200370300200241206a22064200370300200241186a220742003703002002" + "4200370310200041b4066a2203027f200041046a41142002410c6a4104200241106a41201017220441204704400240" + "20044100480440200320043602040c010b2003417f3602040b41010c010b20032002290310370001200341196a2005" + "290300370000200341116a2006290300370000200341096a200729030037000041000b3a0000200241306a24002000" + "41d8066a200341cf89c000410541818020101820002d00d806410146044020002802dc06210141878ac00041124213" + "10001a0c010b41d489c000413341014100410010011a0b20004180076a240020010f0b418080c000410b417f200120" + "01417f4e1bac1000000bfd0401067f200241104f0440024020002000410020006b41037122056a22044f0d00200121" + "0320050440200521060340200020032d00003a0000200341016a2103200041016a2100200641016b22060d000b0b20" + "0541016b4107490d000340200020032d00003a0000200041016a200341016a2d00003a0000200041026a200341026a" + "2d00003a0000200041036a200341036a2d00003a0000200041046a200341046a2d00003a0000200041056a20034105" + "6a2d00003a0000200041066a200341066a2d00003a0000200041076a200341076a2d00003a0000200341086a210320" + "0041086a22002004470d000b0b2004200220056b2207417c7122086a21000240200120056a22064103714504402000" + "20044d0d0120062101034020042001280200360200200141046a2101200441046a22042000490d000b0c010b200020" + "044d0d002006410374220541187121032006417c71220241046a2101410020056b4118712105200228020021020340" + "200420022003762001280200220220057472360200200141046a2101200441046a22042000490d000b0b2007410371" + "2102200620086a21010b02402000200020026a22064f0d002002410771220304400340200020012d00003a00002001" + "41016a2101200041016a2100200341016b22030d000b0b200241016b4107490d000340200020012d00003a00002000" + "41016a200141016a2d00003a0000200041026a200141026a2d00003a0000200041036a200141036a2d00003a000020" + "0041046a200141046a2d00003a0000200041056a200141056a2d00003a0000200041066a200141066a2d00003a0000" + "200041076a200141076a2d00003a0000200141086a2101200041086a22002006470d000b0b0b0ba30a0100418080c0" + "000b990a6572726f725f636f64653d47657474696e67206669656c643a204669656c6420646174613a204572726f72" + "2067657474696e67206669656c643a204572726f723a204572726f722067657474696e67206b65796c65743a202424" + "242424205354415254494e47205741534d20455845435554494f4e2024242424244163636f756e743a44657374696e" + "6174696f6e3a4163636f756e744163636f756e74206f626a656374206578697374732c2070726f63656564696e6720" + "7769746820657363726f772066696e6973682e54727573746c696e6554727573746c696e65206f626a656374206578" + "697374732c2070726f63656564696e67207769746820657363726f772066696e6973682e414d4d414d4d206f626a65" + "6374206578697374732c2070726f63656564696e67207769746820657363726f772066696e6973682e436865636b43" + "6865636b206f626a656374206578697374732c2070726f63656564696e67207769746820657363726f772066696e69" + "73682e7465726d73616e64636f6e646974696f6e7343726564656e7469616c43726564656e7469616c206f626a6563" + "74206578697374732c2070726f63656564696e67207769746820657363726f772066696e6973682e44656c65676174" + "6544656c6567617465206f626a656374206578697374732c2070726f63656564696e67207769746820657363726f77" + "2066696e6973682e4465706f736974507265617574684465706f73697450726561757468206f626a65637420657869" + "7374732c2070726f63656564696e67207769746820657363726f772066696e6973682e444944444944206f626a6563" + "74206578697374732c2070726f63656564696e67207769746820657363726f772066696e6973682e457363726f7745" + "7363726f77206f626a656374206578697374732c2070726f63656564696e67207769746820657363726f772066696e" + "6973682e4d505449737375616e63654d505449737375616e6365206f626a656374206578697374732c2070726f6365" + "6564696e67207769746820657363726f772066696e6973682e4d50546f6b656e4d50546f6b656e206f626a65637420" + "6578697374732c2070726f63656564696e67207769746820657363726f772066696e6973682e4e46546f6b656e4f66" + "6665724e46546f6b656e4f66666572206f626a656374206578697374732c2070726f63656564696e67207769746820" + "657363726f772066696e6973682e4f666665724f66666572206f626a656374206578697374732c2070726f63656564" + "696e67207769746820657363726f772066696e6973682e5061794368616e6e656c5061794368616e6e656c206f626a" + "656374206578697374732c2070726f63656564696e67207769746820657363726f772066696e6973682e5065726d69" + "7373696f6e6564446f6d61696e5065726d697373696f6e6564446f6d61696e206f626a656374206578697374732c20" + "70726f63656564696e67207769746820657363726f772066696e6973682e5369676e65724c6973745369676e65724c" + "697374206f626a656374206578697374732c2070726f63656564696e67207769746820657363726f772066696e6973" + "682e5469636b65745469636b6574206f626a656374206578697374732c2070726f63656564696e6720776974682065" + "7363726f772066696e6973682e5661756c745661756c74206f626a656374206578697374732c2070726f6365656469" + "6e67207769746820657363726f772066696e6973682e43757272656e74207365712076616c75653a004d0970726f64" + "756365727302086c616e6775616765010452757374000c70726f6365737365642d6279010572757374631d312e3837" + "2e30202831373036376539616320323032352d30352d303929002c0f7461726765745f6665617475726573022b0f6d" + "757461626c652d676c6f62616c732b087369676e2d657874"; + +extern std::string const codecovTestsWasmHex = + "0061736d0100000001570b60067f7f7f7f7f7f017f60047f7f7f7f017f60027f7f017f60057f7f7f7f7f017f60037f" + "7f7f017f60077f7f7f7f7f7f7f017f60087f7f7f7f7f7f7f7f017f60017f017f60037f7f7e017f60047f7f7f7f0060" + "00017f02990d3c08686f73745f6c6962057472616365000308686f73745f6c69620974726163655f6e756d00080868" + "6f73745f6c69620e6765745f6c65646765725f73716e000208686f73745f6c6962166765745f706172656e745f6c65" + "646765725f74696d65000208686f73745f6c6962166765745f706172656e745f6c65646765725f6861736800020868" + "6f73745f6c69620c6765745f626173655f666565000208686f73745f6c696211616d656e646d656e745f656e61626c" + "6564000208686f73745f6c69620c6765745f74785f6669656c64000408686f73745f6c69620e6163636f756e745f6b" + "65796c6574000108686f73745f6c69621063616368655f6c65646765725f6f626a000408686f73745f6c69621c6765" + "745f63757272656e745f6c65646765725f6f626a5f6669656c64000408686f73745f6c6962146765745f6c65646765" + "725f6f626a5f6669656c64000108686f73745f6c6962136765745f74785f6e65737465645f6669656c64000108686f" + "73745f6c6962236765745f63757272656e745f6c65646765725f6f626a5f6e65737465645f6669656c64000108686f" + "73745f6c69621b6765745f6c65646765725f6f626a5f6e65737465645f6669656c64000308686f73745f6c69621067" + "65745f74785f61727261795f6c656e000708686f73745f6c6962206765745f63757272656e745f6c65646765725f6f" + "626a5f61727261795f6c656e000708686f73745f6c6962186765745f6c65646765725f6f626a5f61727261795f6c65" + "6e000208686f73745f6c6962176765745f74785f6e65737465645f61727261795f6c656e000208686f73745f6c6962" + "276765745f63757272656e745f6c65646765725f6f626a5f6e65737465645f61727261795f6c656e000208686f7374" + "5f6c69621f6765745f6c65646765725f6f626a5f6e65737465645f61727261795f6c656e000408686f73745f6c6962" + "0b7570646174655f64617461000208686f73745f6c696213636f6d707574655f7368613531325f68616c6600010868" + "6f73745f6c696209636865636b5f736967000008686f73745f6c6962076765745f6e6674000008686f73745f6c6962" + "0e6765745f6e66745f697373756572000108686f73745f6c69620d6765745f6e66745f7461786f6e000108686f7374" + "5f6c69620d6765745f6e66745f666c616773000208686f73745f6c6962146765745f6e66745f7472616e736665725f" + "666565000208686f73745f6c69620e6765745f6e66745f73657269616c000108686f73745f6c69620d74726163655f" + "6163636f756e74000108686f73745f6c69620c74726163655f616d6f756e74000108686f73745f6c69620c63686563" + "6b5f6b65796c6574000008686f73745f6c69620f666c6f61745f66726f6d5f75696e74000308686f73745f6c69620b" + "6c696e655f6b65796c6574000608686f73745f6c69620a616d6d5f6b65796c6574000008686f73745f6c6962116372" + "6564656e7469616c5f6b65796c6574000608686f73745f6c69620e6d70746f6b656e5f6b65796c6574000008686f73" + "745f6c69621274726163655f6f70617175655f666c6f6174000108686f73745f6c69620d666c6f61745f636f6d7061" + "7265000108686f73745f6c696209666c6f61745f616464000508686f73745f6c69620e666c6f61745f737562747261" + "6374000508686f73745f6c69620e666c6f61745f6d756c7469706c79000508686f73745f6c69620c666c6f61745f64" + "6976696465000508686f73745f6c69620a666c6f61745f726f6f74000008686f73745f6c696209666c6f61745f706f" + "77000008686f73745f6c696209666c6f61745f6c6f67000308686f73745f6c69620d657363726f775f6b65796c6574" + "000008686f73745f6c6962136d70745f69737375616e63655f6b65796c6574000008686f73745f6c6962106e66745f" + "6f666665725f6b65796c6574000008686f73745f6c69620c6f666665725f6b65796c6574000008686f73745f6c6962" + "0d6f7261636c655f6b65796c6574000008686f73745f6c69620e7061796368616e5f6b65796c6574000608686f7374" + "5f6c69621a7065726d697373696f6e65645f646f6d61696e5f6b65796c6574000008686f73745f6c69620d7469636b" + "65745f6b65796c6574000008686f73745f6c69620c7661756c745f6b65796c6574000008686f73745f6c69620f6465" + "6c65676174655f6b65796c6574000008686f73745f6c6962166465706f7369745f707265617574685f6b65796c6574" + "000008686f73745f6c69620a6469645f6b65796c6574000108686f73745f6c69620e7369676e6572735f6b65796c65" + "740001030302090a05030100110619037f01418080c0000b7f0041bba1c0000b7f0041c0a1c0000b072e04066d656d" + "6f727902000666696e697368003d0a5f5f646174615f656e6403010b5f5f686561705f6261736503020abf2c024600" + "0240200020014704402002200341014100410010001a20004100480d01418b80c000410b2000ad1001000b20022003" + "2000ac10011a0f0b418b80c000410b2000ac1001000bf52b020a7f017e23004190026b22002400419680c000412341" + "014100410010001a20004100360260200041e0006a220241041002410441bd8cc000410e103c200041003602602002" + "41041003410441cb8cc0004116103c200041f8006a22044200370300200041f0006a22014200370300200041e8006a" + "2205420037030020004200370360200241201004412041e18cc0004116103c20004100360260200241041005410441" + "f78cc000410c103c200041106a2207428182848890a0c08001370300200041186a2206428182848890a0c080013703" + "00200041206a2209428182848890a0c080013703002000428182848890a0c0800137030841b980c000410e10064101" + "41c780c0004111103c200041086a41201006410141c780c0004111103c418180202002411410072203411446044002" + "402000412e6a200041e2006a2d00003a0000200020002900673703e8012000200041ec006a2900003700ed01200020" + "002f00603b012c200020002903e8013703a801200020002900ed013700ad012000200028006336002f200041386a20" + "002900ad01370000200020002903a80137003320044200370300200142003703002005420037030020004200370360" + "2000412c6a2204411420024120100822034120470d00200041c2006a20002d00623a0000200041f0016a2203200041" + "ef006a290000220a370300200041cf006a200a370000200041d7006a200041f7006a290000370000200041df006a20" + "0041ff006a2d00003a0000200020002f01603b01402000200028006336004320002000290067370047200041406b41" + "2041001009410141d880c0004110103c2001410036020020054200370300200042003703604181802020024114100a" + "411441838dc000411c103c20014100360200200542003703002000420037036041014181802020024114100b411441" + "9f8dc0004114103c200041043602a001200041818020360260200041f8016a22054100360200200342003703002000" + "42003703e80120024104200041e8016a22014114100c411441b38dc0004113103c2005410036020020034200370300" + "200042003703e801200220002802a00120014114100d411441c68dc0004123103c2005410036020020034200370300" + "200042003703e8014101200220002802a00120014114100e411441e98dc000411b103c4189803c100f412041e880c0" + "004110103c4189803c1010412041f880c0004120103c41014189803c10114120419881c0004118103c200220002802" + "a0011012412041b081c0004117103c200220002802a0011013412041c781c0004127103c4101200220002802a00110" + "14412041ee81c000411f103c2004411410154114418d82c000410b103c20004180026a220842003703002005420037" + "030020034200370300200042003703e801200220002802a001200141201016412041848ec0004113103c419882c000" + "410c41a482c000410b41af82c000410e1017410141bd82c0004109103c200041c0016a2009290300370300200041b8" + "016a2006290300370300200041b0016a2007290300370300200020002903083703a801200541003b01002003420037" + "0300200042003703e80120044114200041a8016a22074120200141121018411241978ec0004107103c200541003602" + "0020034200370300200042003703e801200741202001411410194114419e8ec000410e103c200041003602e8012007" + "412020014104101a410441ac8ec000410d103c20074120101b410841c682c000410d103c20074120101c410a41d382" + "c0004114103c200041003602e8012007412020014104101d410441b98ec000410e103c41e782c000410d2004411410" + "1e410041f482c000410d103c41e782c000410d418183c0004108101f4100418983c000410c103c41e782c000410d41" + "9583c0004108101f4100419d83c0004111103c417f41041004417141ae83c000411e103c200041003602e801200141" + "7f1004417141c78ec000411e103c200041ea016a41003a0000200041003b01e801200141031004417d41e58ec00041" + "24103c200041003602e8012001418094ebdc031004417341898fc0004123103c4102100f416f41cc83c000411f103c" + "417f20002802a0011012417141eb83c000411f103c2002417f10124171418a84c000411f103c200241810810124174" + "41a984c0004120103c200041e094ebdc036a220620002802a0011012417341c984c000411f103c2008420037030020" + "05420037030020034200370300200042003703e8012004411420064108200141201020417341ac8fc0004118103c20" + "0842003703002005420037030020034200370300200042003703e8012004411420044114200141201020417141c48f" + "c000411a103c200842003703002005420037030020034200370300200042003703e801200641082001412041001021" + "417341de8fc0004117103c200842003703002005420037030020034200370300200042003703e801200220002802a0" + "012001412041001021417141f58fc0004120103c200620002802a00141011009417341e884c0004118103c20022000" + "2802a001410110094171418085c000411a103c200842003703002005420037030020034200370300200042003703e8" + "01200620002802a0012001412010084173419590c0004116103c200842003703002005420037030020034200370300" + "200042003703e801200220002802a001200141201008417141ab90c0004118103c2008420037030020054200370300" + "20034200370300200042003703e8012004411420044114200620002802a001200141201022417341c390c000411c10" + "3c200842003703002005420037030020034200370300200042003703e8012004411420044114200220002802a00120" + "0141201022417141df90c000411e103c200842003703002005420037030020034200370300200042003703e80141a7" + "a1c0004114200620002802a001200141201023417341fd90c0004119103c2008420037030020054200370300200342" + "00370300200042003703e80141a7a1c0004114200220002802a0012001412010234171419691c000411f103c200842" + "003703002005420037030020034200370300200042003703e80141a7a1c0004114419a85c000411420014120102341" + "7141b591c0004129103c200842003703002005420037030020034200370300200042003703e80141ae85c000412841" + "a7a1c0004114200141201023417141de91c0004125103c200041dc016a2000413c6a280100360200200041d4016a20" + "0041346a2901003702002000200029012c3702cc01200041808080083602c801200041003b01e801200041c8016a22" + "09411841a7a1c00041142001410210234171418392c000410e103c200620002802a001422a1001417341d685c00041" + "11103c200041003b01e8014102200141021007416f419192c000411b103c200041003b01e801410220014102100a41" + "6f41ac92c000412b103c200041003b01e8014101410220014102100b416f41d792c0004123103c4102100f416f41cc" + "83c000411f103c41021010416f41e785c000412f103c410141021011416f419686c0004127103c41b980c000418108" + "1006417441bd86c000411f103c41b980c00041c1001006417441dc86c000411a103c200041003b01e8012002418108" + "20014102100c417441fa92c0004121103c200041003b01e801200241810820014102100d4174419b93c0004131103c" + "200041003b01e8014101200241810820014102100e417441cc93c0004129103c20024181081012417441f686c00041" + "25103c200241810810134174419b87c0004135103c410120024181081014417441d087c000412d103c200241812010" + "15417441fd87c0004119103c41e782c00041810841a482c000410b41af82c000410e1017417441bd82c0004109103c" + "41e782c000410d41a482c00041810841af82c000410e1017417441bd82c0004109103c41e782c000410d41a482c000" + "410b41af82c0004181081017417441bd82c0004109103c200041003b01e8012002418108200141021016417441f593" + "c0004121103c200041003b01e80141a7a1c00041810841a7a1c00041142001410210234174419694c0004118103c20" + "0041003b01e80120044114200441142002418108200141021024417441ae94c000411f103c200041003b01e8012009" + "41810820044114200141021025417441cd94c0004122103c41e782c000410d200620002802a0014100100041734196" + "88c000410f103c200042d487b6f4c7d4b1c0003700e00141e782c000410d200041e095ebdc036a2207410810264173" + "41a588c000411c103c41e782c000410d200620002802a001101f417341c188c0004116103c20074108200041e0016a" + "220641081027417341d788c0004118103c20064108200741081027417341ef88c0004118103c200041003b01e80120" + "074108200641082001410241001028417341ef94c0004114103c200041003b01e80120064108200741082001410241" + "0010284173418395c0004114103c200041003b01e801200741082006410820014102410010294173419795c0004119" + "103c200041003b01e80120064108200741082001410241001029417341b095c0004119103c200041003b01e8012007" + "410820064108200141024100102a417341c995c0004119103c200041003b01e8012006410820074108200141024100" + "102a417341e295c0004119103c200041003b01e8012007410820064108200141024100102b417341fb95c000411710" + "3c200041003b01e8012006410820074108200141024100102b4173419296c0004117103c200041003b01e801200741" + "084103200141024100102c417341a996c0004114103c200041003b01e801200741084103200141024100102d417341" + "bd96c0004113103c200041003b01e80120074108200141024100102e417341d096c0004113103c2008420037030020" + "05420037030020034200370300200042003703e801200441142004411420014120102f417141e396c000411f103c20" + "0842003703002005420037030020034200370300200042003703e80120044114200441142001412010304171418297" + "c0004125103c200842003703002005420037030020034200370300200042003703e801200441142004411420014120" + "1031417141a797c0004122103c200842003703002005420037030020034200370300200042003703e8012004411420" + "044114200141201032417141c997c000411e103c200842003703002005420037030020034200370300200042003703" + "e8012004411420044114200141201033417141e797c000411f103c2008420037030020054200370300200342003703" + "00200042003703e8012004411420044114200441142001412010344171418698c0004120103c200842003703002005" + "420037030020034200370300200042003703e8012004411420044114200141201035417141a698c000412c103c2008" + "42003703002005420037030020034200370300200042003703e8012004411420044114200141201036417141d298c0" + "00411f103c200842003703002005420037030020034200370300200042003703e80120044114200441142001412010" + "37417141f198c000411e103c200220002802a001410010094171418789c0004123103c200041003b01e80120044114" + "200220002802a0012001410210184171418f99c000411a103c200041003b01e801200220002802a001200141021019" + "417141a999c0004121103c200041003b01e801200220002802a00120014102101a417141ca99c0004120103c200220" + "002802a001101b417141aa89c0004120103c200220002802a001101c417141ca89c0004127103c200041003602e801" + "200220002802a00120014104101d417141ea99c0004121103c200041003b01e801200220002802a001200141021008" + "4171418b9ac0004124103c200041808080083602e801200041003b018e02200220002802a001200141042000418e02" + "6a220341021020417141af9ac0004122103c200041003b018e02200220002802a00122052004411420022005200341" + "021024417141d19ac0004128103c200041003b018e0220044114200220002802a00122052002200520034102102441" + "7141f99ac0004128103c200041003b018e02200220002802a00120044114200341021038417141a19bc0004126103c" + "200041003b018e0220044114200220002802a001200341021038417141c79bc0004126103c200041003b018e022002" + "20002802a00120044114200341021039417141ed9bc000412d103c200041003b018e0220044114200220002802a001" + "2003410210394171419a9cc000412d103c200041003b018e02200220002802a00120034102103a417141c79cc00041" + "20103c200041003b018e02200220002802a0012001410420034102102f417141e79cc0004123103c200041003b018e" + "02200220002802a00120044114419a85c00041142003410210224171418a9dc0004122103c200041003b018e022004" + "4114200220002802a001419a85c0004114200341021022417141ac9dc0004122103c200041003b018e022002200028" + "02a00120014104200341021030417141ce9dc0004129103c200041003b018e0220094118200220002802a001200341" + "021025417141f79dc0004124103c200041003b018e02200220002802a001200141042003410210314171419b9ec000" + "4126103c200041003b018e02200220002802a00120014104200341021032417141c19ec0004122103c200041003b01" + "8e02200220002802a00120014104200341021033417141e39ec0004123103c200041003b018e02200220002802a001" + "2004411420014104200341021034417141869fc0004125103c200041003b018e0220044114200220002802a0012001" + "4104200341021034417141ab9fc0004125103c200041003b018e02200220002802a001200141042003410210354171" + "41d09fc0004130103c200041003b018e02200220002802a00120034102103b41714180a0c0004124103c200041003b" + "018e02200220002802a00120014104200341021036417141a4a0c0004123103c200041003b018e02200220002802a0" + "0120014104200341021037417141c7a0c0004122103c200041003b018e02200220002802a00141f189c00041202003" + "41021018417141e9a0c000411d103c41e782c000410d200220002802a001101e417141918ac0004123103c41e796ab" + "dd03410d41f189c000412041001000417341b48ac0004110103c41e796abdd03410d200641081026417341c48ac000" + "411d103c41e796abdd03410d20044114101e417341e18ac0004118103c41e796abdd03410d419583c0004108101f41" + "7341f98ac0004117103c200220002802a001200241810841001000417441908bc000410e103c200241810842011001" + "4174419e8bc0004112103c41e782c000418108200641081026417441b08bc000411b103c41e782c000418108200441" + "14101e417441cb8bc0004116103c41e782c000418108419583c0004108101f417441e18bc0004115103c41e782c000" + "410d200220002802a001101f417141f68bc0004119103c200041003b018e02200220002802a0012004411420034102" + "102541714186a1c0004121103c41e782c000410d200220002802a001410210004171418f8cc0004114103c41014100" + "20044114101e410041a38cc000411a103c20004190026a240041010f0b0b418080c000410b417f20032003417f4e1b" + "ac1001000b0b92210200418080c0000bae056572726f725f636f64653d54455354204641494c454424242424242053" + "54415254494e47205741534d20455845435554494f4e202424242424746573745f616d656e646d656e74616d656e64" + "6d656e745f656e61626c656463616368655f6c65646765725f6f626a6765745f74785f61727261795f6c656e676574" + "5f63757272656e745f6c65646765725f6f626a5f61727261795f6c656e6765745f6c65646765725f6f626a5f617272" + "61795f6c656e6765745f74785f6e65737465645f61727261795f6c656e6765745f63757272656e745f6c6564676572" + "5f6f626a5f6e65737465645f61727261795f6c656e6765745f6c65646765725f6f626a5f6e65737465645f61727261" + "795f6c656e7570646174655f6461746174657374206d65737361676574657374207075626b65797465737420736967" + "6e6174757265636865636b5f7369676765745f6e66745f666c6167736765745f6e66745f7472616e736665725f6665" + "6574657374696e6720747261636574726163655f6163636f756e74400000000000005f74726163655f616d6f756e74" + "400000000000000074726163655f616d6f756e745f7a65726f6765745f706172656e745f6c65646765725f68617368" + "5f6e65675f7074726765745f74785f61727261795f6c656e5f696e76616c69645f736669656c646765745f74785f6e" + "65737465645f61727261795f6c656e5f6e65675f7074726765745f74785f6e65737465645f61727261795f6c656e5f" + "6e65675f6c656e6765745f74785f6e65737465645f61727261795f6c656e5f746f6f5f6c6f6e676765745f74785f6e" + "65737465645f61727261795f6c656e5f7074725f6f6f6263616368655f6c65646765725f6f626a5f7074725f6f6f62" + "63616368655f6c65646765725f6f626a5f77726f6e675f6c656e555344303030303030303030303030303030303000" + "41d685c0000bd11b74726163655f6e756d5f6f6f625f7374726765745f63757272656e745f6c65646765725f6f626a" + "5f61727261795f6c656e5f696e76616c69645f736669656c646765745f6c65646765725f6f626a5f61727261795f6c" + "656e5f696e76616c69645f736669656c64616d656e646d656e745f656e61626c65645f746f6f5f6269675f736c6963" + "65616d656e646d656e745f656e61626c65645f746f6f5f6c6f6e676765745f74785f6e65737465645f61727261795f" + "6c656e5f746f6f5f6269675f736c6963656765745f63757272656e745f6c65646765725f6f626a5f6e65737465645f" + "61727261795f6c656e5f746f6f5f6269675f736c6963656765745f6c65646765725f6f626a5f6e65737465645f6172" + "7261795f6c656e5f746f6f5f6269675f736c6963657570646174655f646174615f746f6f5f6269675f736c69636574" + "726163655f6f6f625f736c69636574726163655f6f70617175655f666c6f61745f6f6f625f736c6963657472616365" + "5f616d6f756e745f6f6f625f736c696365666c6f61745f636f6d706172655f6f6f625f736c69636531666c6f61745f" + "636f6d706172655f6f6f625f736c6963653263616368655f6c65646765725f6f626a5f77726f6e675f73697a655f75" + "696e743235366765745f6e66745f666c6167735f77726f6e675f73697a655f75696e743235366765745f6e66745f74" + "72616e736665725f6665655f77726f6e675f73697a655f75696e743235363030303030303030303030303030303030" + "30303030303030303030303030303174726163655f6163636f756e745f77726f6e675f73697a655f6163636f756e74" + "5f696474726163655f6f6f625f737472696e6774726163655f6f70617175655f666c6f61745f6f6f625f737472696e" + "6774726163655f6163636f756e745f6f6f625f737472696e6774726163655f616d6f756e745f6f6f625f737472696e" + "6774726163655f746f6f5f6c6f6e6774726163655f6e756d5f746f6f5f6c6f6e6774726163655f6f70617175655f66" + "6c6f61745f746f6f5f6c6f6e6774726163655f6163636f756e745f746f6f5f6c6f6e6774726163655f616d6f756e74" + "5f746f6f5f6c6f6e6774726163655f616d6f756e745f77726f6e675f6c656e67746874726163655f696e76616c6964" + "5f61735f68657874726163655f6163636f756e745f636865636b5f646573796e636765745f6c65646765725f73716e" + "6765745f706172656e745f6c65646765725f74696d656765745f706172656e745f6c65646765725f68617368676574" + "5f626173655f6665656765745f63757272656e745f6c65646765725f6f626a5f6669656c646765745f6c6564676572" + "5f6f626a5f6669656c646765745f74785f6e65737465645f6669656c646765745f63757272656e745f6c6564676572" + "5f6f626a5f6e65737465645f6669656c646765745f6c65646765725f6f626a5f6e65737465645f6669656c64636f6d" + "707574655f7368613531325f68616c666765745f6e66746765745f6e66745f6973737565726765745f6e66745f7461" + "786f6e6765745f6e66745f73657269616c6765745f706172656e745f6c65646765725f686173685f6e65675f6c656e" + "6765745f706172656e745f6c65646765725f686173685f6275665f746f6f5f736d616c6c6765745f706172656e745f" + "6c65646765725f686173685f6c656e5f746f6f5f6c6f6e67636865636b5f6b65796c65745f6f6f625f6c656e5f7533" + "32636865636b5f6b65796c65745f77726f6e675f6c656e5f753332666c6f61745f66726f6d5f75696e745f6c656e5f" + "6f6f62666c6f61745f66726f6d5f75696e745f77726f6e675f6c656e5f75696e7436346163636f756e745f6b65796c" + "65745f6c656e5f6f6f626163636f756e745f6b65796c65745f77726f6e675f6c656e6c696e655f6b65796c65745f6c" + "656e5f6f6f625f63757272656e63796c696e655f6b65796c65745f77726f6e675f6c656e5f63757272656e6379616d" + "6d5f6b65796c65745f6c656e5f6f6f625f617373657432616d6d5f6b65796c65745f6c656e5f77726f6e675f6c656e" + "5f617373657432616d6d5f6b65796c65745f6c656e5f77726f6e675f6e6f6e5f7872705f63757272656e63795f6c65" + "6e616d6d5f6b65796c65745f6c656e5f77726f6e675f7872705f63757272656e63795f6c656e616d6d5f6b65796c65" + "745f6d70746765745f74785f6669656c645f696e76616c69645f736669656c646765745f63757272656e745f6c6564" + "6765725f6f626a5f6669656c645f696e76616c69645f736669656c646765745f6c65646765725f6f626a5f6669656c" + "645f696e76616c69645f736669656c646765745f74785f6e65737465645f6669656c645f746f6f5f6269675f736c69" + "63656765745f63757272656e745f6c65646765725f6f626a5f6e65737465645f6669656c645f746f6f5f6269675f73" + "6c6963656765745f6c65646765725f6f626a5f6e65737465645f6669656c645f746f6f5f6269675f736c696365636f" + "6d707574655f7368613531325f68616c665f746f6f5f6269675f736c696365616d6d5f6b65796c65745f746f6f5f62" + "69675f736c69636563726564656e7469616c5f6b65796c65745f746f6f5f6269675f736c6963656d70746f6b656e5f" + "6b65796c65745f746f6f5f6269675f736c6963655f6d70746964666c6f61745f6164645f6f6f625f736c6963653166" + "6c6f61745f6164645f6f6f625f736c69636532666c6f61745f73756274726163745f6f6f625f736c69636531666c6f" + "61745f73756274726163745f6f6f625f736c69636532666c6f61745f6d756c7469706c795f6f6f625f736c69636531" + "666c6f61745f6d756c7469706c795f6f6f625f736c69636532666c6f61745f6469766964655f6f6f625f736c696365" + "31666c6f61745f6469766964655f6f6f625f736c69636532666c6f61745f726f6f745f6f6f625f736c696365666c6f" + "61745f706f775f6f6f625f736c696365666c6f61745f6c6f675f6f6f625f736c696365657363726f775f6b65796c65" + "745f77726f6e675f73697a655f75696e7433326d70745f69737375616e63655f6b65796c65745f77726f6e675f7369" + "7a655f75696e7433326e66745f6f666665725f6b65796c65745f77726f6e675f73697a655f75696e7433326f666665" + "725f6b65796c65745f77726f6e675f73697a655f75696e7433326f7261636c655f6b65796c65745f77726f6e675f73" + "697a655f75696e7433327061796368616e5f6b65796c65745f77726f6e675f73697a655f75696e7433327065726d69" + "7373696f6e65645f646f6d61696e5f6b65796c65745f77726f6e675f73697a655f75696e7433327469636b65745f6b" + "65796c65745f77726f6e675f73697a655f75696e7433327661756c745f6b65796c65745f77726f6e675f73697a655f" + "75696e7433326765745f6e66745f77726f6e675f73697a655f75696e743235366765745f6e66745f6973737565725f" + "77726f6e675f73697a655f75696e743235366765745f6e66745f7461786f6e5f77726f6e675f73697a655f75696e74" + "3235366765745f6e66745f73657269616c5f77726f6e675f73697a655f75696e743235366163636f756e745f6b6579" + "6c65745f77726f6e675f73697a655f6163636f756e745f6964636865636b5f6b65796c65745f77726f6e675f73697a" + "655f6163636f756e745f696463726564656e7469616c5f6b65796c65745f77726f6e675f73697a655f6163636f756e" + "745f69643163726564656e7469616c5f6b65796c65745f77726f6e675f73697a655f6163636f756e745f6964326465" + "6c65676174655f6b65796c65745f77726f6e675f73697a655f6163636f756e745f69643164656c65676174655f6b65" + "796c65745f77726f6e675f73697a655f6163636f756e745f6964326465706f7369745f707265617574685f6b65796c" + "65745f77726f6e675f73697a655f6163636f756e745f6964316465706f7369745f707265617574685f6b65796c6574" + "5f77726f6e675f73697a655f6163636f756e745f6964326469645f6b65796c65745f77726f6e675f73697a655f6163" + "636f756e745f6964657363726f775f6b65796c65745f77726f6e675f73697a655f6163636f756e745f69646c696e65" + "5f6b65796c65745f77726f6e675f73697a655f6163636f756e745f6964316c696e655f6b65796c65745f77726f6e67" + "5f73697a655f6163636f756e745f6964326d70745f69737375616e63655f6b65796c65745f77726f6e675f73697a65" + "5f6163636f756e745f69646d70746f6b656e5f6b65796c65745f77726f6e675f73697a655f6163636f756e745f6964" + "6e66745f6f666665725f6b65796c65745f77726f6e675f73697a655f6163636f756e745f69646f666665725f6b6579" + "6c65745f77726f6e675f73697a655f6163636f756e745f69646f7261636c655f6b65796c65745f77726f6e675f7369" + "7a655f6163636f756e745f69647061796368616e5f6b65796c65745f77726f6e675f73697a655f6163636f756e745f" + "6964317061796368616e5f6b65796c65745f77726f6e675f73697a655f6163636f756e745f6964327065726d697373" + "696f6e65645f646f6d61696e5f6b65796c65745f77726f6e675f73697a655f6163636f756e745f69647369676e6572" + "735f6b65796c65745f77726f6e675f73697a655f6163636f756e745f69647469636b65745f6b65796c65745f77726f" + "6e675f73697a655f6163636f756e745f69647661756c745f6b65796c65745f77726f6e675f73697a655f6163636f75" + "6e745f69646765745f6e66745f77726f6e675f73697a655f6163636f756e745f69646d70746f6b656e5f6b65796c65" + "745f6d707469645f77726f6e675f6c656e677468004d0970726f64756365727302086c616e67756167650104527573" + "74000c70726f6365737365642d6279010572757374631d312e38372e30202831373036376539616320323032352d30" + "352d303929002c0f7461726765745f6665617475726573022b0f6d757461626c652d676c6f62616c732b087369676e" + "2d657874"; + +extern std::string const floatTestsWasmHex = + "0061736d0100000001430860077f7f7f7f7f7f7f017f60057f7f7f7f7f017f60047f7f7f7f017f60067f7f7f7f7f7f" + "017f60047e7f7f7f017f60057f7e7f7f7f017f60037f7f7e017f6000017f02c9020e08686f73745f6c696205747261" + "6365000108686f73745f6c69620e666c6f61745f66726f6d5f696e74000408686f73745f6c69621274726163655f6f" + "70617175655f666c6f6174000208686f73745f6c69620f666c6f61745f66726f6d5f75696e74000108686f73745f6c" + "696209666c6f61745f736574000508686f73745f6c69620d666c6f61745f636f6d70617265000208686f73745f6c69" + "6209666c6f61745f616464000008686f73745f6c69620e666c6f61745f7375627472616374000008686f73745f6c69" + "620e666c6f61745f6d756c7469706c79000008686f73745f6c69620c666c6f61745f646976696465000008686f7374" + "5f6c696209666c6f61745f706f77000308686f73745f6c69620974726163655f6e756d000608686f73745f6c69620a" + "666c6f61745f726f6f74000308686f73745f6c696209666c6f61745f6c6f6700010302010705030100110619037f01" + "418080c0000b7f0041d28ac0000b7f0041e08ac0000b072e04066d656d6f727902000666696e697368000e0a5f5f64" + "6174615f656e6403010b5f5f686561705f6261736503020af20e01ef0e01047f230041206b22012400418080c00041" + "1d41014100410010001a200142003703100240428ce000200141106a22004108410010014108460440419d80c00041" + "172000410810021a41b480c000411e20004108410110001a0c010b41d280c000411e41014100410010001a0b200142" + "8ce0003703180240200141186a4108200141106a2200410841001003410846044041f080c00041172000410810021a" + "0c010b418781c000411e41014100410010001a0b0240410242fb00200141106a2200410841001004410846044041a5" + "81c00041212000410810021a0c010b41c681c000412641014100410010001a0b41ec81c0004115418182c000410810" + "021a419182c0004116418982c000410810021a41a782c000411b41014100410010001a200142003703180240420120" + "0141186a2200410841001001410846044041c282c000410f2000410810021a0c010b41d182c0004116410141004100" + "10001a0b0240200141186a4108418182c0004108100545044041e782c000411b41014100410010001a0c010b418283" + "c000411b41014100410010001a0b0240200141186a4108418982c000410810054101460440419d83c0004123410141" + "00410010001a0c010b41c083c000412441014100410010001a0b0240418982c0004108200141186a41081005410246" + "044041e483c000412341014100410010001a0c010b418784c000412441014100410010001a0b41ab84c00041204101" + "4100410010001a200142d487b6f4c7d4b1c000370310410921000340200141106a22024108418182c0004108200241" + "08410010061a200041016b22000d000b20014200370318420a200141186a22004108410010011a0240200041082002" + "4108100545044041cb84c000411441014100410010001a0c010b41df84c000411341014100410010001a0b410b2100" + "0340200141106a22024108418182c000410820024108410010071a200041016b22000d000b024020024108418982c0" + "004108100545044041f284c000411941014100410010001a0c010b418b85c000411841014100410010001a0b41a385" + "c000412341014100410010001a20014200370300420a20014108410010011a200142d487b6f4c7d4b1c00037030841" + "0621000340200141086a220241082001410820024108410010081a200041016b22000d000b2001420037031042c084" + "3d200141106a22004108410010011a02402000410820024108100545044041c685c000411941014100410010001a0c" + "010b41df85c000411841014100410010001a0b410721000340200141086a220241082001410820024108410010091a" + "200041016b22000d000b20014200370318417f4201200141186a22004108410010041a024020024108200041081005" + "45044041f785c000411741014100410010001a0c010b418e86c000411641014100410010001a0b41a486c000411741" + "014100410010001a20014200370308418182c00041084103200141086a220041084100100a1a41bb86c00041122000" + "410810021a418982c00041084106200041084100100a1a41cd86c00041182000410810021a20014200370310420920" + "0141106a22024108410010011a200241084102200041084100100a1a41e586c00041142000410810021a2002410841" + "00200041084100100a1a41f986c00041172000410810021a200142003703184200200141186a22034108410010011a" + "200341084102200041084100100a1a419087c00041142000410810021a41a487c00041382003410841002000410841" + "00100aac100b1a41dc87c000411841014100410010001a20014200370308420920004108410010011a200142003703" + "10200041084102200241084100100c1a41f487c00041122002410810021a200041084103200241084100100c1a4186" + "88c00041122002410810021a2001420037031842c0843d20034108410010011a200341084103200241084100100c1a" + "419888c00041182002410810021a200341084106200241084100100c1a41b088c000411c2002410810021a41cc88c0" + "00411741014100410010001a2001420037031042c0843d20024108410010011a200142003703182002410820034108" + "4100100d1a41e388c00041142003410810021a41f788c000411a41014100410010001a20014200370318418182c000" + "4108418982c000410820034108410010081a0240418982c0004108200341081005450440419189c000411641014100" + "410010001a0c010b41a789c000411541014100410010001a0b418982c0004108418982c0004108200141186a220041" + "08410010081a0240418182c000410820004108100545044041bc89c000411741014100410010001a0c010b41d389c0" + "00411641014100410010001a0b41e989c000411a41014100410010001a2001420037031020014200370318420a2001" + "41186a22024108410010011a418182c000410820024108200141106a22004108410010091a41838ac0004119200041" + "0810021a418182c00041082000410820004108410010091a419c8ac000410f2000410810021a024020024108200041" + "08100545044041ab8ac000411441014100410010001a0c010b41bf8ac000411341014100410010001a0b200141206a" + "240041010b0bdc0a0100418080c0000bd20a0a24242420746573745f666c6f61745f66726f6d5f7761736d20242424" + "2020666c6f61742066726f6d206936342031323330303a2020666c6f61742066726f6d206936342031323330302061" + "73204845583a2020666c6f61742066726f6d206936342031323330303a206661696c65642020666c6f61742066726f" + "6d207536342031323330303a2020666c6f61742066726f6d207536342031323330303a206661696c65642020666c6f" + "61742066726f6d2065787020322c206d616e7469737361203132333a2020666c6f61742066726f6d2065787020322c" + "206d616e746973736120333a206661696c65642020666c6f61742066726f6d20636f6e737420313ad4838d7ea4c680" + "0094838d7ea4c680002020666c6f61742066726f6d20636f6e7374202d313a0a24242420746573745f666c6f61745f" + "636f6d70617265202424242020666c6f61742066726f6d20313a2020666c6f61742066726f6d20313a206661696c65" + "642020666c6f61742066726f6d2031203d3d20464c4f41545f4f4e452020666c6f61742066726f6d203120213d2046" + "4c4f41545f4f4e452020666c6f61742066726f6d2031203e20464c4f41545f4e454741544956455f4f4e452020666c" + "6f61742066726f6d203120213e20464c4f41545f4e454741544956455f4f4e452020464c4f41545f4e454741544956" + "455f4f4e45203c20666c6f61742066726f6d20312020464c4f41545f4e454741544956455f4f4e4520213c20666c6f" + "61742066726f6d20310a24242420746573745f666c6f61745f6164645f737562747261637420242424202072657065" + "61746564206164643a20676f6f6420207265706561746564206164643a206261642020726570656174656420737562" + "74726163743a20676f6f64202072657065617465642073756274726163743a206261640a24242420746573745f666c" + "6f61745f6d756c7469706c795f6469766964652024242420207265706561746564206d756c7469706c793a20676f6f" + "6420207265706561746564206d756c7469706c793a2062616420207265706561746564206469766964653a20676f6f" + "6420207265706561746564206469766964653a206261640a24242420746573745f666c6f61745f706f772024242420" + "20666c6f61742063756265206f6620313a2020666c6f61742036746820706f776572206f66202d313a2020666c6f61" + "7420737175617265206f6620393a2020666c6f61742030746820706f776572206f6620393a2020666c6f6174207371" + "75617265206f6620303a2020666c6f61742030746820706f776572206f6620302028657870656374696e6720494e56" + "414c49445f504152414d53206572726f72293a0a24242420746573745f666c6f61745f726f6f74202424242020666c" + "6f61742073717274206f6620393a2020666c6f61742063627274206f6620393a2020666c6f61742063627274206f66" + "20313030303030303a2020666c6f61742036746820726f6f74206f6620313030303030303a0a24242420746573745f" + "666c6f61745f6c6f672024242420206c6f675f3130206f6620313030303030303a0a24242420746573745f666c6f61" + "745f6e65676174652024242420206e656761746520636f6e737420313a20676f6f6420206e656761746520636f6e73" + "7420313a2062616420206e656761746520636f6e7374202d313a20676f6f6420206e656761746520636f6e7374202d" + "313a206261640a24242420746573745f666c6f61745f696e76657274202424242020696e76657274206120666c6f61" + "742066726f6d2031303a2020696e7665727420616761696e3a2020696e766572742074776963653a20676f6f642020" + "696e766572742074776963653a20626164004d0970726f64756365727302086c616e6775616765010452757374000c" + "70726f6365737365642d6279010572757374631d312e38372e30202831373036376539616320323032352d30352d30" + "3929002c0f7461726765745f6665617475726573022b0f6d757461626c652d676c6f62616c732b087369676e2d6578" + "74"; + +extern std::string const float0Hex = + "0061736d0100000001290560057f7f7f7f7f017f60047e7f7f7f017f60077f7f7f7f7f7f7f017f60047f7f7f7f017f" + "6000017f025f0408686f73745f6c6962057472616365000008686f73745f6c69620e666c6f61745f66726f6d5f696e" + "74000108686f73745f6c69620e666c6f61745f7375627472616374000208686f73745f6c69620d666c6f61745f636f" + "6d7061726500030302010405030100110619037f01418080c0000b7f00419281c0000b7f0041a081c0000b072e0406" + "6d656d6f727902000666696e69736800040a5f5f646174615f656e6403010b5f5f686561705f6261736503020abe02" + "01bb0201017f23808080800041206b2200248080808000418080c0800041154100410041001080808080001a200042" + "003703084200200041086a410841001081808080001a20004200370310420a200041106a410841001081808080001a" + "200042003703180240200041106a4108200041106a4108200041186a410841001082808080004108460d00419580c0" + "800041154100410041001080808080001a0b02400240200041086a4108200041186a41081083808080000d0041aa80" + "c0800041174100410041001080808080001a0c010b41c180c0800041164100410041001080808080001a0b02400240" + "200041086a410841d780c0800041081083808080000d0041df80c08000411a4100410041001080808080001a0c010b" + "41f980c0800041194100410041001080808080001a0b200041206a24808080800041010b0b9c010100418080c0000b" + "92010a24242420746573745f666c6f61745f30202424242020666c6f61742031302d31303a206661696c6564202066" + "6c6f6174203020636f6d706172653a20676f6f642020666c6f6174203020636f6d706172653a206261648000000000" + "0000002020464c4f41545f5a45524f20636f6d706172653a20676f6f642020464c4f41545f5a45524f20636f6d7061" + "72653a20626164009502046e616d65001110666c6f61745f74657374732e7761736d01da0105002b5f5a4e38787270" + "6c5f73746434686f7374357472616365313768616338383262323664656162656436364501355f5a4e387872706c5f" + "73746434686f73743134666c6f61745f66726f6d5f696e74313768303234306638653361383964313939654502355f" + "5a4e387872706c5f73746434686f73743134666c6f61745f7375627472616374313768643634306331353233343534" + "323935634503345f5a4e387872706c5f73746434686f73743133666c6f61745f636f6d706172653137683663386465" + "656231323864393638386645040666696e697368071201000f5f5f737461636b5f706f696e746572090a0100072e72" + "6f64617461004d0970726f64756365727302086c616e6775616765010452757374000c70726f6365737365642d6279" + "010572757374631d312e38392e30202832393438333838336520323032352d30382d3034290094010f746172676574" + "5f6665617475726573082b0b62756c6b2d6d656d6f72792b0f62756c6b2d6d656d6f72792d6f70742b1663616c6c2d" + "696e6469726563742d6f7665726c6f6e672b0a6d756c746976616c75652b0f6d757461626c652d676c6f62616c732" + "b136e6f6e7472617070696e672d6670746f696e742b0f7265666572656e63652d74797065732b087369676e2d65787" + "4"; + +extern std::string const disabledFloatHex = + "0061736d010000000108026000006000017f03030200010503010002063e0a7f004180080b7f004180080b7f004180" + "100b7f004180100b7f00418090040b7f004180080b7f00418090040b7f00418080080b7f0041000b7f0041010b07b0" + "010d066d656d6f72790200115f5f7761736d5f63616c6c5f63746f727300000666696e69736800010362756603000c" + "5f5f64736f5f68616e646c6503010a5f5f646174615f656e6403020b5f5f737461636b5f6c6f7703030c5f5f737461" + "636b5f6869676803040d5f5f676c6f62616c5f6261736503050b5f5f686561705f6261736503060a5f5f686561705f" + "656e6403070d5f5f6d656d6f72795f6261736503080c5f5f7461626c655f6261736503090a150202000b1000430000" + "00c54300200045921a41010b"; + +extern std::string const memoryPointerAtLimitHex = + "0061736d010000000105016000017f030201000503010001070a010666696e69736800000a0e010c0041ffff032d00" + "001a41010b"; + +extern std::string const memoryPointerOverLimitHex = + "0061736d010000000105016000017f030201000503010001070a010666696e69736800000a0e010c00418080042d00" + "001a41010b"; + +extern std::string const memoryOffsetOverLimitHex = + "0061736d010000000105016000017f030201000503010001071302066d656d6f727902000666696e69736800000a0e" + "010c00410028028080041a41010b"; + +extern std::string const memoryEndOfWordOverLimitHex = + "0061736d010000000105016000017f030201000503010001071302066d656d6f727902000666696e69736800000a0e" + "010c0041feff032802001a41010b"; + +extern std::string const memoryGrow0To1PageHex = + "0061736d010000000105016000017f030201000503010000071302066d656d6f727902000666696e69736800000a0b" + "010900410140001a41010b"; + +extern std::string const memoryGrow1To0PageHex = + "0061736d010000000105016000017f030201000503010001071302066d656d6f727902000666696e69736800000a13" + "011100417f4000417f460440417f0f0b41010b"; + +extern std::string const memoryLastByteOf8MBHex = + "0061736d010000000105016000017f030201000506010180018001071302066d656d6f727902000666696e69736800" + "000a0f010d0041ffffff032d00001a41010b"; + +extern std::string const memoryGrow1MoreThan8MBHex = + "0061736d010000000105016000017f03020100050401008001071302066d656d6f727902000666696e69736800000a" + "1301110041014000417f460440417f0f0b41010b"; + +extern std::string const memoryGrow0MoreThan8MBHex = + "0061736d010000000105016000017f03020100050401008001071302066d656d6f727902000666696e69736800000a" + "1301110041004000417f460440417f0f0b41010b"; + +extern std::string const memoryInit1MoreThan8MBHex = + "0061736d010000000105016000017f030201000506010181018101071302066d656d6f727902000666696e69736800" + "000a0f010d0041ffffff032d00001a41010b"; + +extern std::string const memoryNegativeAddressHex = + "0061736d010000000105016000017f030201000506010180018001071302066d656d6f727902000666696e69736800" + "000a0c010a00417f2d00001a41010b"; + +extern std::string const table64ElementsHex = + "0061736d010000000108026000006000017f0303020001040401700040070a010666696e6973680001094601004100" + "0b40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000a090202000b040041010b"; + +extern std::string const table65ElementsHex = + "0061736d010000000108026000006000017f0303020001040401700041070a010666696e6973680001094701004100" + "0b41000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + "00000000000000000000000000000000000000000a090202000b040041010b"; + +extern std::string const table2TablesHex = + "0061736d010000000108026000006000017f03030200010409027001010170010101070a010666696e697368000109" + "0f020041000b0100020141000b0001000a090202000b040041010b"; + +extern std::string const table0ElementsHex = + "0061736d010000000105016000017f03020100040401700000070a010666696e69736800000a0601040041010b"; + +extern std::string const tableUintMaxHex = + "0061736d010000000105016000017f030201000408017000ffffffff0f070a010666696e69736800000a0601040041" + "010b"; + +extern std::string const proposalMutableGlobalHex = + "0061736d010000000105016000017f030201000606017f0141000b07140207636f756e74657203000666696e697368" + "00000a0d010b00230041016a240041010b"; + +extern std::string const proposalGcStructNewHex = + "0061736d01000000010b026000017f5f027f017f0103020100070a010666696e69736800000a0a010800fb01011a41" + "010b"; + +extern std::string const proposalMultiValueHex = + "0061736d010000000110036000027f7f6000017f60027f7f017f0303020001070a010666696e69736800010a140206" + "00410a41140b0b00100002026a411e460b0b"; + +extern std::string const proposalSignExtHex = + "0061736d010000000105016000017f03020100070a010666696e69736800000a0b01090041ff01c0417f460b"; + +extern std::string const proposalFloatToIntHex = + "0061736d010000000105016000017f03020100070a010666696e69736800000a1201100043f9021550fc0041ffffff" + "ff07460b"; + +extern std::string const proposalBulkMemoryHex = + "0061736d010000000105016000017f030201000503010001071302066d656d6f727902000666696e69736800000a1f" + "011d004100412a3a000041e40041004101fc0a000041e4002d0000412a460b"; + +extern std::string const proposalRefTypesHex = + "0061736d010000000105016000017f020f0103656e76057461626c65016f000103020100070a010666696e69736800" + "000a0c010a004100d06f260041010b"; + +extern std::string const proposalTailCallHex = + "0061736d010000000105016000017f0303020000070a010666696e69736800010a0b02040041010b040012000b"; + +extern std::string const proposalExtendedConstHex = + "0061736d010000000105016000017f030201000609017f00410a41206a0b070a010666696e69736800000a09010700" + "2300412a460b"; + +extern std::string const proposalMultiMemoryHex = + "0061736d010000000105016000017f0302010005050200000001070a010666696e69736800000a060104003f010b"; + +extern std::string const proposalCustomPageSizesHex = + "0061736d010000000105016000017f0302010005040108010a070a010666696e69736800000a0601040041010b0010" + "046e616d65010901000666696e697368"; + +extern std::string const proposalMemory64Hex = + "0061736d010000000105016000017f030201000503010401070a010666696e69736800000a10010e004200412a3a00" + "003f004201510b0010046e616d65010901000666696e697368"; + +extern std::string const proposalWideArithmeticHex = + "0061736d010000000105016000017f03020100070a010666696e69736800000a0e010c0042014202fc161a1a41010b" + "0010046e616d65010901000666696e697368"; + +extern std::string const trapDivideBy0Hex = + "0061736d010000000105016000017f03020100070a010666696e69736800000a0c010a00412a41006d1a41010b"; + +extern std::string const trapIntOverflowHex = + "0061736d010000000105016000017f03020100070a010666696e69736800000a0d010b00418080808078417f6d0b"; + +extern std::string const trapUnreachableHex = + "0061736d010000000105016000017f03020100070a010666696e69736800000a070105000041010b"; + +extern std::string const trapNullCallHex = + "0061736d010000000105016000017f03020100040401700001070a010666696e69736800000a090107004100110000" + "0b"; + +extern std::string const trapFuncSigMismatchHex = + "0061736d010000000108026000006000017f0303020001040401700001070a010666696e6973680001090701004100" + "0b01000a0d020300010b070041001101000b"; + +extern std::string const wasiGetTimeHex = + "0061736d01000000010c0260037f7e7f017f6000017f02290116776173695f736e617073686f745f70726576696577" + "310e636c6f636b5f74696d655f6765740000030201010503010001071302066d656d6f727902000666696e69736800" + "010a16011400410042e8074100100045047f410105417f0b0b"; + +extern std::string const wasiPrintHex = + "0061736d01000000010d0260047f7f7f7f017f6000017f02230116776173695f736e617073686f745f707265766965" + "77310866645f77726974650000030201010503010001071302066d656d6f727902000666696e69736800010a1d011b" + "01017f411821004101410041012000100045047f410105417f0b0b0b1e030041100b0648656c6c6f0a0041000b0410" + "0000000041040b0406000000"; + +// The following several wasm hex strings are for testing wasm section +// corruption cases. They are illegal hence do not have corresponding +// rust or wat sources. +// Wasm code magic number is "0061736d", and the only valid version is 1. + +extern std::string const badMagicNumberHex = "1061736d01000000"; +extern std::string const badVersionNumberHex = "0061736d02000000"; + +// Corruption Test: lyingHeader +// Scenario: A section declares it is 2GB long, but the file ends immediately. +// Attack: Buffer pre-allocation DoS (OOM). +// # Magic (00 61 73 6d) + Version (01 00 00 00) +// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00' +// # Type Section (ID 1) +// # Size: LEB128 encoded 2GB (0x80 0x80 0x80 0x80 0x08) +// data += b'\x01\x80\x80\x80\x80\x08' +extern std::string const lyingHeaderHex = "0061736d01000000018080808008"; + +// Corruption Test: neverEndingNumber +// Scenario: An LEB128 integer that never has a stop bit (byte < 0x80). +// Attack: Infinite loop in parser or read out of bounds. +// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00' +// # Type Section (ID 1), Size 5 +// data += b'\x01\x05' +// # Vector count: Infinite stream of 0x80 (100 bytes) +// data += b'\x80' * 100 +extern std::string const neverEndingNumberHex = + "0061736d01000000010580808080808080808080808080808080808080808080808080808080808080808080808080" + "8080808080808080808080808080808080808080808080808080808080808080808080808080808080808080808080" + "80808080808080808080808080808080"; + +// Corruption Test: vectorLie +// Scenario: A vector declares it has 4 billion items, but provides none. +// Attack: Vector pre-allocation DoS (OOM). +// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00' +// # Type Section (ID 1) +// # Size 5 (just enough for the count bytes) +// data += b'\x01\x05' +// # Vector Count: 0xFF 0xFF 0xFF 0xFF 0x0F (4,294,967,295 items) +// data += b'\xff\xff\xff\xff\x0f' +// # No actual items follow... +extern std::string const vectorLieHex = "0061736d010000000105ffffffff0f"; + +// Corruption Test: sectionOrdering +// Scenario: Sections appear out of order +// (Code section before Function section). +// Attack: Parser state confusion / potential null pointer deref. +// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00' +// # Code Section (ID 10) - usually last +// # Size 2, Count 0 +// data += b'\x0a\x02\x00\x0b' +// # Function Section (ID 3) - usually 3rd +// data += b'\x03\x02\x00\x00' +extern std::string const sectionOrderingHex = "0061736d010000000a02000b03020000"; + +// Corruption Test: ghostPayload +// Scenario: Valid headers, but file is truncated in the middle of a payload. +// Attack: Read out of bounds panic. +// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00' +// # Type Section (ID 1), Size 10 +// data += b'\x01\x0a' +// # Content: Count 1 +// data += b'\x01' +// # Start of a type definition (0x60 = func) +// data += b'\x60' +// # File ends abruptly here (missing params/results) +extern std::string const ghostPayloadHex = "0061736d01000000010a0160"; + +// Corruption Test: junkAfterSection +// Scenario: Section declares size X, but logical content finishes at X-5. +// Attack: Validation bypass if parser stops early, +// or panic if strict check missing. +// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00' +// # Type Section (ID 1), Size 10 bytes +// data += b'\x01\x0a' +// # Real content: Count 1, (func -> void) = 4 bytes +// # \x01 (count) \x60 (func) \x00 (0 params) \x00 (0 results) +// data += b'\x01\x60\x00\x00' +// # Remaining 6 bytes are junk padding within the section size +// data += b'\x00' * 6 +extern std::string const junkAfterSectionHex = "0061736d01000000010a01600000000000000000"; + +// Corruption Test: invalidSectionId +// Scenario: A section ID that doesn't exist (0xFF). +// Attack: Default case handling / unhandled enum variant. +// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00' +// # Section ID 0xFF, Size 1 +// data += b'\xff\x01\x00' +extern std::string const invalidSectionIdHex = "0061736d01000000ff0100"; + +// Corruption Test: localVariableBomb +// Scenario: A function declares 4 billion local variables. +// Attack: Stack Overflow / OOM during function init (memset). +// data = b'\x00\x61\x73\x6d\x01\x00\x00\x00' +// # 1. Type Section: (func) -> () +// data += b'\x01\x04\x01\x60\x00\x00' +// # 3. Function Section: 1 function of type 0 +// data += b'\x03\x02\x01\x00' +// # 10. Code Section +// # ID 10, Size 15 (estimated), Count 1 +// data += b'\x0a\x0f\x01' +// # Function Body Size: 13 bytes +// data += b'\x0d' +// # Local Declarations Count: 1 entry +// data += b'\x01' +// # The Bomb: 4,294,967,295 locals of type i32 +// # Count: 0xFF 0xFF 0xFF 0xFF 0x0F +// # Type: 0x7F (i32) +// data += b'\xff\xff\xff\xff\x0f\x7f' +// # Instruction: end (0x0b) +// data += b'\x0b' +extern std::string const localVariableBombHex = + "0061736d01000000010401600000030201000a0f010d01ffffffff0f7f0b"; + +extern std::string const infiniteLoopWasmHex = + "0061736d010000000108026000006000017f030302000105030100020638097f004180080b7f004180080b7f004180" + "080b7f00418088040b7f004180080b7f00418088040b7f00418080080b7f0041000b7f0041010b07a8010c066d656d" + "6f72790200115f5f7761736d5f63616c6c5f63746f72730000046c6f6f7000010c5f5f64736f5f68616e646c650300" + "0a5f5f646174615f656e6403010b5f5f737461636b5f6c6f7703020c5f5f737461636b5f6869676803030d5f5f676c" + "6f62616c5f6261736503040b5f5f686561705f6261736503050a5f5f686561705f656e6403060d5f5f6d656d6f7279" + "5f6261736503070c5f5f7461626c655f6261736503080a270202000b220041fc87044100360200034041fc870441fc" + "870428020041016a3602000c000b000b007f0970726f647563657273010c70726f6365737365642d62790105636c61" + "6e675f31392e312e352d776173692d73646b202868747470733a2f2f6769746875622e636f6d2f6c6c766d2f6c6c76" + "6d2d70726f6a6563742061623462356132646235383239353861663165653330386137393063666462343262643234" + "3732302900490f7461726765745f6665617475726573042b0f6d757461626c652d676c6f62616c732b087369676e2d" + "6578742b0f7265666572656e63652d74797065732b0a6d756c746976616c7565"; +extern std::string const startLoopHex = + "0061736d010000000108026000006000017f030302000107120205737461727400000666696e69736800010801000a" + "0e02070003400c000b0b040041010b"; + +extern std::string const badAlignWasmHex = + "0061736d01000000011b046000017f60057f7f7f7f7f017f60067f7f7f7f7f7f017f600000022a0203656e760f666c" + "6f61745f66726f6d5f75696e74000103656e760c636865636b5f6b65796c6574000203050403000000050301000306" + "470b7f004180080b7f00418088020b7f004180080b7f00418088040b7f00418088040b7f00418088080b7f00418008" + "0b7f00418088080b7f004180800c0b7f0041000b7f0041010b07cc0110066d656d6f72790200115f5f7761736d5f63" + "616c6c5f63746f72730002057465737431000307655f64617461310300057465737432000407655f64617461320301" + "047465737400050c5f5f64736f5f68616e646c6503020a5f5f646174615f656e6403030b5f5f737461636b5f6c6f77" + "03040c5f5f737461636b5f6869676803050d5f5f676c6f62616c5f6261736503060b5f5f686561705f626173650307" + "0a5f5f686561705f656e6403080d5f5f6d656d6f72795f6261736503090c5f5f7461626c655f62617365030a0a9902" + "0402000b2801017f418108427f370000418108410841a308410c41001000220041a40828020020004100481b0b5f01" + "017f419a88024191a4cca00136010041928802428994ace0d0c1c38710370100418a88024281848ca0d0c0c1830837" + "010041818802417f360000418a8802411441818802410441a3880241201001220041a4880228020020004100481b0b" + "8a0101037f418108427f370000418108410841a308410c410010002100419a88024191a4cca0013601004192880242" + "8994ace0d0c1c38710370100418a88024281848ca0d0c0c1830837010041818802417f36000041a408280200210141" + "8a8802411441818802410441a3880241201001220241a4880228020020024100481b2000200120004100481b6a0b00" + "7f0970726f647563657273010c70726f6365737365642d62790105636c616e675f31392e312e352d776173692d7364" + "6b202868747470733a2f2f6769746875622e636f6d2f6c6c766d2f6c6c766d2d70726f6a6563742061623462356132" + "6462353832393538616631656533303861373930636664623432626432343732302900490f7461726765745f666561" + "7475726573042b0f6d757461626c652d676c6f62616c732b087369676e2d6578742b0f7265666572656e63652d7479" + "7065732b0a6d756c746976616c7565"; + +extern std::string const thousandParamsHex = + "0061736d0100000001f1070260000060e8077f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f017f030302000105030100020638097f" + "004180080b7f004180080b7f004180080b7f00418088040b7f004180080b7f00418088040b7f00418080080b7f0041" + "000b7f0041010b07a8010c066d656d6f72790200115f5f7761736d5f63616c6c5f63746f7273000004746573740001" + "0c5f5f64736f5f68616e646c6503000a5f5f646174615f656e6403010b5f5f737461636b5f6c6f7703020c5f5f7374" + "61636b5f6869676803030d5f5f676c6f62616c5f6261736503040b5f5f686561705f6261736503050a5f5f68656170" + "5f656e6403060d5f5f6d656d6f72795f6261736503070c5f5f7461626c655f6261736503080aa71e0202000ba11e00" + "200020016a20026a20036a20046a20056a20066a20076a20086a20096a200a6a200b6a200c6a200d6a200e6a200f6a" + "20106a20116a20126a20136a20146a20156a20166a20176a20186a20196a201a6a201b6a201c6a201d6a201e6a201f" + "6a20206a20216a20226a20236a20246a20256a20266a20276a20286a20296a202a6a202b6a202c6a202d6a202e6a20" + "2f6a20306a20316a20326a20336a20346a20356a20366a20376a20386a20396a203a6a203b6a203c6a203d6a203e6a" + "203f6a20406a20416a20426a20436a20446a20456a20466a20476a20486a20496a204a6a204b6a204c6a204d6a204e" + "6a204f6a20506a20516a20526a20536a20546a20556a20566a20576a20586a20596a205a6a205b6a205c6a205d6a20" + "5e6a205f6a20606a20616a20626a20636a20646a20656a20666a20676a20686a20696a206a6a206b6a206c6a206d6a" + "206e6a206f6a20706a20716a20726a20736a20746a20756a20766a20776a20786a20796a207a6a207b6a207c6a207d" + "6a207e6a207f6a2080016a2081016a2082016a2083016a2084016a2085016a2086016a2087016a2088016a2089016a" + "208a016a208b016a208c016a208d016a208e016a208f016a2090016a2091016a2092016a2093016a2094016a209501" + "6a2096016a2097016a2098016a2099016a209a016a209b016a209c016a209d016a209e016a209f016a20a0016a20a1" + "016a20a2016a20a3016a20a4016a20a5016a20a6016a20a7016a20a8016a20a9016a20aa016a20ab016a20ac016a20" + "ad016a20ae016a20af016a20b0016a20b1016a20b2016a20b3016a20b4016a20b5016a20b6016a20b7016a20b8016a" + "20b9016a20ba016a20bb016a20bc016a20bd016a20be016a20bf016a20c0016a20c1016a20c2016a20c3016a20c401" + "6a20c5016a20c6016a20c7016a20c8016a20c9016a20ca016a20cb016a20cc016a20cd016a20ce016a20cf016a20d0" + "016a20d1016a20d2016a20d3016a20d4016a20d5016a20d6016a20d7016a20d8016a20d9016a20da016a20db016a20" + "dc016a20dd016a20de016a20df016a20e0016a20e1016a20e2016a20e3016a20e4016a20e5016a20e6016a20e7016a" + "20e8016a20e9016a20ea016a20eb016a20ec016a20ed016a20ee016a20ef016a20f0016a20f1016a20f2016a20f301" + "6a20f4016a20f5016a20f6016a20f7016a20f8016a20f9016a20fa016a20fb016a20fc016a20fd016a20fe016a20ff" + "016a2080026a2081026a2082026a2083026a2084026a2085026a2086026a2087026a2088026a2089026a208a026a20" + "8b026a208c026a208d026a208e026a208f026a2090026a2091026a2092026a2093026a2094026a2095026a2096026a" + "2097026a2098026a2099026a209a026a209b026a209c026a209d026a209e026a209f026a20a0026a20a1026a20a202" + "6a20a3026a20a4026a20a5026a20a6026a20a7026a20a8026a20a9026a20aa026a20ab026a20ac026a20ad026a20ae" + "026a20af026a20b0026a20b1026a20b2026a20b3026a20b4026a20b5026a20b6026a20b7026a20b8026a20b9026a20" + "ba026a20bb026a20bc026a20bd026a20be026a20bf026a20c0026a20c1026a20c2026a20c3026a20c4026a20c5026a" + "20c6026a20c7026a20c8026a20c9026a20ca026a20cb026a20cc026a20cd026a20ce026a20cf026a20d0026a20d102" + "6a20d2026a20d3026a20d4026a20d5026a20d6026a20d7026a20d8026a20d9026a20da026a20db026a20dc026a20dd" + "026a20de026a20df026a20e0026a20e1026a20e2026a20e3026a20e4026a20e5026a20e6026a20e7026a20e8026a20" + "e9026a20ea026a20eb026a20ec026a20ed026a20ee026a20ef026a20f0026a20f1026a20f2026a20f3026a20f4026a" + "20f5026a20f6026a20f7026a20f8026a20f9026a20fa026a20fb026a20fc026a20fd026a20fe026a20ff026a208003" + "6a2081036a2082036a2083036a2084036a2085036a2086036a2087036a2088036a2089036a208a036a208b036a208c" + "036a208d036a208e036a208f036a2090036a2091036a2092036a2093036a2094036a2095036a2096036a2097036a20" + "98036a2099036a209a036a209b036a209c036a209d036a209e036a209f036a20a0036a20a1036a20a2036a20a3036a" + "20a4036a20a5036a20a6036a20a7036a20a8036a20a9036a20aa036a20ab036a20ac036a20ad036a20ae036a20af03" + "6a20b0036a20b1036a20b2036a20b3036a20b4036a20b5036a20b6036a20b7036a20b8036a20b9036a20ba036a20bb" + "036a20bc036a20bd036a20be036a20bf036a20c0036a20c1036a20c2036a20c3036a20c4036a20c5036a20c6036a20" + "c7036a20c8036a20c9036a20ca036a20cb036a20cc036a20cd036a20ce036a20cf036a20d0036a20d1036a20d2036a" + "20d3036a20d4036a20d5036a20d6036a20d7036a20d8036a20d9036a20da036a20db036a20dc036a20dd036a20de03" + "6a20df036a20e0036a20e1036a20e2036a20e3036a20e4036a20e5036a20e6036a20e7036a20e8036a20e9036a20ea" + "036a20eb036a20ec036a20ed036a20ee036a20ef036a20f0036a20f1036a20f2036a20f3036a20f4036a20f5036a20" + "f6036a20f7036a20f8036a20f9036a20fa036a20fb036a20fc036a20fd036a20fe036a20ff036a2080046a2081046a" + "2082046a2083046a2084046a2085046a2086046a2087046a2088046a2089046a208a046a208b046a208c046a208d04" + "6a208e046a208f046a2090046a2091046a2092046a2093046a2094046a2095046a2096046a2097046a2098046a2099" + "046a209a046a209b046a209c046a209d046a209e046a209f046a20a0046a20a1046a20a2046a20a3046a20a4046a20" + "a5046a20a6046a20a7046a20a8046a20a9046a20aa046a20ab046a20ac046a20ad046a20ae046a20af046a20b0046a" + "20b1046a20b2046a20b3046a20b4046a20b5046a20b6046a20b7046a20b8046a20b9046a20ba046a20bb046a20bc04" + "6a20bd046a20be046a20bf046a20c0046a20c1046a20c2046a20c3046a20c4046a20c5046a20c6046a20c7046a20c8" + "046a20c9046a20ca046a20cb046a20cc046a20cd046a20ce046a20cf046a20d0046a20d1046a20d2046a20d3046a20" + "d4046a20d5046a20d6046a20d7046a20d8046a20d9046a20da046a20db046a20dc046a20dd046a20de046a20df046a" + "20e0046a20e1046a20e2046a20e3046a20e4046a20e5046a20e6046a20e7046a20e8046a20e9046a20ea046a20eb04" + "6a20ec046a20ed046a20ee046a20ef046a20f0046a20f1046a20f2046a20f3046a20f4046a20f5046a20f6046a20f7" + "046a20f8046a20f9046a20fa046a20fb046a20fc046a20fd046a20fe046a20ff046a2080056a2081056a2082056a20" + "83056a2084056a2085056a2086056a2087056a2088056a2089056a208a056a208b056a208c056a208d056a208e056a" + "208f056a2090056a2091056a2092056a2093056a2094056a2095056a2096056a2097056a2098056a2099056a209a05" + "6a209b056a209c056a209d056a209e056a209f056a20a0056a20a1056a20a2056a20a3056a20a4056a20a5056a20a6" + "056a20a7056a20a8056a20a9056a20aa056a20ab056a20ac056a20ad056a20ae056a20af056a20b0056a20b1056a20" + "b2056a20b3056a20b4056a20b5056a20b6056a20b7056a20b8056a20b9056a20ba056a20bb056a20bc056a20bd056a" + "20be056a20bf056a20c0056a20c1056a20c2056a20c3056a20c4056a20c5056a20c6056a20c7056a20c8056a20c905" + "6a20ca056a20cb056a20cc056a20cd056a20ce056a20cf056a20d0056a20d1056a20d2056a20d3056a20d4056a20d5" + "056a20d6056a20d7056a20d8056a20d9056a20da056a20db056a20dc056a20dd056a20de056a20df056a20e0056a20" + "e1056a20e2056a20e3056a20e4056a20e5056a20e6056a20e7056a20e8056a20e9056a20ea056a20eb056a20ec056a" + "20ed056a20ee056a20ef056a20f0056a20f1056a20f2056a20f3056a20f4056a20f5056a20f6056a20f7056a20f805" + "6a20f9056a20fa056a20fb056a20fc056a20fd056a20fe056a20ff056a2080066a2081066a2082066a2083066a2084" + "066a2085066a2086066a2087066a2088066a2089066a208a066a208b066a208c066a208d066a208e066a208f066a20" + "90066a2091066a2092066a2093066a2094066a2095066a2096066a2097066a2098066a2099066a209a066a209b066a" + "209c066a209d066a209e066a209f066a20a0066a20a1066a20a2066a20a3066a20a4066a20a5066a20a6066a20a706" + "6a20a8066a20a9066a20aa066a20ab066a20ac066a20ad066a20ae066a20af066a20b0066a20b1066a20b2066a20b3" + "066a20b4066a20b5066a20b6066a20b7066a20b8066a20b9066a20ba066a20bb066a20bc066a20bd066a20be066a20" + "bf066a20c0066a20c1066a20c2066a20c3066a20c4066a20c5066a20c6066a20c7066a20c8066a20c9066a20ca066a" + "20cb066a20cc066a20cd066a20ce066a20cf066a20d0066a20d1066a20d2066a20d3066a20d4066a20d5066a20d606" + "6a20d7066a20d8066a20d9066a20da066a20db066a20dc066a20dd066a20de066a20df066a20e0066a20e1066a20e2" + "066a20e3066a20e4066a20e5066a20e6066a20e7066a20e8066a20e9066a20ea066a20eb066a20ec066a20ed066a20" + "ee066a20ef066a20f0066a20f1066a20f2066a20f3066a20f4066a20f5066a20f6066a20f7066a20f8066a20f9066a" + "20fa066a20fb066a20fc066a20fd066a20fe066a20ff066a2080076a2081076a2082076a2083076a2084076a208507" + "6a2086076a2087076a2088076a2089076a208a076a208b076a208c076a208d076a208e076a208f076a2090076a2091" + "076a2092076a2093076a2094076a2095076a2096076a2097076a2098076a2099076a209a076a209b076a209c076a20" + "9d076a209e076a209f076a20a0076a20a1076a20a2076a20a3076a20a4076a20a5076a20a6076a20a7076a20a8076a" + "20a9076a20aa076a20ab076a20ac076a20ad076a20ae076a20af076a20b0076a20b1076a20b2076a20b3076a20b407" + "6a20b5076a20b6076a20b7076a20b8076a20b9076a20ba076a20bb076a20bc076a20bd076a20be076a20bf076a20c0" + "076a20c1076a20c2076a20c3076a20c4076a20c5076a20c6076a20c7076a20c8076a20c9076a20ca076a20cb076a20" + "cc076a20cd076a20ce076a20cf076a20d0076a20d1076a20d2076a20d3076a20d4076a20d5076a20d6076a20d7076a" + "20d8076a20d9076a20da076a20db076a20dc076a20dd076a20de076a20df076a20e0076a20e1076a20e2076a20e307" + "6a20e4076a20e5076a20e6076a20e7076a0b007f0970726f647563657273010c70726f6365737365642d6279010563" + "6c616e675f31392e312e352d776173692d73646b202868747470733a2f2f6769746875622e636f6d2f6c6c766d2f6c" + "6c766d2d70726f6a656374206162346235613264623538323935386166316565333038613739306366646234326264" + "32343732302900490f7461726765745f6665617475726573042b0f6d757461626c652d676c6f62616c732b08736967" + "6e2d6578742b0f7265666572656e63652d74797065732b0a6d756c746976616c7565"; + +extern std::string const thousand1ParamsHex = + "0061736d0100000001f2070260000060e9077f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f" + "7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f017f03030200010503010002063809" + "7f004180080b7f004180080b7f004180080b7f00418088040b7f004180080b7f00418088040b7f00418080080b7f00" + "41000b7f0041010b07a8010c066d656d6f72790200115f5f7761736d5f63616c6c5f63746f72730000047465737400" + "010c5f5f64736f5f68616e646c6503000a5f5f646174615f656e6403010b5f5f737461636b5f6c6f7703020c5f5f73" + "7461636b5f6869676803030d5f5f676c6f62616c5f6261736503040b5f5f686561705f6261736503050a5f5f686561" + "705f656e6403060d5f5f6d656d6f72795f6261736503070c5f5f7461626c655f6261736503080aab1e0202000ba51e" + "00200020016a20026a20036a20046a20056a20066a20076a20086a20096a200a6a200b6a200c6a200d6a200e6a200f" + "6a20106a20116a20126a20136a20146a20156a20166a20176a20186a20196a201a6a201b6a201c6a201d6a201e6a20" + "1f6a20206a20216a20226a20236a20246a20256a20266a20276a20286a20296a202a6a202b6a202c6a202d6a202e6a" + "202f6a20306a20316a20326a20336a20346a20356a20366a20376a20386a20396a203a6a203b6a203c6a203d6a203e" + "6a203f6a20406a20416a20426a20436a20446a20456a20466a20476a20486a20496a204a6a204b6a204c6a204d6a20" + "4e6a204f6a20506a20516a20526a20536a20546a20556a20566a20576a20586a20596a205a6a205b6a205c6a205d6a" + "205e6a205f6a20606a20616a20626a20636a20646a20656a20666a20676a20686a20696a206a6a206b6a206c6a206d" + "6a206e6a206f6a20706a20716a20726a20736a20746a20756a20766a20776a20786a20796a207a6a207b6a207c6a20" + "7d6a207e6a207f6a2080016a2081016a2082016a2083016a2084016a2085016a2086016a2087016a2088016a208901" + "6a208a016a208b016a208c016a208d016a208e016a208f016a2090016a2091016a2092016a2093016a2094016a2095" + "016a2096016a2097016a2098016a2099016a209a016a209b016a209c016a209d016a209e016a209f016a20a0016a20" + "a1016a20a2016a20a3016a20a4016a20a5016a20a6016a20a7016a20a8016a20a9016a20aa016a20ab016a20ac016a" + "20ad016a20ae016a20af016a20b0016a20b1016a20b2016a20b3016a20b4016a20b5016a20b6016a20b7016a20b801" + "6a20b9016a20ba016a20bb016a20bc016a20bd016a20be016a20bf016a20c0016a20c1016a20c2016a20c3016a20c4" + "016a20c5016a20c6016a20c7016a20c8016a20c9016a20ca016a20cb016a20cc016a20cd016a20ce016a20cf016a20" + "d0016a20d1016a20d2016a20d3016a20d4016a20d5016a20d6016a20d7016a20d8016a20d9016a20da016a20db016a" + "20dc016a20dd016a20de016a20df016a20e0016a20e1016a20e2016a20e3016a20e4016a20e5016a20e6016a20e701" + "6a20e8016a20e9016a20ea016a20eb016a20ec016a20ed016a20ee016a20ef016a20f0016a20f1016a20f2016a20f3" + "016a20f4016a20f5016a20f6016a20f7016a20f8016a20f9016a20fa016a20fb016a20fc016a20fd016a20fe016a20" + "ff016a2080026a2081026a2082026a2083026a2084026a2085026a2086026a2087026a2088026a2089026a208a026a" + "208b026a208c026a208d026a208e026a208f026a2090026a2091026a2092026a2093026a2094026a2095026a209602" + "6a2097026a2098026a2099026a209a026a209b026a209c026a209d026a209e026a209f026a20a0026a20a1026a20a2" + "026a20a3026a20a4026a20a5026a20a6026a20a7026a20a8026a20a9026a20aa026a20ab026a20ac026a20ad026a20" + "ae026a20af026a20b0026a20b1026a20b2026a20b3026a20b4026a20b5026a20b6026a20b7026a20b8026a20b9026a" + "20ba026a20bb026a20bc026a20bd026a20be026a20bf026a20c0026a20c1026a20c2026a20c3026a20c4026a20c502" + "6a20c6026a20c7026a20c8026a20c9026a20ca026a20cb026a20cc026a20cd026a20ce026a20cf026a20d0026a20d1" + "026a20d2026a20d3026a20d4026a20d5026a20d6026a20d7026a20d8026a20d9026a20da026a20db026a20dc026a20" + "dd026a20de026a20df026a20e0026a20e1026a20e2026a20e3026a20e4026a20e5026a20e6026a20e7026a20e8026a" + "20e9026a20ea026a20eb026a20ec026a20ed026a20ee026a20ef026a20f0026a20f1026a20f2026a20f3026a20f402" + "6a20f5026a20f6026a20f7026a20f8026a20f9026a20fa026a20fb026a20fc026a20fd026a20fe026a20ff026a2080" + "036a2081036a2082036a2083036a2084036a2085036a2086036a2087036a2088036a2089036a208a036a208b036a20" + "8c036a208d036a208e036a208f036a2090036a2091036a2092036a2093036a2094036a2095036a2096036a2097036a" + "2098036a2099036a209a036a209b036a209c036a209d036a209e036a209f036a20a0036a20a1036a20a2036a20a303" + "6a20a4036a20a5036a20a6036a20a7036a20a8036a20a9036a20aa036a20ab036a20ac036a20ad036a20ae036a20af" + "036a20b0036a20b1036a20b2036a20b3036a20b4036a20b5036a20b6036a20b7036a20b8036a20b9036a20ba036a20" + "bb036a20bc036a20bd036a20be036a20bf036a20c0036a20c1036a20c2036a20c3036a20c4036a20c5036a20c6036a" + "20c7036a20c8036a20c9036a20ca036a20cb036a20cc036a20cd036a20ce036a20cf036a20d0036a20d1036a20d203" + "6a20d3036a20d4036a20d5036a20d6036a20d7036a20d8036a20d9036a20da036a20db036a20dc036a20dd036a20de" + "036a20df036a20e0036a20e1036a20e2036a20e3036a20e4036a20e5036a20e6036a20e7036a20e8036a20e9036a20" + "ea036a20eb036a20ec036a20ed036a20ee036a20ef036a20f0036a20f1036a20f2036a20f3036a20f4036a20f5036a" + "20f6036a20f7036a20f8036a20f9036a20fa036a20fb036a20fc036a20fd036a20fe036a20ff036a2080046a208104" + "6a2082046a2083046a2084046a2085046a2086046a2087046a2088046a2089046a208a046a208b046a208c046a208d" + "046a208e046a208f046a2090046a2091046a2092046a2093046a2094046a2095046a2096046a2097046a2098046a20" + "99046a209a046a209b046a209c046a209d046a209e046a209f046a20a0046a20a1046a20a2046a20a3046a20a4046a" + "20a5046a20a6046a20a7046a20a8046a20a9046a20aa046a20ab046a20ac046a20ad046a20ae046a20af046a20b004" + "6a20b1046a20b2046a20b3046a20b4046a20b5046a20b6046a20b7046a20b8046a20b9046a20ba046a20bb046a20bc" + "046a20bd046a20be046a20bf046a20c0046a20c1046a20c2046a20c3046a20c4046a20c5046a20c6046a20c7046a20" + "c8046a20c9046a20ca046a20cb046a20cc046a20cd046a20ce046a20cf046a20d0046a20d1046a20d2046a20d3046a" + "20d4046a20d5046a20d6046a20d7046a20d8046a20d9046a20da046a20db046a20dc046a20dd046a20de046a20df04" + "6a20e0046a20e1046a20e2046a20e3046a20e4046a20e5046a20e6046a20e7046a20e8046a20e9046a20ea046a20eb" + "046a20ec046a20ed046a20ee046a20ef046a20f0046a20f1046a20f2046a20f3046a20f4046a20f5046a20f6046a20" + "f7046a20f8046a20f9046a20fa046a20fb046a20fc046a20fd046a20fe046a20ff046a2080056a2081056a2082056a" + "2083056a2084056a2085056a2086056a2087056a2088056a2089056a208a056a208b056a208c056a208d056a208e05" + "6a208f056a2090056a2091056a2092056a2093056a2094056a2095056a2096056a2097056a2098056a2099056a209a" + "056a209b056a209c056a209d056a209e056a209f056a20a0056a20a1056a20a2056a20a3056a20a4056a20a5056a20" + "a6056a20a7056a20a8056a20a9056a20aa056a20ab056a20ac056a20ad056a20ae056a20af056a20b0056a20b1056a" + "20b2056a20b3056a20b4056a20b5056a20b6056a20b7056a20b8056a20b9056a20ba056a20bb056a20bc056a20bd05" + "6a20be056a20bf056a20c0056a20c1056a20c2056a20c3056a20c4056a20c5056a20c6056a20c7056a20c8056a20c9" + "056a20ca056a20cb056a20cc056a20cd056a20ce056a20cf056a20d0056a20d1056a20d2056a20d3056a20d4056a20" + "d5056a20d6056a20d7056a20d8056a20d9056a20da056a20db056a20dc056a20dd056a20de056a20df056a20e0056a" + "20e1056a20e2056a20e3056a20e4056a20e5056a20e6056a20e7056a20e8056a20e9056a20ea056a20eb056a20ec05" + "6a20ed056a20ee056a20ef056a20f0056a20f1056a20f2056a20f3056a20f4056a20f5056a20f6056a20f7056a20f8" + "056a20f9056a20fa056a20fb056a20fc056a20fd056a20fe056a20ff056a2080066a2081066a2082066a2083066a20" + "84066a2085066a2086066a2087066a2088066a2089066a208a066a208b066a208c066a208d066a208e066a208f066a" + "2090066a2091066a2092066a2093066a2094066a2095066a2096066a2097066a2098066a2099066a209a066a209b06" + "6a209c066a209d066a209e066a209f066a20a0066a20a1066a20a2066a20a3066a20a4066a20a5066a20a6066a20a7" + "066a20a8066a20a9066a20aa066a20ab066a20ac066a20ad066a20ae066a20af066a20b0066a20b1066a20b2066a20" + "b3066a20b4066a20b5066a20b6066a20b7066a20b8066a20b9066a20ba066a20bb066a20bc066a20bd066a20be066a" + "20bf066a20c0066a20c1066a20c2066a20c3066a20c4066a20c5066a20c6066a20c7066a20c8066a20c9066a20ca06" + "6a20cb066a20cc066a20cd066a20ce066a20cf066a20d0066a20d1066a20d2066a20d3066a20d4066a20d5066a20d6" + "066a20d7066a20d8066a20d9066a20da066a20db066a20dc066a20dd066a20de066a20df066a20e0066a20e1066a20" + "e2066a20e3066a20e4066a20e5066a20e6066a20e7066a20e8066a20e9066a20ea066a20eb066a20ec066a20ed066a" + "20ee066a20ef066a20f0066a20f1066a20f2066a20f3066a20f4066a20f5066a20f6066a20f7066a20f8066a20f906" + "6a20fa066a20fb066a20fc066a20fd066a20fe066a20ff066a2080076a2081076a2082076a2083076a2084076a2085" + "076a2086076a2087076a2088076a2089076a208a076a208b076a208c076a208d076a208e076a208f076a2090076a20" + "91076a2092076a2093076a2094076a2095076a2096076a2097076a2098076a2099076a209a076a209b076a209c076a" + "209d076a209e076a209f076a20a0076a20a1076a20a2076a20a3076a20a4076a20a5076a20a6076a20a7076a20a807" + "6a20a9076a20aa076a20ab076a20ac076a20ad076a20ae076a20af076a20b0076a20b1076a20b2076a20b3076a20b4" + "076a20b5076a20b6076a20b7076a20b8076a20b9076a20ba076a20bb076a20bc076a20bd076a20be076a20bf076a20" + "c0076a20c1076a20c2076a20c3076a20c4076a20c5076a20c6076a20c7076a20c8076a20c9076a20ca076a20cb076a" + "20cc076a20cd076a20ce076a20cf076a20d0076a20d1076a20d2076a20d3076a20d4076a20d5076a20d6076a20d707" + "6a20d8076a20d9076a20da076a20db076a20dc076a20dd076a20de076a20df076a20e0076a20e1076a20e2076a20e3" + "076a20e4076a20e5076a20e6076a20e7076a20e8076a0b007f0970726f647563657273010c70726f6365737365642d" + "62790105636c616e675f31392e312e352d776173692d73646b202868747470733a2f2f6769746875622e636f6d2f6c" + "6c766d2f6c6c766d2d70726f6a65637420616234623561326462353832393538616631656533303861373930636664" + "623432626432343732302900490f7461726765745f6665617475726573042b0f6d757461626c652d676c6f62616c73" + "2b087369676e2d6578742b0f7265666572656e63652d74797065732b0a6d756c746976616c7565"; + +extern std::string const opcReservedHex = + "0061736d010000000105016000017f03030200000404017000010503010001060b027f0141000b7e0142000b071401" + "10616c6c5f696e737472756374696f6e7300010907010041000b01000a53020400412a0b4c02017f017e0101010101" + "0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101" + "01010101010101010101010101010101410b0b0b0a010041000b0474657374"; + +extern std::string const impExpHex = + "0061736d0100000001100360027f7f017f6000017f60017f017f02330203656e760e6765745f6c65646765725f7371" + "6e000003656e76166765745f706172656e745f6c65646765725f686173680000030403010201050301000107310406" + "6d656d6f72790200096578705f66756e63310002096578705f66756e633200030c746573745f696d706f7274730004" + "0a2b03040041010b0700200041026c0b1c01027f4120410410001a41202802002100410041201001210120000b"; + +extern std::string const updateDataWasmHex = + "0061736d01000000010e0360027f7f017f6000006000017f02130103656e760b7570646174" + "655f64617461000003030201020503010002063f0a7f01419088040b7f004180080b7f0041" + "85080b7f004190080b7f00419088040b7f004180080b7f00419088040b7f00418080080b7f" + "0041000b7f0041010b07aa010c066d656d6f72790200115f5f7761736d5f63616c6c5f6374" + "6f727300010666696e69736800020c5f5f64736f5f68616e646c6503010a5f5f646174615f" + "656e6403020b5f5f737461636b5f6c6f7703030c5f5f737461636b5f6869676803040d5f5f" + "676c6f62616c5f6261736503050b5f5f686561705f6261736503060a5f5f686561705f656e" + "6403070d5f5f6d656d6f72795f6261736503080c5f5f7461626c655f6261736503090a3f02" + "02000b3a01017f230041106b220024002000410c6a4184082d00003a000020004180082800" + "00360208200041086a410410001a200041106a240041807e0b0b0b01004180080b04446174" + "61007f0970726f647563657273010c70726f6365737365642d62790105636c616e675f3139" + "2e312e352d776173692d73646b202868747470733a2f2f6769746875622e636f6d2f6c6c76" + "6d2f6c6c766d2d70726f6a6563742061623462356132646235383239353861663165653330" + "3861373930636664623432626432343732302900490f7461726765745f6665617475726573" + "042b0f6d757461626c652d676c6f62616c732b087369676e2d6578742b0f7265666572656e" + "63652d74797065732b0a6d756c746976616c7565"; diff --git a/src/test/app/wasm_fixtures/fixtures.h b/src/test/app/wasm_fixtures/fixtures.h new file mode 100644 index 00000000000..96b22268f6f --- /dev/null +++ b/src/test/app/wasm_fixtures/fixtures.h @@ -0,0 +1,138 @@ +#pragma once + +// TODO: consider moving these to separate files (and figure out the build) + +#include +#include +#include + +// WASM binary format constants and helpers for building test modules +namespace wasm_constants { + +// Magic + version header +uint8_t const WASM_HEADER[] = { + 0x00, + 0x61, + 0x73, + 0x6d, // magic: \0asm + 0x01, + 0x00, + 0x00, + 0x00 // version: 1 +}; + +// Type section: () -> () +uint8_t const TYPE_EMPTY_FUNC[] = {0x01, 0x04, 0x01, 0x60, 0x00, 0x00}; + +// Function section: one function using type 0 +uint8_t const FUNC_TYPE0[] = {0x03, 0x02, 0x01, 0x00}; + +// Export section: export func 0 as "finish" +uint8_t const EXPORT_FINISH[] = {0x07, 0x0a, 0x01, 0x06, 'f', 'i', 'n', 'i', 's', 'h', 0x00, 0x00}; + +// Empty function body: 0 locals, end +uint8_t const EMPTY_BODY[] = {0x00, 0x0b}; + +// Data segment offset: i32.const 0, end +uint8_t const DATA_OFFSET_ZERO[] = {0x41, 0x00, 0x0b}; + +// Section IDs +uint8_t const SECTION_MEMORY = 0x05; +uint8_t const SECTION_CODE = 0x0a; +uint8_t const SECTION_DATA = 0x0b; + +// Instructions +uint8_t const INSTR_NOP = 0x01; +uint8_t const INSTR_END = 0x0b; + +// Fill byte for data section bloat +uint8_t const DATA_FILL_BYTE = 0xEE; + +// Generator for WASM module with large code section (many NOPs) +std::vector +generateCodeBlob(uint32_t num_instructions); + +// Generator for WASM module with large data section +std::vector +generateDataBlob(uint32_t data_size); + +} // namespace wasm_constants + +extern std::string const ledgerSqnWasmHex; +extern std::string const allHostFunctionsWasmHex; +extern std::string const allKeyletsWasmHex; +extern std::string const codecovTestsWasmHex; + +extern std::string const fibWasmHex; + +extern std::string const floatTestsWasmHex; +extern std::string const float0Hex; +extern std::string const disabledFloatHex; + +extern std::string const memoryPointerAtLimitHex; +extern std::string const memoryPointerOverLimitHex; +extern std::string const memoryOffsetOverLimitHex; +extern std::string const memoryEndOfWordOverLimitHex; +extern std::string const memoryGrow0To1PageHex; +extern std::string const memoryGrow1To0PageHex; +extern std::string const memoryLastByteOf8MBHex; +extern std::string const memoryGrow1MoreThan8MBHex; +extern std::string const memoryGrow0MoreThan8MBHex; +extern std::string const memoryInit1MoreThan8MBHex; +extern std::string const memoryNegativeAddressHex; + +extern std::string const table64ElementsHex; +extern std::string const table65ElementsHex; +extern std::string const table2TablesHex; +extern std::string const table0ElementsHex; +extern std::string const tableUintMaxHex; + +extern std::string const proposalMutableGlobalHex; +extern std::string const proposalGcStructNewHex; +extern std::string const proposalMultiValueHex; +extern std::string const proposalSignExtHex; +extern std::string const proposalFloatToIntHex; +extern std::string const proposalBulkMemoryHex; +extern std::string const proposalRefTypesHex; +extern std::string const proposalTailCallHex; +extern std::string const proposalExtendedConstHex; +extern std::string const proposalMultiMemoryHex; +extern std::string const proposalCustomPageSizesHex; +extern std::string const proposalMemory64Hex; +extern std::string const proposalWideArithmeticHex; + +extern std::string const trapDivideBy0Hex; +extern std::string const trapIntOverflowHex; +extern std::string const trapUnreachableHex; +extern std::string const trapNullCallHex; +extern std::string const trapFuncSigMismatchHex; + +extern std::string const wasiGetTimeHex; +extern std::string const wasiPrintHex; + +extern std::string const badMagicNumberHex; +extern std::string const badVersionNumberHex; +extern std::string const lyingHeaderHex; +extern std::string const neverEndingNumberHex; +extern std::string const vectorLieHex; +extern std::string const sectionOrderingHex; +extern std::string const ghostPayloadHex; +extern std::string const junkAfterSectionHex; +extern std::string const invalidSectionIdHex; +extern std::string const localVariableBombHex; + +extern std::string const deepRecursionHex; +extern std::string const infiniteLoopWasmHex; +extern std::string const startLoopHex; + +extern std::string const badAlignWasmHex; + +extern std::string const thousandParamsHex; +extern std::string const thousand1ParamsHex; +extern std::string const locals10kHex; +extern std::string const functions5kHex; + +extern std::string const opcReservedHex; + +extern std::string const impExpHex; +extern std::string const updateDataWasmHex; diff --git a/src/test/app/wasm_fixtures/float_tests/Cargo.lock b/src/test/app/wasm_fixtures/float_tests/Cargo.lock new file mode 100644 index 00000000000..0b9def91300 --- /dev/null +++ b/src/test/app/wasm_fixtures/float_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 = "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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +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 = "float_tests" +version = "0.0.1" +dependencies = [ + "xrpl-wasm-stdlib", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +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.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +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.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "xrpl-address-macro" +version = "0.7.1" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#1b27d310dedb4d1279407cf3a40f6858be0e3f14" +dependencies = [ + "bs58", + "quote", + "sha2", + "syn", +] + +[[package]] +name = "xrpl-wasm-stdlib" +version = "0.7.1" +source = "git+https://github.com/ripple/xrpl-wasm-stdlib.git#1b27d310dedb4d1279407cf3a40f6858be0e3f14" +dependencies = [ + "xrpl-address-macro", +] diff --git a/src/test/app/wasm_fixtures/float_tests/Cargo.toml b/src/test/app/wasm_fixtures/float_tests/Cargo.toml new file mode 100644 index 00000000000..b369cac5010 --- /dev/null +++ b/src/test/app/wasm_fixtures/float_tests/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "float_tests" +version = "0.0.1" +edition = "2024" + +# 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" } + +[profile.dev] +panic = "abort" diff --git a/src/test/app/wasm_fixtures/float_tests/src/lib.rs b/src/test/app/wasm_fixtures/float_tests/src/lib.rs new file mode 100644 index 00000000000..fc8a1ff5cbc --- /dev/null +++ b/src/test/app/wasm_fixtures/float_tests/src/lib.rs @@ -0,0 +1,461 @@ +#![allow(unused_imports)] +#![allow(unused_variables)] +#![cfg_attr(target_arch = "wasm32", no_std)] + +#[cfg(not(target_arch = "wasm32"))] +extern crate std; + +use xrpl_std::core::locator::Locator; +use xrpl_std::core::types::opaque_float::{FLOAT_NEGATIVE_ONE, FLOAT_ONE}; +use xrpl_std::decode_hex_32; +use xrpl_std::host::trace::DataRepr::AsHex; +use xrpl_std::host::trace::{trace, trace_data, trace_float, trace_num, DataRepr}; +use xrpl_std::host::{ + cache_ledger_obj, float_add, float_compare, float_divide, float_from_int, float_from_uint, + float_log, float_multiply, float_pow, float_root, float_set, float_subtract, + get_ledger_obj_array_len, get_ledger_obj_field, get_ledger_obj_nested_field, + trace_opaque_float, FLOAT_ROUNDING_MODES_TO_NEAREST, +}; +use xrpl_std::sfield; +use xrpl_std::sfield::{ + Account, AccountTxnID, Balance, Domain, EmailHash, Flags, LedgerEntryType, MessageKey, + OwnerCount, PreviousTxnID, PreviousTxnLgrSeq, RegularKey, Sequence, TicketCount, TransferRate, +}; + +fn test_float_from_wasm() { + let _ = trace("\n$$$ test_float_from_wasm $$$"); + + let mut f: [u8; 8] = [0u8; 8]; + if 8 == unsafe { float_from_int(12300, f.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) } { + let _ = trace_float(" float from i64 12300:", &f); + let _ = trace_data(" float from i64 12300 as HEX:", &f, AsHex); + } else { + let _ = trace(" float from i64 12300: failed"); + } + + let u64_value: u64 = 12300; + if 8 == unsafe { + float_from_uint( + &u64_value as *const u64 as *const u8, + 8, + f.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + } { + let _ = trace_float(" float from u64 12300:", &f); + } else { + let _ = trace(" float from u64 12300: failed"); + } + + if 8 == unsafe { float_set(2, 123, f.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) } { + let _ = trace_float(" float from exp 2, mantissa 123:", &f); + } else { + let _ = trace(" float from exp 2, mantissa 3: failed"); + } + + let _ = trace_float(" float from const 1:", &FLOAT_ONE); + let _ = trace_float(" float from const -1:", &FLOAT_NEGATIVE_ONE); +} + +fn test_float_compare() { + let _ = trace("\n$$$ test_float_compare $$$"); + + let mut f1: [u8; 8] = [0u8; 8]; + if 8 != unsafe { float_from_int(1, f1.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) } { + let _ = trace(" float from 1: failed"); + } else { + let _ = trace_float(" float from 1:", &f1); + } + + if 0 == unsafe { float_compare(f1.as_ptr(), 8, FLOAT_ONE.as_ptr(), 8) } { + let _ = trace(" float from 1 == FLOAT_ONE"); + } else { + let _ = trace(" float from 1 != FLOAT_ONE"); + } + + if 1 == unsafe { float_compare(f1.as_ptr(), 8, FLOAT_NEGATIVE_ONE.as_ptr(), 8) } { + let _ = trace(" float from 1 > FLOAT_NEGATIVE_ONE"); + } else { + let _ = trace(" float from 1 !> FLOAT_NEGATIVE_ONE"); + } + + if 2 == unsafe { float_compare(FLOAT_NEGATIVE_ONE.as_ptr(), 8, f1.as_ptr(), 8) } { + let _ = trace(" FLOAT_NEGATIVE_ONE < float from 1"); + } else { + let _ = trace(" FLOAT_NEGATIVE_ONE !< float from 1"); + } +} + +fn test_float_add_subtract() { + let _ = trace("\n$$$ test_float_add_subtract $$$"); + + let mut f_compute: [u8; 8] = FLOAT_ONE; + for i in 0..9 { + unsafe { + float_add( + f_compute.as_ptr(), + 8, + FLOAT_ONE.as_ptr(), + 8, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + // let _ = trace_float(" float:", &f_compute); + } + let mut f10: [u8; 8] = [0u8; 8]; + if 8 != unsafe { float_from_int(10, f10.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) } { + // let _ = trace(" float from 10: failed"); + } + if 0 == unsafe { float_compare(f10.as_ptr(), 8, f_compute.as_ptr(), 8) } { + let _ = trace(" repeated add: good"); + } else { + let _ = trace(" repeated add: bad"); + } + + for i in 0..11 { + unsafe { + float_subtract( + f_compute.as_ptr(), + 8, + FLOAT_ONE.as_ptr(), + 8, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + } + if 0 == unsafe { float_compare(f_compute.as_ptr(), 8, FLOAT_NEGATIVE_ONE.as_ptr(), 8) } { + let _ = trace(" repeated subtract: good"); + } else { + let _ = trace(" repeated subtract: bad"); + } +} + +fn test_float_multiply_divide() { + let _ = trace("\n$$$ test_float_multiply_divide $$$"); + + let mut f10: [u8; 8] = [0u8; 8]; + unsafe { float_from_int(10, f10.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) }; + let mut f_compute: [u8; 8] = FLOAT_ONE; + for i in 0..6 { + unsafe { + float_multiply( + f_compute.as_ptr(), + 8, + f10.as_ptr(), + 8, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + // let _ = trace_float(" float:", &f_compute); + } + let mut f1000000: [u8; 8] = [0u8; 8]; + unsafe { + float_from_int( + 1000000, + f1000000.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + + if 0 == unsafe { float_compare(f1000000.as_ptr(), 8, f_compute.as_ptr(), 8) } { + let _ = trace(" repeated multiply: good"); + } else { + let _ = trace(" repeated multiply: bad"); + } + + for i in 0..7 { + unsafe { + float_divide( + f_compute.as_ptr(), + 8, + f10.as_ptr(), + 8, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + } + let mut f01: [u8; 8] = [0u8; 8]; + unsafe { float_set(-1, 1, f01.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) }; + + if 0 == unsafe { float_compare(f_compute.as_ptr(), 8, f01.as_ptr(), 8) } { + let _ = trace(" repeated divide: good"); + } else { + let _ = trace(" repeated divide: bad"); + } +} + +fn test_float_pow() { + let _ = trace("\n$$$ test_float_pow $$$"); + + let mut f_compute: [u8; 8] = [0u8; 8]; + unsafe { + float_pow( + FLOAT_ONE.as_ptr(), + 8, + 3, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + let _ = trace_float(" float cube of 1:", &f_compute); + + unsafe { + float_pow( + FLOAT_NEGATIVE_ONE.as_ptr(), + 8, + 6, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + let _ = trace_float(" float 6th power of -1:", &f_compute); + + let mut f9: [u8; 8] = [0u8; 8]; + unsafe { float_from_int(9, f9.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) }; + unsafe { + float_pow( + f9.as_ptr(), + 8, + 2, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + let _ = trace_float(" float square of 9:", &f_compute); + + unsafe { + float_pow( + f9.as_ptr(), + 8, + 0, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + let _ = trace_float(" float 0th power of 9:", &f_compute); + + let mut f0: [u8; 8] = [0u8; 8]; + unsafe { float_from_int(0, f0.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) }; + unsafe { + float_pow( + f0.as_ptr(), + 8, + 2, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + let _ = trace_float(" float square of 0:", &f_compute); + + let r = unsafe { + float_pow( + f0.as_ptr(), + 8, + 0, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + let _ = trace_num( + " float 0th power of 0 (expecting INVALID_PARAMS error):", + r as i64, + ); +} + +fn test_float_root() { + let _ = trace("\n$$$ test_float_root $$$"); + + let mut f9: [u8; 8] = [0u8; 8]; + unsafe { float_from_int(9, f9.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) }; + let mut f_compute: [u8; 8] = [0u8; 8]; + unsafe { + float_root( + f9.as_ptr(), + 8, + 2, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + let _ = trace_float(" float sqrt of 9:", &f_compute); + unsafe { + float_root( + f9.as_ptr(), + 8, + 3, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + let _ = trace_float(" float cbrt of 9:", &f_compute); + + let mut f1000000: [u8; 8] = [0u8; 8]; + unsafe { + float_from_int( + 1000000, + f1000000.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + unsafe { + float_root( + f1000000.as_ptr(), + 8, + 3, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + let _ = trace_float(" float cbrt of 1000000:", &f_compute); + unsafe { + float_root( + f1000000.as_ptr(), + 8, + 6, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + let _ = trace_float(" float 6th root of 1000000:", &f_compute); +} + +fn test_float_log() { + let _ = trace("\n$$$ test_float_log $$$"); + + let mut f1000000: [u8; 8] = [0u8; 8]; + unsafe { + float_from_int( + 1000000, + f1000000.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + let mut f_compute: [u8; 8] = [0u8; 8]; + unsafe { + float_log( + f1000000.as_ptr(), + 8, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + let _ = trace_float(" log_10 of 1000000:", &f_compute); +} + +fn test_float_negate() { + let _ = trace("\n$$$ test_float_negate $$$"); + + let mut f_compute: [u8; 8] = [0u8; 8]; + unsafe { + float_multiply( + FLOAT_ONE.as_ptr(), + 8, + FLOAT_NEGATIVE_ONE.as_ptr(), + 8, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + // let _ = trace_float(" float:", &f_compute); + if 0 == unsafe { float_compare(FLOAT_NEGATIVE_ONE.as_ptr(), 8, f_compute.as_ptr(), 8) } { + let _ = trace(" negate const 1: good"); + } else { + let _ = trace(" negate const 1: bad"); + } + + unsafe { + float_multiply( + FLOAT_NEGATIVE_ONE.as_ptr(), + 8, + FLOAT_NEGATIVE_ONE.as_ptr(), + 8, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + // let _ = trace_float(" float:", &f_compute); + if 0 == unsafe { float_compare(FLOAT_ONE.as_ptr(), 8, f_compute.as_ptr(), 8) } { + let _ = trace(" negate const -1: good"); + } else { + let _ = trace(" negate const -1: bad"); + } +} + +fn test_float_invert() { + let _ = trace("\n$$$ test_float_invert $$$"); + + let mut f_compute: [u8; 8] = [0u8; 8]; + let mut f10: [u8; 8] = [0u8; 8]; + unsafe { float_from_int(10, f10.as_mut_ptr(), 8, FLOAT_ROUNDING_MODES_TO_NEAREST) }; + unsafe { + float_divide( + FLOAT_ONE.as_ptr(), + 8, + f10.as_ptr(), + 8, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + let _ = trace_float(" invert a float from 10:", &f_compute); + unsafe { + float_divide( + FLOAT_ONE.as_ptr(), + 8, + f_compute.as_ptr(), + 8, + f_compute.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + let _ = trace_float(" invert again:", &f_compute); + + // if f10's value is 7, then invert twice won't match the original value + if 0 == unsafe { float_compare(f10.as_ptr(), 8, f_compute.as_ptr(), 8) } { + let _ = trace(" invert twice: good"); + } else { + let _ = trace(" invert twice: bad"); + } +} + +#[unsafe(no_mangle)] +pub extern "C" fn finish() -> i32 { + test_float_from_wasm(); + test_float_compare(); + test_float_add_subtract(); + test_float_multiply_divide(); + test_float_pow(); + test_float_root(); + test_float_log(); + test_float_negate(); + test_float_invert(); + + 1 +} diff --git a/src/test/app/wasm_fixtures/infiniteLoop.c b/src/test/app/wasm_fixtures/infiniteLoop.c new file mode 100644 index 00000000000..ba84a92ac1d --- /dev/null +++ b/src/test/app/wasm_fixtures/infiniteLoop.c @@ -0,0 +1,7 @@ +int loop() +{ + int volatile x = 0; + while (1) + x++; + return x; +} diff --git a/src/test/app/wasm_fixtures/ledgerSqn.c b/src/test/app/wasm_fixtures/ledgerSqn.c new file mode 100644 index 00000000000..e4d57b85936 --- /dev/null +++ b/src/test/app/wasm_fixtures/ledgerSqn.c @@ -0,0 +1,14 @@ +#include + +int32_t get_ledger_sqn(uint8_t *, int32_t); + +int finish() +{ + uint32_t sqn; + int32_t result = get_ledger_sqn((uint8_t *)&sqn, sizeof(sqn)); + + if (result < 0) + return result; + + return sqn >= 5 ? 5 : 0; +} diff --git a/src/test/app/wasm_fixtures/parameters/Cargo.lock b/src/test/app/wasm_fixtures/parameters/Cargo.lock new file mode 100644 index 00000000000..c42d205b52d --- /dev/null +++ b/src/test/app/wasm_fixtures/parameters/Cargo.lock @@ -0,0 +1,15 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "parameters" +version = "0.0.1" +dependencies = [ + "xrpl-wasm-std", +] + +[[package]] +name = "xrpl-wasm-std" +version = "0.5.1-devnet5" +source = "git+https://github.com/Transia-RnD/craft.git?branch=dangell%2Fsmart-contracts#3c8191ae9832ea25f7d8f3e5eeb33b65181d31b5" diff --git a/src/test/app/wasm_fixtures/parameters/Cargo.toml b/src/test/app/wasm_fixtures/parameters/Cargo.toml new file mode 100644 index 00000000000..ab2406a30f4 --- /dev/null +++ b/src/test/app/wasm_fixtures/parameters/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "parameters" +version = "0.0.1" +edition = "2024" + +# 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-wasm-std = { git = "https://github.com/Transia-RnD/craft.git", branch = "dangell/smart-contracts", package = "xrpl-wasm-std" } + +[profile.dev] +panic = "abort" diff --git a/src/test/app/wasm_fixtures/parameters/src/lib.rs b/src/test/app/wasm_fixtures/parameters/src/lib.rs new file mode 100644 index 00000000000..e03abbf4c9a --- /dev/null +++ b/src/test/app/wasm_fixtures/parameters/src/lib.rs @@ -0,0 +1,751 @@ +#![allow(unused_imports)] +#![cfg_attr(target_arch = "wasm32", no_std)] + +#[cfg(not(target_arch = "wasm32"))] +extern crate std; + +use xrpl_wasm_std::core::params::function::get_function_param; +use xrpl_wasm_std::core::params::instance::get_instance_param; +use xrpl_wasm_std::core::type_codes::{ + STI_ACCOUNT, STI_AMOUNT, STI_ARRAY, STI_CURRENCY, STI_NUMBER, STI_OBJECT, STI_UINT128, + STI_UINT16, STI_UINT160, STI_UINT192, STI_UINT256, STI_UINT32, STI_UINT64, STI_UINT8, STI_VL, +}; +use xrpl_wasm_std::core::types::account_id::AccountID; +use xrpl_wasm_std::core::types::amount::opaque_float::OpaqueFloat; +use xrpl_wasm_std::core::types::amount::opaque_float::{FLOAT_NEGATIVE_ONE, FLOAT_ONE}; +use xrpl_wasm_std::core::types::amount::token_amount::TokenAmount; +use xrpl_wasm_std::core::types::hash_256::Hash256; +use xrpl_wasm_std::core::types::number::Number; +use xrpl_wasm_std::core::types::uint_160::UInt160; +use xrpl_wasm_std::core::types::uint_192::UInt192; +use xrpl_wasm_std::host::trace::{trace, trace_data, trace_float, trace_num, DataRepr}; +use xrpl_wasm_std::host::{float_add, float_set, FLOAT_ROUNDING_MODES_TO_NEAREST}; +use xrpl_wasm_std::host::{function_param, instance_param}; + +#[unsafe(no_mangle)] +pub extern "C" fn function_params() -> i32 { + // UINT8 + let value = match get_function_param::(0) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT8 Parameter Error Code:", err as i64); + return -1; + } + }; + let _ = trace_num("UINT8 Value:", value as i64); + // as hex + let _ = trace_data("UINT8 Hex:", &[value], DataRepr::AsHex); + + // TODO: replace with require + if value != 255 { + let _ = trace("UINT8 Parameter Error: Invalid Value"); + return -1; + } + + // UINT16 + let value = match get_function_param::(1) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT16 Parameter Error Code:", err as i64); + return -1; + } + }; + let _ = trace_num("UINT16 Value:", value as i64); + // as hex + let buf = value.to_le_bytes(); + let _ = trace_data("UINT16 Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if value != 65535 { + let _ = trace("UINT16 Parameter Error: Invalid Value"); + return -1; + } + + // UINT32 + let value = match get_function_param::(2) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT32 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.to_le_bytes(); + let _ = trace_data("UINT32 Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if value != 4294967295 { + let _ = trace("UINT32 Parameter Error: Invalid Value"); + return -1; + } + + // UINT64 + let value = match get_function_param::(3) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT64 Parameter Error Code:", err as i64); + return -1; + } + }; + let _ = trace_num("UINT64 Value:", value as i64); + // as hex + let buf = value.to_le_bytes(); + let _ = trace_data("UINT64 Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if buf != [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] { + let _ = trace("UINT64 Parameter Error: Invalid Value"); + return -1; + } + + // UINT128 + let value = match get_function_param::(4) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT128 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.to_le_bytes(); + let _ = trace_data("UINT128 Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if buf + != [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, + ] + { + let _ = trace("UINT128 Parameter Error: Invalid Value"); + return -1; + } + + // UINT160 + let value = match get_function_param::(5) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT160 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.as_bytes(); + let _ = trace_data("UINT160 Hex:", buf, DataRepr::AsHex); + + // TODO: replace with require + let expected190: [u8; 20] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, + ]; + if *buf != expected190 { + let _ = trace("UINT160 Parameter Error: Invalid Value"); + return -1; + } + + // UINT192 + let value = match get_function_param::(6) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT192 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.as_bytes(); + let _ = trace_data("UINT192 Hex:", buf, DataRepr::AsHex); + + // TODO: replace with require + let expected192: [u8; 24] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + ]; + if *buf != expected192 { + let _ = trace("UINT192 Parameter Error: Invalid Value"); + return -1; + } + + // UINT256 + let value = match get_function_param::(7) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT256 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.as_bytes(); + let _ = trace_data("UINT256 Hex:", buf, DataRepr::AsHex); + + // TODO: replace with require + let expected256: [u8; 32] = [ + 0xD9, 0x55, 0xDA, 0xC2, 0xE7, 0x75, 0x19, 0xF0, 0x5A, 0xD1, 0x51, 0xA5, 0xD3, 0xC9, 0x9F, + 0xC8, 0x12, 0x5F, 0xB3, 0x9D, 0x58, 0xFF, 0x9F, 0x10, 0x6F, 0x1A, 0xCA, 0x44, 0x91, 0x90, + 0x2C, 0x25, + ]; + if *buf != expected256 { + let _ = trace("UINT256 Parameter Error: Invalid Value"); + return -1; + } + + // // VL + // let mut buf = [0x00; 4]; + // let output_len = unsafe { function_param(8, STI_VL.into(), buf.as_mut_ptr(), buf.len()) }; + // let _ = trace_num("VL Value Len:", output_len as i64); + // // as hex + // let _ = trace_data("VL Hex:", &buf[0..4], DataRepr::AsHex); + + // ACCOUNT + let account_id = match get_function_param::(9) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("ACCOUNT Parameter Error Code:", err as i64); + return -1; + } + }; + // trace the value + let _ = trace_data("ACCOUNT Value:", &account_id.0, DataRepr::AsHex); + + // TODO: replace with require + let expectedAccount: [u8; 20] = [ + 0xAE, 0x12, 0x3A, 0x85, 0x56, 0xF3, 0xCF, 0x91, 0x15, 0x47, 0x11, 0x37, 0x6A, 0xFB, 0x0F, + 0x89, 0x4F, 0x83, 0x2B, 0x3D, + ]; + if account_id.0 != expectedAccount { + let _ = trace("ACCOUNT Parameter Error: Invalid Value"); + return -1; + } + + // AMOUNT XRP + let xrp_token = match get_function_param::(10) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("AMOUNT XRP Parameter Error Code:", err as i64); + return -1; + } + }; + match xrp_token { + TokenAmount::XRP { num_drops } => { + let _ = trace_num("AMOUNT Value (XRP):", num_drops); + } + _ => { + let _ = trace_num("AMOUNT Value (XRP):", -1); + } + } + let buf = match xrp_token { + TokenAmount::XRP { num_drops } => num_drops.to_le_bytes(), + _ => [0u8; 8], + }; + let _ = trace_data("AMOUNT Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if let TokenAmount::XRP { num_drops } = xrp_token { + if num_drops != 1000000 { + let _ = trace("AMOUNT.XRP Parameter Error: Invalid Value"); + return -1; + } + } else { + let _ = trace("AMOUNT.XRP Parameter Error: Invalid Type"); + return -1; + } + + // AMOUNT IOU + let iou_token = match get_function_param::(11) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("AMOUNT IOU Parameter Error Code:", err as i64); + return -1; + } + }; + let (iou_amount, iou_issuer, iou_currency) = match &iou_token { + TokenAmount::IOU { + amount, + issuer, + currency_code, + } => { + // trace amount hex + let _ = trace_data("AMOUNT Value (IOU):", &amount.0, DataRepr::AsHex); + let _ = trace_float("AMOUNT Value (IOU) - Original:", &amount.0); + let _ = trace_data("IOU Issuer:", &issuer.0, DataRepr::AsHex); + let _ = trace_data("IOU Currency:", ¤cy_code.0, DataRepr::AsHex); + + // Add FLOAT_ONE to the IOU amount + let mut new_amount: [u8; 8] = [0u8; 8]; + let result = unsafe { + float_add( + amount.0.as_ptr(), + 8, + FLOAT_ONE.as_ptr(), + 8, + new_amount.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + + if result == 8 { + // trace hex of the new amount + let _ = trace_data( + "AMOUNT Value (IOU) - After adding 1:", + &new_amount, + DataRepr::AsHex, + ); + let _ = trace_float("AMOUNT Value (IOU) - After adding 1:", &new_amount); + + // Create a new TokenAmount with the updated amount + let updated_token = TokenAmount::IOU { + amount: new_amount.into(), + issuer: *issuer, + currency_code: *currency_code, + }; + + // You now have the updated token amount in `updated_token` + // and the raw float bytes in `new_amount` + } else { + let _ = trace_num( + "Error adding FLOAT_ONE to IOU amount, result:", + result as i64, + ); + } + + (Some(*amount), Some(*issuer), Some(*currency_code)) + } + _ => { + let _ = trace_data("AMOUNT Value (IOU):", &[0u8; 8], DataRepr::AsHex); + (None, None, None) + } + }; + // trace new iou_amount as hex + if let Some(amount) = iou_amount { + let _ = trace_data("IOU Amount:", &amount.0, DataRepr::AsHex); + } else { + let _ = trace_data("IOU Amount:", &[0u8; 8], DataRepr::AsHex); + } + + // TODO: replace with require + if iou_amount.is_none() { + let _ = trace("AMOUNT.IOU Parameter Error: Invalid Type"); + return -1; + } + + // let mut buf = [0x00; 12]; + // let output_len = unsafe { function_param(12, STI_NUMBER.into(), buf.as_mut_ptr(), buf.len()) }; + // let _ = trace_num("NUMBER Value Len:", output_len as i64); + + // NUMBER + let number = match get_function_param::(12) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("NUMBER Parameter Error Code:", err as i64); + return -1; + } + }; + // trace the value + let buf = number.as_bytes(); + let _ = trace_data("NUMBER Value:", &buf, DataRepr::AsHex); + + // TODO: replace with require + let expectedNumber: [u8; 12] = [ + 0x00, 0x04, 0x43, 0x64, 0xC5, 0xBB, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF1, + ]; + if buf != expectedNumber { + let _ = trace("NUMBER Parameter Error: Invalid Value"); + return -1; + } + + // // Parse Number to get mantissa and exponent + // let stnum = Number::from(&buf).unwrap(); + let _ = trace_num("NUMBER Mantissa:", number.mantissa); + let _ = trace_num("NUMBER Exponent:", number.exponent as i64); + + let mut opaque_float_buf = [0x00; 8]; + let result = unsafe { + float_set( + number.exponent, + number.mantissa, + opaque_float_buf.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + + let opaque = OpaqueFloat::from(opaque_float_buf); + let _ = trace_float("NUMBER as OpaqueFloat:", &opaque.0); + let _ = trace_data( + "NUMBER OpaqueFloat Hex:", + &opaque_float_buf, + DataRepr::AsHex, + ); + + // AMOUNT (MPT) + // ISSUE (XRP) + // ISSUE (IOU) + // ISSUE (MPT) + // CURRENCY + + return 0; // Return success code +} + +#[unsafe(no_mangle)] +pub extern "C" fn instance_params() -> i32 { + // UINT8 + let value = match get_instance_param::(1) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT8 Parameter Error Code:", err as i64); + return -1; + } + }; + let _ = trace_num("UINT8 Value:", value as i64); + // as hex + let _ = trace_data("UINT8 Hex:", &[value], DataRepr::AsHex); + + // TODO: replace with require + if value != 255 { + let _ = trace("UINT8 Parameter Error: Invalid Value"); + return -1; + } + + // UINT16 + let value = match get_instance_param::(2) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT16 Parameter Error Code:", err as i64); + return -1; + } + }; + let _ = trace_num("UINT16 Value:", value as i64); + // as hex + let buf = value.to_le_bytes(); + let _ = trace_data("UINT16 Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if value != 65535 { + let _ = trace("UINT16 Parameter Error: Invalid Value"); + return -1; + } + + // UINT32 + let value = match get_instance_param::(3) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT32 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.to_le_bytes(); + let _ = trace_data("UINT32 Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if value != 4294967295 { + let _ = trace("UINT32 Parameter Error: Invalid Value"); + return -1; + } + + // UINT64 + let value = match get_instance_param::(4) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT64 Parameter Error Code:", err as i64); + return -1; + } + }; + let _ = trace_num("UINT64 Value:", value as i64); + // as hex + let buf = value.to_le_bytes(); + let _ = trace_data("UINT64 Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if buf != [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] { + let _ = trace("UINT64 Parameter Error: Invalid Value"); + return -1; + } + + // UINT128 + let value = match get_instance_param::(5) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT128 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.to_le_bytes(); + let _ = trace_data("UINT128 Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if buf + != [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, + ] + { + let _ = trace("UINT128 Parameter Error: Invalid Value"); + return -1; + } + + // UINT160 + let value = match get_instance_param::(6) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT160 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.as_bytes(); + let _ = trace_data("UINT160 Hex:", buf, DataRepr::AsHex); + + // TODO: replace with require + let expected190: [u8; 20] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, + ]; + if *buf != expected190 { + let _ = trace("UINT160 Parameter Error: Invalid Value"); + return -1; + } + + // UINT192 + let value = match get_instance_param::(7) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT192 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.as_bytes(); + let _ = trace_data("UINT192 Hex:", buf, DataRepr::AsHex); + + // TODO: replace with require + let expected192: [u8; 24] = [ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + ]; + if *buf != expected192 { + let _ = trace("UINT192 Parameter Error: Invalid Value"); + return -1; + } + + // UINT256 + let value = match get_instance_param::(8) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("UINT256 Parameter Error Code:", err as i64); + return -1; + } + }; + // as hex + let buf = value.as_bytes(); + let _ = trace_data("UINT256 Hex:", buf, DataRepr::AsHex); + + // TODO: replace with require + let expected256: [u8; 32] = [ + 0xD9, 0x55, 0xDA, 0xC2, 0xE7, 0x75, 0x19, 0xF0, 0x5A, 0xD1, 0x51, 0xA5, 0xD3, 0xC9, 0x9F, + 0xC8, 0x12, 0x5F, 0xB3, 0x9D, 0x58, 0xFF, 0x9F, 0x10, 0x6F, 0x1A, 0xCA, 0x44, 0x91, 0x90, + 0x2C, 0x25, + ]; + if *buf != expected256 { + let _ = trace("UINT256 Parameter Error: Invalid Value"); + return -1; + } + + // // VL + // let mut buf = [0x00; 4]; + // let output_len = unsafe { instance_param(8, STI_VL.into(), buf.as_mut_ptr(), buf.len()) }; + // let _ = trace_num("VL Value Len:", output_len as i64); + // // as hex + // let _ = trace_data("VL Hex:", &buf[0..4], DataRepr::AsHex); + + // ACCOUNT + let account_id = match get_instance_param::(10) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("ACCOUNT Parameter Error Code:", err as i64); + return -1; + } + }; + // trace the value + let _ = trace_data("ACCOUNT Value:", &account_id.0, DataRepr::AsHex); + + // TODO: replace with require + let expectedAccount: [u8; 20] = [ + 0xAE, 0x12, 0x3A, 0x85, 0x56, 0xF3, 0xCF, 0x91, 0x15, 0x47, 0x11, 0x37, 0x6A, 0xFB, 0x0F, + 0x89, 0x4F, 0x83, 0x2B, 0x3D, + ]; + if account_id.0 != expectedAccount { + let _ = trace("ACCOUNT Parameter Error: Invalid Value"); + return -1; + } + + // AMOUNT XRP + let xrp_token = match get_instance_param::(11) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("AMOUNT XRP Parameter Error Code:", err as i64); + return -1; + } + }; + match xrp_token { + TokenAmount::XRP { num_drops } => { + let _ = trace_num("AMOUNT Value (XRP):", num_drops); + } + _ => { + let _ = trace_num("AMOUNT Value (XRP):", -1); + } + } + let buf = match xrp_token { + TokenAmount::XRP { num_drops } => num_drops.to_le_bytes(), + _ => [0u8; 8], + }; + let _ = trace_data("AMOUNT Hex:", &buf, DataRepr::AsHex); + + // TODO: replace with require + if let TokenAmount::XRP { num_drops } = xrp_token { + if num_drops != 1000000 { + let _ = trace("AMOUNT.XRP Parameter Error: Invalid Value"); + return -1; + } + } else { + let _ = trace("AMOUNT.XRP Parameter Error: Invalid Type"); + return -1; + } + + // AMOUNT IOU + let iou_token = match get_instance_param::(12) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("AMOUNT IOU Parameter Error Code:", err as i64); + return -1; + } + }; + let (iou_amount, iou_issuer, iou_currency) = match &iou_token { + TokenAmount::IOU { + amount, + issuer, + currency_code, + } => { + // trace amount hex + let _ = trace_data("AMOUNT Value (IOU):", &amount.0, DataRepr::AsHex); + let _ = trace_float("AMOUNT Value (IOU) - Original:", &amount.0); + let _ = trace_data("IOU Issuer:", &issuer.0, DataRepr::AsHex); + let _ = trace_data("IOU Currency:", ¤cy_code.0, DataRepr::AsHex); + + // Add FLOAT_ONE to the IOU amount + let mut new_amount: [u8; 8] = [0u8; 8]; + let result = unsafe { + float_add( + amount.0.as_ptr(), + 8, + FLOAT_ONE.as_ptr(), + 8, + new_amount.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + + if result == 8 { + // trace hex of the new amount + let _ = trace_data( + "AMOUNT Value (IOU) - After adding 1:", + &new_amount, + DataRepr::AsHex, + ); + let _ = trace_float("AMOUNT Value (IOU) - After adding 1:", &new_amount); + + // Create a new TokenAmount with the updated amount + let updated_token = TokenAmount::IOU { + amount: new_amount.into(), + issuer: *issuer, + currency_code: *currency_code, + }; + + // You now have the updated token amount in `updated_token` + // and the raw float bytes in `new_amount` + } else { + let _ = trace_num( + "Error adding FLOAT_ONE to IOU amount, result:", + result as i64, + ); + } + + (Some(*amount), Some(*issuer), Some(*currency_code)) + } + _ => { + let _ = trace_data("AMOUNT Value (IOU):", &[0u8; 8], DataRepr::AsHex); + (None, None, None) + } + }; + // trace new iou_amount as hex + if let Some(amount) = iou_amount { + let _ = trace_data("IOU Amount:", &amount.0, DataRepr::AsHex); + } else { + let _ = trace_data("IOU Amount:", &[0u8; 8], DataRepr::AsHex); + } + + // TODO: replace with require + if iou_amount.is_none() { + let _ = trace("AMOUNT.IOU Parameter Error: Invalid Type"); + return -1; + } + + // let mut buf = [0x00; 12]; + // let output_len = unsafe { instance_param(12, STI_NUMBER.into(), buf.as_mut_ptr(), buf.len()) }; + // let _ = trace_num("NUMBER Value Len:", output_len as i64); + + // NUMBER + let number = match get_instance_param::(13) { + Ok(a) => a, + Err(err) => { + let _ = trace_num("NUMBER Parameter Error Code:", err as i64); + return -1; + } + }; + // trace the value + let buf = number.as_bytes(); + let _ = trace_data("NUMBER Value:", &buf, DataRepr::AsHex); + + // TODO: replace with require + let expectedNumber: [u8; 12] = [ + 0x00, 0x04, 0x43, 0x64, 0xC5, 0xBB, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xF1, + ]; + if buf != expectedNumber { + let _ = trace("NUMBER Parameter Error: Invalid Value"); + return -1; + } + + // // Parse Number to get mantissa and exponent + // let stnum = Number::from(&buf).unwrap(); + let _ = trace_num("NUMBER Mantissa:", number.mantissa); + let _ = trace_num("NUMBER Exponent:", number.exponent as i64); + + let mut opaque_float_buf = [0x00; 8]; + let result = unsafe { + float_set( + number.exponent, + number.mantissa, + opaque_float_buf.as_mut_ptr(), + 8, + FLOAT_ROUNDING_MODES_TO_NEAREST, + ) + }; + + let opaque = OpaqueFloat::from(opaque_float_buf); + let _ = trace_float("NUMBER as OpaqueFloat:", &opaque.0); + let _ = trace_data( + "NUMBER OpaqueFloat Hex:", + &opaque_float_buf, + DataRepr::AsHex, + ); + + // AMOUNT (MPT) + // ISSUE (XRP) + // ISSUE (IOU) + // ISSUE (MPT) + // CURRENCY + + return 0; // Return success code +} diff --git a/src/test/app/wasm_fixtures/thousand1_params.c b/src/test/app/wasm_fixtures/thousand1_params.c new file mode 100644 index 00000000000..1a281461c4c --- /dev/null +++ b/src/test/app/wasm_fixtures/thousand1_params.c @@ -0,0 +1,264 @@ +// clang-format off + +#include + +int32_t test( + int32_t p0, int32_t p1, int32_t p2, int32_t p3, int32_t p4, int32_t p5, int32_t p6, int32_t p7 +, int32_t p8, int32_t p9, int32_t p10, int32_t p11, int32_t p12, int32_t p13, int32_t p14, int32_t p15 +, int32_t p16, int32_t p17, int32_t p18, int32_t p19, int32_t p20, int32_t p21, int32_t p22, int32_t p23 +, int32_t p24, int32_t p25, int32_t p26, int32_t p27, int32_t p28, int32_t p29, int32_t p30, int32_t p31 +, int32_t p32, int32_t p33, int32_t p34, int32_t p35, int32_t p36, int32_t p37, int32_t p38, int32_t p39 +, int32_t p40, int32_t p41, int32_t p42, int32_t p43, int32_t p44, int32_t p45, int32_t p46, int32_t p47 +, int32_t p48, int32_t p49, int32_t p50, int32_t p51, int32_t p52, int32_t p53, int32_t p54, int32_t p55 +, int32_t p56, int32_t p57, int32_t p58, int32_t p59, int32_t p60, int32_t p61, int32_t p62, int32_t p63 +, int32_t p64, int32_t p65, int32_t p66, int32_t p67, int32_t p68, int32_t p69, int32_t p70, int32_t p71 +, int32_t p72, int32_t p73, int32_t p74, int32_t p75, int32_t p76, int32_t p77, int32_t p78, int32_t p79 +, int32_t p80, int32_t p81, int32_t p82, int32_t p83, int32_t p84, int32_t p85, int32_t p86, int32_t p87 +, int32_t p88, int32_t p89, int32_t p90, int32_t p91, int32_t p92, int32_t p93, int32_t p94, int32_t p95 +, int32_t p96, int32_t p97, int32_t p98, int32_t p99, int32_t p100, int32_t p101, int32_t p102, int32_t p103 +, int32_t p104, int32_t p105, int32_t p106, int32_t p107, int32_t p108, int32_t p109, int32_t p110, int32_t p111 +, int32_t p112, int32_t p113, int32_t p114, int32_t p115, int32_t p116, int32_t p117, int32_t p118, int32_t p119 +, int32_t p120, int32_t p121, int32_t p122, int32_t p123, int32_t p124, int32_t p125, int32_t p126, int32_t p127 +, int32_t p128, int32_t p129, int32_t p130, int32_t p131, int32_t p132, int32_t p133, int32_t p134, int32_t p135 +, int32_t p136, int32_t p137, int32_t p138, int32_t p139, int32_t p140, int32_t p141, int32_t p142, int32_t p143 +, int32_t p144, int32_t p145, int32_t p146, int32_t p147, int32_t p148, int32_t p149, int32_t p150, int32_t p151 +, int32_t p152, int32_t p153, int32_t p154, int32_t p155, int32_t p156, int32_t p157, int32_t p158, int32_t p159 +, int32_t p160, int32_t p161, int32_t p162, int32_t p163, int32_t p164, int32_t p165, int32_t p166, int32_t p167 +, int32_t p168, int32_t p169, int32_t p170, int32_t p171, int32_t p172, int32_t p173, int32_t p174, int32_t p175 +, int32_t p176, int32_t p177, int32_t p178, int32_t p179, int32_t p180, int32_t p181, int32_t p182, int32_t p183 +, int32_t p184, int32_t p185, int32_t p186, int32_t p187, int32_t p188, int32_t p189, int32_t p190, int32_t p191 +, int32_t p192, int32_t p193, int32_t p194, int32_t p195, int32_t p196, int32_t p197, int32_t p198, int32_t p199 +, int32_t p200, int32_t p201, int32_t p202, int32_t p203, int32_t p204, int32_t p205, int32_t p206, int32_t p207 +, int32_t p208, int32_t p209, int32_t p210, int32_t p211, int32_t p212, int32_t p213, int32_t p214, int32_t p215 +, int32_t p216, int32_t p217, int32_t p218, int32_t p219, int32_t p220, int32_t p221, int32_t p222, int32_t p223 +, int32_t p224, int32_t p225, int32_t p226, int32_t p227, int32_t p228, int32_t p229, int32_t p230, int32_t p231 +, int32_t p232, int32_t p233, int32_t p234, int32_t p235, int32_t p236, int32_t p237, int32_t p238, int32_t p239 +, int32_t p240, int32_t p241, int32_t p242, int32_t p243, int32_t p244, int32_t p245, int32_t p246, int32_t p247 +, int32_t p248, int32_t p249, int32_t p250, int32_t p251, int32_t p252, int32_t p253, int32_t p254, int32_t p255 +, int32_t p256, int32_t p257, int32_t p258, int32_t p259, int32_t p260, int32_t p261, int32_t p262, int32_t p263 +, int32_t p264, int32_t p265, int32_t p266, int32_t p267, int32_t p268, int32_t p269, int32_t p270, int32_t p271 +, int32_t p272, int32_t p273, int32_t p274, int32_t p275, int32_t p276, int32_t p277, int32_t p278, int32_t p279 +, int32_t p280, int32_t p281, int32_t p282, int32_t p283, int32_t p284, int32_t p285, int32_t p286, int32_t p287 +, int32_t p288, int32_t p289, int32_t p290, int32_t p291, int32_t p292, int32_t p293, int32_t p294, int32_t p295 +, int32_t p296, int32_t p297, int32_t p298, int32_t p299, int32_t p300, int32_t p301, int32_t p302, int32_t p303 +, int32_t p304, int32_t p305, int32_t p306, int32_t p307, int32_t p308, int32_t p309, int32_t p310, int32_t p311 +, int32_t p312, int32_t p313, int32_t p314, int32_t p315, int32_t p316, int32_t p317, int32_t p318, int32_t p319 +, int32_t p320, int32_t p321, int32_t p322, int32_t p323, int32_t p324, int32_t p325, int32_t p326, int32_t p327 +, int32_t p328, int32_t p329, int32_t p330, int32_t p331, int32_t p332, int32_t p333, int32_t p334, int32_t p335 +, int32_t p336, int32_t p337, int32_t p338, int32_t p339, int32_t p340, int32_t p341, int32_t p342, int32_t p343 +, int32_t p344, int32_t p345, int32_t p346, int32_t p347, int32_t p348, int32_t p349, int32_t p350, int32_t p351 +, int32_t p352, int32_t p353, int32_t p354, int32_t p355, int32_t p356, int32_t p357, int32_t p358, int32_t p359 +, int32_t p360, int32_t p361, int32_t p362, int32_t p363, int32_t p364, int32_t p365, int32_t p366, int32_t p367 +, int32_t p368, int32_t p369, int32_t p370, int32_t p371, int32_t p372, int32_t p373, int32_t p374, int32_t p375 +, int32_t p376, int32_t p377, int32_t p378, int32_t p379, int32_t p380, int32_t p381, int32_t p382, int32_t p383 +, int32_t p384, int32_t p385, int32_t p386, int32_t p387, int32_t p388, int32_t p389, int32_t p390, int32_t p391 +, int32_t p392, int32_t p393, int32_t p394, int32_t p395, int32_t p396, int32_t p397, int32_t p398, int32_t p399 +, int32_t p400, int32_t p401, int32_t p402, int32_t p403, int32_t p404, int32_t p405, int32_t p406, int32_t p407 +, int32_t p408, int32_t p409, int32_t p410, int32_t p411, int32_t p412, int32_t p413, int32_t p414, int32_t p415 +, int32_t p416, int32_t p417, int32_t p418, int32_t p419, int32_t p420, int32_t p421, int32_t p422, int32_t p423 +, int32_t p424, int32_t p425, int32_t p426, int32_t p427, int32_t p428, int32_t p429, int32_t p430, int32_t p431 +, int32_t p432, int32_t p433, int32_t p434, int32_t p435, int32_t p436, int32_t p437, int32_t p438, int32_t p439 +, int32_t p440, int32_t p441, int32_t p442, int32_t p443, int32_t p444, int32_t p445, int32_t p446, int32_t p447 +, int32_t p448, int32_t p449, int32_t p450, int32_t p451, int32_t p452, int32_t p453, int32_t p454, int32_t p455 +, int32_t p456, int32_t p457, int32_t p458, int32_t p459, int32_t p460, int32_t p461, int32_t p462, int32_t p463 +, int32_t p464, int32_t p465, int32_t p466, int32_t p467, int32_t p468, int32_t p469, int32_t p470, int32_t p471 +, int32_t p472, int32_t p473, int32_t p474, int32_t p475, int32_t p476, int32_t p477, int32_t p478, int32_t p479 +, int32_t p480, int32_t p481, int32_t p482, int32_t p483, int32_t p484, int32_t p485, int32_t p486, int32_t p487 +, int32_t p488, int32_t p489, int32_t p490, int32_t p491, int32_t p492, int32_t p493, int32_t p494, int32_t p495 +, int32_t p496, int32_t p497, int32_t p498, int32_t p499, int32_t p500, int32_t p501, int32_t p502, int32_t p503 +, int32_t p504, int32_t p505, int32_t p506, int32_t p507, int32_t p508, int32_t p509, int32_t p510, int32_t p511 +, int32_t p512, int32_t p513, int32_t p514, int32_t p515, int32_t p516, int32_t p517, int32_t p518, int32_t p519 +, int32_t p520, int32_t p521, int32_t p522, int32_t p523, int32_t p524, int32_t p525, int32_t p526, int32_t p527 +, int32_t p528, int32_t p529, int32_t p530, int32_t p531, int32_t p532, int32_t p533, int32_t p534, int32_t p535 +, int32_t p536, int32_t p537, int32_t p538, int32_t p539, int32_t p540, int32_t p541, int32_t p542, int32_t p543 +, int32_t p544, int32_t p545, int32_t p546, int32_t p547, int32_t p548, int32_t p549, int32_t p550, int32_t p551 +, int32_t p552, int32_t p553, int32_t p554, int32_t p555, int32_t p556, int32_t p557, int32_t p558, int32_t p559 +, int32_t p560, int32_t p561, int32_t p562, int32_t p563, int32_t p564, int32_t p565, int32_t p566, int32_t p567 +, int32_t p568, int32_t p569, int32_t p570, int32_t p571, int32_t p572, int32_t p573, int32_t p574, int32_t p575 +, int32_t p576, int32_t p577, int32_t p578, int32_t p579, int32_t p580, int32_t p581, int32_t p582, int32_t p583 +, int32_t p584, int32_t p585, int32_t p586, int32_t p587, int32_t p588, int32_t p589, int32_t p590, int32_t p591 +, int32_t p592, int32_t p593, int32_t p594, int32_t p595, int32_t p596, int32_t p597, int32_t p598, int32_t p599 +, int32_t p600, int32_t p601, int32_t p602, int32_t p603, int32_t p604, int32_t p605, int32_t p606, int32_t p607 +, int32_t p608, int32_t p609, int32_t p610, int32_t p611, int32_t p612, int32_t p613, int32_t p614, int32_t p615 +, int32_t p616, int32_t p617, int32_t p618, int32_t p619, int32_t p620, int32_t p621, int32_t p622, int32_t p623 +, int32_t p624, int32_t p625, int32_t p626, int32_t p627, int32_t p628, int32_t p629, int32_t p630, int32_t p631 +, int32_t p632, int32_t p633, int32_t p634, int32_t p635, int32_t p636, int32_t p637, int32_t p638, int32_t p639 +, int32_t p640, int32_t p641, int32_t p642, int32_t p643, int32_t p644, int32_t p645, int32_t p646, int32_t p647 +, int32_t p648, int32_t p649, int32_t p650, int32_t p651, int32_t p652, int32_t p653, int32_t p654, int32_t p655 +, int32_t p656, int32_t p657, int32_t p658, int32_t p659, int32_t p660, int32_t p661, int32_t p662, int32_t p663 +, int32_t p664, int32_t p665, int32_t p666, int32_t p667, int32_t p668, int32_t p669, int32_t p670, int32_t p671 +, int32_t p672, int32_t p673, int32_t p674, int32_t p675, int32_t p676, int32_t p677, int32_t p678, int32_t p679 +, int32_t p680, int32_t p681, int32_t p682, int32_t p683, int32_t p684, int32_t p685, int32_t p686, int32_t p687 +, int32_t p688, int32_t p689, int32_t p690, int32_t p691, int32_t p692, int32_t p693, int32_t p694, int32_t p695 +, int32_t p696, int32_t p697, int32_t p698, int32_t p699, int32_t p700, int32_t p701, int32_t p702, int32_t p703 +, int32_t p704, int32_t p705, int32_t p706, int32_t p707, int32_t p708, int32_t p709, int32_t p710, int32_t p711 +, int32_t p712, int32_t p713, int32_t p714, int32_t p715, int32_t p716, int32_t p717, int32_t p718, int32_t p719 +, int32_t p720, int32_t p721, int32_t p722, int32_t p723, int32_t p724, int32_t p725, int32_t p726, int32_t p727 +, int32_t p728, int32_t p729, int32_t p730, int32_t p731, int32_t p732, int32_t p733, int32_t p734, int32_t p735 +, int32_t p736, int32_t p737, int32_t p738, int32_t p739, int32_t p740, int32_t p741, int32_t p742, int32_t p743 +, int32_t p744, int32_t p745, int32_t p746, int32_t p747, int32_t p748, int32_t p749, int32_t p750, int32_t p751 +, int32_t p752, int32_t p753, int32_t p754, int32_t p755, int32_t p756, int32_t p757, int32_t p758, int32_t p759 +, int32_t p760, int32_t p761, int32_t p762, int32_t p763, int32_t p764, int32_t p765, int32_t p766, int32_t p767 +, int32_t p768, int32_t p769, int32_t p770, int32_t p771, int32_t p772, int32_t p773, int32_t p774, int32_t p775 +, int32_t p776, int32_t p777, int32_t p778, int32_t p779, int32_t p780, int32_t p781, int32_t p782, int32_t p783 +, int32_t p784, int32_t p785, int32_t p786, int32_t p787, int32_t p788, int32_t p789, int32_t p790, int32_t p791 +, int32_t p792, int32_t p793, int32_t p794, int32_t p795, int32_t p796, int32_t p797, int32_t p798, int32_t p799 +, int32_t p800, int32_t p801, int32_t p802, int32_t p803, int32_t p804, int32_t p805, int32_t p806, int32_t p807 +, int32_t p808, int32_t p809, int32_t p810, int32_t p811, int32_t p812, int32_t p813, int32_t p814, int32_t p815 +, int32_t p816, int32_t p817, int32_t p818, int32_t p819, int32_t p820, int32_t p821, int32_t p822, int32_t p823 +, int32_t p824, int32_t p825, int32_t p826, int32_t p827, int32_t p828, int32_t p829, int32_t p830, int32_t p831 +, int32_t p832, int32_t p833, int32_t p834, int32_t p835, int32_t p836, int32_t p837, int32_t p838, int32_t p839 +, int32_t p840, int32_t p841, int32_t p842, int32_t p843, int32_t p844, int32_t p845, int32_t p846, int32_t p847 +, int32_t p848, int32_t p849, int32_t p850, int32_t p851, int32_t p852, int32_t p853, int32_t p854, int32_t p855 +, int32_t p856, int32_t p857, int32_t p858, int32_t p859, int32_t p860, int32_t p861, int32_t p862, int32_t p863 +, int32_t p864, int32_t p865, int32_t p866, int32_t p867, int32_t p868, int32_t p869, int32_t p870, int32_t p871 +, int32_t p872, int32_t p873, int32_t p874, int32_t p875, int32_t p876, int32_t p877, int32_t p878, int32_t p879 +, int32_t p880, int32_t p881, int32_t p882, int32_t p883, int32_t p884, int32_t p885, int32_t p886, int32_t p887 +, int32_t p888, int32_t p889, int32_t p890, int32_t p891, int32_t p892, int32_t p893, int32_t p894, int32_t p895 +, int32_t p896, int32_t p897, int32_t p898, int32_t p899, int32_t p900, int32_t p901, int32_t p902, int32_t p903 +, int32_t p904, int32_t p905, int32_t p906, int32_t p907, int32_t p908, int32_t p909, int32_t p910, int32_t p911 +, int32_t p912, int32_t p913, int32_t p914, int32_t p915, int32_t p916, int32_t p917, int32_t p918, int32_t p919 +, int32_t p920, int32_t p921, int32_t p922, int32_t p923, int32_t p924, int32_t p925, int32_t p926, int32_t p927 +, int32_t p928, int32_t p929, int32_t p930, int32_t p931, int32_t p932, int32_t p933, int32_t p934, int32_t p935 +, int32_t p936, int32_t p937, int32_t p938, int32_t p939, int32_t p940, int32_t p941, int32_t p942, int32_t p943 +, int32_t p944, int32_t p945, int32_t p946, int32_t p947, int32_t p948, int32_t p949, int32_t p950, int32_t p951 +, int32_t p952, int32_t p953, int32_t p954, int32_t p955, int32_t p956, int32_t p957, int32_t p958, int32_t p959 +, int32_t p960, int32_t p961, int32_t p962, int32_t p963, int32_t p964, int32_t p965, int32_t p966, int32_t p967 +, int32_t p968, int32_t p969, int32_t p970, int32_t p971, int32_t p972, int32_t p973, int32_t p974, int32_t p975 +, int32_t p976, int32_t p977, int32_t p978, int32_t p979, int32_t p980, int32_t p981, int32_t p982, int32_t p983 +, int32_t p984, int32_t p985, int32_t p986, int32_t p987, int32_t p988, int32_t p989, int32_t p990, int32_t p991 +, int32_t p992, int32_t p993, int32_t p994, int32_t p995, int32_t p996, int32_t p997, int32_t p998, int32_t p999 +, int32_t p1000 +) +{ + int32_t x; + x = p0 + p1 + p2 + p3 + p4 + p5 + p6 + p7 + + p8 + p9 + p10 + p11 + p12 + p13 + p14 + p15 + + p16 + p17 + p18 + p19 + p20 + p21 + p22 + p23 + + p24 + p25 + p26 + p27 + p28 + p29 + p30 + p31 + + p32 + p33 + p34 + p35 + p36 + p37 + p38 + p39 + + p40 + p41 + p42 + p43 + p44 + p45 + p46 + p47 + + p48 + p49 + p50 + p51 + p52 + p53 + p54 + p55 + + p56 + p57 + p58 + p59 + p60 + p61 + p62 + p63 + + p64 + p65 + p66 + p67 + p68 + p69 + p70 + p71 + + p72 + p73 + p74 + p75 + p76 + p77 + p78 + p79 + + p80 + p81 + p82 + p83 + p84 + p85 + p86 + p87 + + p88 + p89 + p90 + p91 + p92 + p93 + p94 + p95 + + p96 + p97 + p98 + p99 + p100 + p101 + p102 + p103 + + p104 + p105 + p106 + p107 + p108 + p109 + p110 + p111 + + p112 + p113 + p114 + p115 + p116 + p117 + p118 + p119 + + p120 + p121 + p122 + p123 + p124 + p125 + p126 + p127 + + p128 + p129 + p130 + p131 + p132 + p133 + p134 + p135 + + p136 + p137 + p138 + p139 + p140 + p141 + p142 + p143 + + p144 + p145 + p146 + p147 + p148 + p149 + p150 + p151 + + p152 + p153 + p154 + p155 + p156 + p157 + p158 + p159 + + p160 + p161 + p162 + p163 + p164 + p165 + p166 + p167 + + p168 + p169 + p170 + p171 + p172 + p173 + p174 + p175 + + p176 + p177 + p178 + p179 + p180 + p181 + p182 + p183 + + p184 + p185 + p186 + p187 + p188 + p189 + p190 + p191 + + p192 + p193 + p194 + p195 + p196 + p197 + p198 + p199 + + p200 + p201 + p202 + p203 + p204 + p205 + p206 + p207 + + p208 + p209 + p210 + p211 + p212 + p213 + p214 + p215 + + p216 + p217 + p218 + p219 + p220 + p221 + p222 + p223 + + p224 + p225 + p226 + p227 + p228 + p229 + p230 + p231 + + p232 + p233 + p234 + p235 + p236 + p237 + p238 + p239 + + p240 + p241 + p242 + p243 + p244 + p245 + p246 + p247 + + p248 + p249 + p250 + p251 + p252 + p253 + p254 + p255 + + p256 + p257 + p258 + p259 + p260 + p261 + p262 + p263 + + p264 + p265 + p266 + p267 + p268 + p269 + p270 + p271 + + p272 + p273 + p274 + p275 + p276 + p277 + p278 + p279 + + p280 + p281 + p282 + p283 + p284 + p285 + p286 + p287 + + p288 + p289 + p290 + p291 + p292 + p293 + p294 + p295 + + p296 + p297 + p298 + p299 + p300 + p301 + p302 + p303 + + p304 + p305 + p306 + p307 + p308 + p309 + p310 + p311 + + p312 + p313 + p314 + p315 + p316 + p317 + p318 + p319 + + p320 + p321 + p322 + p323 + p324 + p325 + p326 + p327 + + p328 + p329 + p330 + p331 + p332 + p333 + p334 + p335 + + p336 + p337 + p338 + p339 + p340 + p341 + p342 + p343 + + p344 + p345 + p346 + p347 + p348 + p349 + p350 + p351 + + p352 + p353 + p354 + p355 + p356 + p357 + p358 + p359 + + p360 + p361 + p362 + p363 + p364 + p365 + p366 + p367 + + p368 + p369 + p370 + p371 + p372 + p373 + p374 + p375 + + p376 + p377 + p378 + p379 + p380 + p381 + p382 + p383 + + p384 + p385 + p386 + p387 + p388 + p389 + p390 + p391 + + p392 + p393 + p394 + p395 + p396 + p397 + p398 + p399 + + p400 + p401 + p402 + p403 + p404 + p405 + p406 + p407 + + p408 + p409 + p410 + p411 + p412 + p413 + p414 + p415 + + p416 + p417 + p418 + p419 + p420 + p421 + p422 + p423 + + p424 + p425 + p426 + p427 + p428 + p429 + p430 + p431 + + p432 + p433 + p434 + p435 + p436 + p437 + p438 + p439 + + p440 + p441 + p442 + p443 + p444 + p445 + p446 + p447 + + p448 + p449 + p450 + p451 + p452 + p453 + p454 + p455 + + p456 + p457 + p458 + p459 + p460 + p461 + p462 + p463 + + p464 + p465 + p466 + p467 + p468 + p469 + p470 + p471 + + p472 + p473 + p474 + p475 + p476 + p477 + p478 + p479 + + p480 + p481 + p482 + p483 + p484 + p485 + p486 + p487 + + p488 + p489 + p490 + p491 + p492 + p493 + p494 + p495 + + p496 + p497 + p498 + p499 + p500 + p501 + p502 + p503 + + p504 + p505 + p506 + p507 + p508 + p509 + p510 + p511 + + p512 + p513 + p514 + p515 + p516 + p517 + p518 + p519 + + p520 + p521 + p522 + p523 + p524 + p525 + p526 + p527 + + p528 + p529 + p530 + p531 + p532 + p533 + p534 + p535 + + p536 + p537 + p538 + p539 + p540 + p541 + p542 + p543 + + p544 + p545 + p546 + p547 + p548 + p549 + p550 + p551 + + p552 + p553 + p554 + p555 + p556 + p557 + p558 + p559 + + p560 + p561 + p562 + p563 + p564 + p565 + p566 + p567 + + p568 + p569 + p570 + p571 + p572 + p573 + p574 + p575 + + p576 + p577 + p578 + p579 + p580 + p581 + p582 + p583 + + p584 + p585 + p586 + p587 + p588 + p589 + p590 + p591 + + p592 + p593 + p594 + p595 + p596 + p597 + p598 + p599 + + p600 + p601 + p602 + p603 + p604 + p605 + p606 + p607 + + p608 + p609 + p610 + p611 + p612 + p613 + p614 + p615 + + p616 + p617 + p618 + p619 + p620 + p621 + p622 + p623 + + p624 + p625 + p626 + p627 + p628 + p629 + p630 + p631 + + p632 + p633 + p634 + p635 + p636 + p637 + p638 + p639 + + p640 + p641 + p642 + p643 + p644 + p645 + p646 + p647 + + p648 + p649 + p650 + p651 + p652 + p653 + p654 + p655 + + p656 + p657 + p658 + p659 + p660 + p661 + p662 + p663 + + p664 + p665 + p666 + p667 + p668 + p669 + p670 + p671 + + p672 + p673 + p674 + p675 + p676 + p677 + p678 + p679 + + p680 + p681 + p682 + p683 + p684 + p685 + p686 + p687 + + p688 + p689 + p690 + p691 + p692 + p693 + p694 + p695 + + p696 + p697 + p698 + p699 + p700 + p701 + p702 + p703 + + p704 + p705 + p706 + p707 + p708 + p709 + p710 + p711 + + p712 + p713 + p714 + p715 + p716 + p717 + p718 + p719 + + p720 + p721 + p722 + p723 + p724 + p725 + p726 + p727 + + p728 + p729 + p730 + p731 + p732 + p733 + p734 + p735 + + p736 + p737 + p738 + p739 + p740 + p741 + p742 + p743 + + p744 + p745 + p746 + p747 + p748 + p749 + p750 + p751 + + p752 + p753 + p754 + p755 + p756 + p757 + p758 + p759 + + p760 + p761 + p762 + p763 + p764 + p765 + p766 + p767 + + p768 + p769 + p770 + p771 + p772 + p773 + p774 + p775 + + p776 + p777 + p778 + p779 + p780 + p781 + p782 + p783 + + p784 + p785 + p786 + p787 + p788 + p789 + p790 + p791 + + p792 + p793 + p794 + p795 + p796 + p797 + p798 + p799 + + p800 + p801 + p802 + p803 + p804 + p805 + p806 + p807 + + p808 + p809 + p810 + p811 + p812 + p813 + p814 + p815 + + p816 + p817 + p818 + p819 + p820 + p821 + p822 + p823 + + p824 + p825 + p826 + p827 + p828 + p829 + p830 + p831 + + p832 + p833 + p834 + p835 + p836 + p837 + p838 + p839 + + p840 + p841 + p842 + p843 + p844 + p845 + p846 + p847 + + p848 + p849 + p850 + p851 + p852 + p853 + p854 + p855 + + p856 + p857 + p858 + p859 + p860 + p861 + p862 + p863 + + p864 + p865 + p866 + p867 + p868 + p869 + p870 + p871 + + p872 + p873 + p874 + p875 + p876 + p877 + p878 + p879 + + p880 + p881 + p882 + p883 + p884 + p885 + p886 + p887 + + p888 + p889 + p890 + p891 + p892 + p893 + p894 + p895 + + p896 + p897 + p898 + p899 + p900 + p901 + p902 + p903 + + p904 + p905 + p906 + p907 + p908 + p909 + p910 + p911 + + p912 + p913 + p914 + p915 + p916 + p917 + p918 + p919 + + p920 + p921 + p922 + p923 + p924 + p925 + p926 + p927 + + p928 + p929 + p930 + p931 + p932 + p933 + p934 + p935 + + p936 + p937 + p938 + p939 + p940 + p941 + p942 + p943 + + p944 + p945 + p946 + p947 + p948 + p949 + p950 + p951 + + p952 + p953 + p954 + p955 + p956 + p957 + p958 + p959 + + p960 + p961 + p962 + p963 + p964 + p965 + p966 + p967 + + p968 + p969 + p970 + p971 + p972 + p973 + p974 + p975 + + p976 + p977 + p978 + p979 + p980 + p981 + p982 + p983 + + p984 + p985 + p986 + p987 + p988 + p989 + p990 + p991 + + p992 + p993 + p994 + p995 + p996 + p997 + p998 + p999 + + p1000; + return x; +} + +// clang-format on diff --git a/src/test/app/wasm_fixtures/thousand_params.c b/src/test/app/wasm_fixtures/thousand_params.c new file mode 100644 index 00000000000..d934ca38c83 --- /dev/null +++ b/src/test/app/wasm_fixtures/thousand_params.c @@ -0,0 +1,262 @@ +// clang-format off + +#include + +int32_t test( + int32_t p0, int32_t p1, int32_t p2, int32_t p3, int32_t p4, int32_t p5, int32_t p6, int32_t p7 +, int32_t p8, int32_t p9, int32_t p10, int32_t p11, int32_t p12, int32_t p13, int32_t p14, int32_t p15 +, int32_t p16, int32_t p17, int32_t p18, int32_t p19, int32_t p20, int32_t p21, int32_t p22, int32_t p23 +, int32_t p24, int32_t p25, int32_t p26, int32_t p27, int32_t p28, int32_t p29, int32_t p30, int32_t p31 +, int32_t p32, int32_t p33, int32_t p34, int32_t p35, int32_t p36, int32_t p37, int32_t p38, int32_t p39 +, int32_t p40, int32_t p41, int32_t p42, int32_t p43, int32_t p44, int32_t p45, int32_t p46, int32_t p47 +, int32_t p48, int32_t p49, int32_t p50, int32_t p51, int32_t p52, int32_t p53, int32_t p54, int32_t p55 +, int32_t p56, int32_t p57, int32_t p58, int32_t p59, int32_t p60, int32_t p61, int32_t p62, int32_t p63 +, int32_t p64, int32_t p65, int32_t p66, int32_t p67, int32_t p68, int32_t p69, int32_t p70, int32_t p71 +, int32_t p72, int32_t p73, int32_t p74, int32_t p75, int32_t p76, int32_t p77, int32_t p78, int32_t p79 +, int32_t p80, int32_t p81, int32_t p82, int32_t p83, int32_t p84, int32_t p85, int32_t p86, int32_t p87 +, int32_t p88, int32_t p89, int32_t p90, int32_t p91, int32_t p92, int32_t p93, int32_t p94, int32_t p95 +, int32_t p96, int32_t p97, int32_t p98, int32_t p99, int32_t p100, int32_t p101, int32_t p102, int32_t p103 +, int32_t p104, int32_t p105, int32_t p106, int32_t p107, int32_t p108, int32_t p109, int32_t p110, int32_t p111 +, int32_t p112, int32_t p113, int32_t p114, int32_t p115, int32_t p116, int32_t p117, int32_t p118, int32_t p119 +, int32_t p120, int32_t p121, int32_t p122, int32_t p123, int32_t p124, int32_t p125, int32_t p126, int32_t p127 +, int32_t p128, int32_t p129, int32_t p130, int32_t p131, int32_t p132, int32_t p133, int32_t p134, int32_t p135 +, int32_t p136, int32_t p137, int32_t p138, int32_t p139, int32_t p140, int32_t p141, int32_t p142, int32_t p143 +, int32_t p144, int32_t p145, int32_t p146, int32_t p147, int32_t p148, int32_t p149, int32_t p150, int32_t p151 +, int32_t p152, int32_t p153, int32_t p154, int32_t p155, int32_t p156, int32_t p157, int32_t p158, int32_t p159 +, int32_t p160, int32_t p161, int32_t p162, int32_t p163, int32_t p164, int32_t p165, int32_t p166, int32_t p167 +, int32_t p168, int32_t p169, int32_t p170, int32_t p171, int32_t p172, int32_t p173, int32_t p174, int32_t p175 +, int32_t p176, int32_t p177, int32_t p178, int32_t p179, int32_t p180, int32_t p181, int32_t p182, int32_t p183 +, int32_t p184, int32_t p185, int32_t p186, int32_t p187, int32_t p188, int32_t p189, int32_t p190, int32_t p191 +, int32_t p192, int32_t p193, int32_t p194, int32_t p195, int32_t p196, int32_t p197, int32_t p198, int32_t p199 +, int32_t p200, int32_t p201, int32_t p202, int32_t p203, int32_t p204, int32_t p205, int32_t p206, int32_t p207 +, int32_t p208, int32_t p209, int32_t p210, int32_t p211, int32_t p212, int32_t p213, int32_t p214, int32_t p215 +, int32_t p216, int32_t p217, int32_t p218, int32_t p219, int32_t p220, int32_t p221, int32_t p222, int32_t p223 +, int32_t p224, int32_t p225, int32_t p226, int32_t p227, int32_t p228, int32_t p229, int32_t p230, int32_t p231 +, int32_t p232, int32_t p233, int32_t p234, int32_t p235, int32_t p236, int32_t p237, int32_t p238, int32_t p239 +, int32_t p240, int32_t p241, int32_t p242, int32_t p243, int32_t p244, int32_t p245, int32_t p246, int32_t p247 +, int32_t p248, int32_t p249, int32_t p250, int32_t p251, int32_t p252, int32_t p253, int32_t p254, int32_t p255 +, int32_t p256, int32_t p257, int32_t p258, int32_t p259, int32_t p260, int32_t p261, int32_t p262, int32_t p263 +, int32_t p264, int32_t p265, int32_t p266, int32_t p267, int32_t p268, int32_t p269, int32_t p270, int32_t p271 +, int32_t p272, int32_t p273, int32_t p274, int32_t p275, int32_t p276, int32_t p277, int32_t p278, int32_t p279 +, int32_t p280, int32_t p281, int32_t p282, int32_t p283, int32_t p284, int32_t p285, int32_t p286, int32_t p287 +, int32_t p288, int32_t p289, int32_t p290, int32_t p291, int32_t p292, int32_t p293, int32_t p294, int32_t p295 +, int32_t p296, int32_t p297, int32_t p298, int32_t p299, int32_t p300, int32_t p301, int32_t p302, int32_t p303 +, int32_t p304, int32_t p305, int32_t p306, int32_t p307, int32_t p308, int32_t p309, int32_t p310, int32_t p311 +, int32_t p312, int32_t p313, int32_t p314, int32_t p315, int32_t p316, int32_t p317, int32_t p318, int32_t p319 +, int32_t p320, int32_t p321, int32_t p322, int32_t p323, int32_t p324, int32_t p325, int32_t p326, int32_t p327 +, int32_t p328, int32_t p329, int32_t p330, int32_t p331, int32_t p332, int32_t p333, int32_t p334, int32_t p335 +, int32_t p336, int32_t p337, int32_t p338, int32_t p339, int32_t p340, int32_t p341, int32_t p342, int32_t p343 +, int32_t p344, int32_t p345, int32_t p346, int32_t p347, int32_t p348, int32_t p349, int32_t p350, int32_t p351 +, int32_t p352, int32_t p353, int32_t p354, int32_t p355, int32_t p356, int32_t p357, int32_t p358, int32_t p359 +, int32_t p360, int32_t p361, int32_t p362, int32_t p363, int32_t p364, int32_t p365, int32_t p366, int32_t p367 +, int32_t p368, int32_t p369, int32_t p370, int32_t p371, int32_t p372, int32_t p373, int32_t p374, int32_t p375 +, int32_t p376, int32_t p377, int32_t p378, int32_t p379, int32_t p380, int32_t p381, int32_t p382, int32_t p383 +, int32_t p384, int32_t p385, int32_t p386, int32_t p387, int32_t p388, int32_t p389, int32_t p390, int32_t p391 +, int32_t p392, int32_t p393, int32_t p394, int32_t p395, int32_t p396, int32_t p397, int32_t p398, int32_t p399 +, int32_t p400, int32_t p401, int32_t p402, int32_t p403, int32_t p404, int32_t p405, int32_t p406, int32_t p407 +, int32_t p408, int32_t p409, int32_t p410, int32_t p411, int32_t p412, int32_t p413, int32_t p414, int32_t p415 +, int32_t p416, int32_t p417, int32_t p418, int32_t p419, int32_t p420, int32_t p421, int32_t p422, int32_t p423 +, int32_t p424, int32_t p425, int32_t p426, int32_t p427, int32_t p428, int32_t p429, int32_t p430, int32_t p431 +, int32_t p432, int32_t p433, int32_t p434, int32_t p435, int32_t p436, int32_t p437, int32_t p438, int32_t p439 +, int32_t p440, int32_t p441, int32_t p442, int32_t p443, int32_t p444, int32_t p445, int32_t p446, int32_t p447 +, int32_t p448, int32_t p449, int32_t p450, int32_t p451, int32_t p452, int32_t p453, int32_t p454, int32_t p455 +, int32_t p456, int32_t p457, int32_t p458, int32_t p459, int32_t p460, int32_t p461, int32_t p462, int32_t p463 +, int32_t p464, int32_t p465, int32_t p466, int32_t p467, int32_t p468, int32_t p469, int32_t p470, int32_t p471 +, int32_t p472, int32_t p473, int32_t p474, int32_t p475, int32_t p476, int32_t p477, int32_t p478, int32_t p479 +, int32_t p480, int32_t p481, int32_t p482, int32_t p483, int32_t p484, int32_t p485, int32_t p486, int32_t p487 +, int32_t p488, int32_t p489, int32_t p490, int32_t p491, int32_t p492, int32_t p493, int32_t p494, int32_t p495 +, int32_t p496, int32_t p497, int32_t p498, int32_t p499, int32_t p500, int32_t p501, int32_t p502, int32_t p503 +, int32_t p504, int32_t p505, int32_t p506, int32_t p507, int32_t p508, int32_t p509, int32_t p510, int32_t p511 +, int32_t p512, int32_t p513, int32_t p514, int32_t p515, int32_t p516, int32_t p517, int32_t p518, int32_t p519 +, int32_t p520, int32_t p521, int32_t p522, int32_t p523, int32_t p524, int32_t p525, int32_t p526, int32_t p527 +, int32_t p528, int32_t p529, int32_t p530, int32_t p531, int32_t p532, int32_t p533, int32_t p534, int32_t p535 +, int32_t p536, int32_t p537, int32_t p538, int32_t p539, int32_t p540, int32_t p541, int32_t p542, int32_t p543 +, int32_t p544, int32_t p545, int32_t p546, int32_t p547, int32_t p548, int32_t p549, int32_t p550, int32_t p551 +, int32_t p552, int32_t p553, int32_t p554, int32_t p555, int32_t p556, int32_t p557, int32_t p558, int32_t p559 +, int32_t p560, int32_t p561, int32_t p562, int32_t p563, int32_t p564, int32_t p565, int32_t p566, int32_t p567 +, int32_t p568, int32_t p569, int32_t p570, int32_t p571, int32_t p572, int32_t p573, int32_t p574, int32_t p575 +, int32_t p576, int32_t p577, int32_t p578, int32_t p579, int32_t p580, int32_t p581, int32_t p582, int32_t p583 +, int32_t p584, int32_t p585, int32_t p586, int32_t p587, int32_t p588, int32_t p589, int32_t p590, int32_t p591 +, int32_t p592, int32_t p593, int32_t p594, int32_t p595, int32_t p596, int32_t p597, int32_t p598, int32_t p599 +, int32_t p600, int32_t p601, int32_t p602, int32_t p603, int32_t p604, int32_t p605, int32_t p606, int32_t p607 +, int32_t p608, int32_t p609, int32_t p610, int32_t p611, int32_t p612, int32_t p613, int32_t p614, int32_t p615 +, int32_t p616, int32_t p617, int32_t p618, int32_t p619, int32_t p620, int32_t p621, int32_t p622, int32_t p623 +, int32_t p624, int32_t p625, int32_t p626, int32_t p627, int32_t p628, int32_t p629, int32_t p630, int32_t p631 +, int32_t p632, int32_t p633, int32_t p634, int32_t p635, int32_t p636, int32_t p637, int32_t p638, int32_t p639 +, int32_t p640, int32_t p641, int32_t p642, int32_t p643, int32_t p644, int32_t p645, int32_t p646, int32_t p647 +, int32_t p648, int32_t p649, int32_t p650, int32_t p651, int32_t p652, int32_t p653, int32_t p654, int32_t p655 +, int32_t p656, int32_t p657, int32_t p658, int32_t p659, int32_t p660, int32_t p661, int32_t p662, int32_t p663 +, int32_t p664, int32_t p665, int32_t p666, int32_t p667, int32_t p668, int32_t p669, int32_t p670, int32_t p671 +, int32_t p672, int32_t p673, int32_t p674, int32_t p675, int32_t p676, int32_t p677, int32_t p678, int32_t p679 +, int32_t p680, int32_t p681, int32_t p682, int32_t p683, int32_t p684, int32_t p685, int32_t p686, int32_t p687 +, int32_t p688, int32_t p689, int32_t p690, int32_t p691, int32_t p692, int32_t p693, int32_t p694, int32_t p695 +, int32_t p696, int32_t p697, int32_t p698, int32_t p699, int32_t p700, int32_t p701, int32_t p702, int32_t p703 +, int32_t p704, int32_t p705, int32_t p706, int32_t p707, int32_t p708, int32_t p709, int32_t p710, int32_t p711 +, int32_t p712, int32_t p713, int32_t p714, int32_t p715, int32_t p716, int32_t p717, int32_t p718, int32_t p719 +, int32_t p720, int32_t p721, int32_t p722, int32_t p723, int32_t p724, int32_t p725, int32_t p726, int32_t p727 +, int32_t p728, int32_t p729, int32_t p730, int32_t p731, int32_t p732, int32_t p733, int32_t p734, int32_t p735 +, int32_t p736, int32_t p737, int32_t p738, int32_t p739, int32_t p740, int32_t p741, int32_t p742, int32_t p743 +, int32_t p744, int32_t p745, int32_t p746, int32_t p747, int32_t p748, int32_t p749, int32_t p750, int32_t p751 +, int32_t p752, int32_t p753, int32_t p754, int32_t p755, int32_t p756, int32_t p757, int32_t p758, int32_t p759 +, int32_t p760, int32_t p761, int32_t p762, int32_t p763, int32_t p764, int32_t p765, int32_t p766, int32_t p767 +, int32_t p768, int32_t p769, int32_t p770, int32_t p771, int32_t p772, int32_t p773, int32_t p774, int32_t p775 +, int32_t p776, int32_t p777, int32_t p778, int32_t p779, int32_t p780, int32_t p781, int32_t p782, int32_t p783 +, int32_t p784, int32_t p785, int32_t p786, int32_t p787, int32_t p788, int32_t p789, int32_t p790, int32_t p791 +, int32_t p792, int32_t p793, int32_t p794, int32_t p795, int32_t p796, int32_t p797, int32_t p798, int32_t p799 +, int32_t p800, int32_t p801, int32_t p802, int32_t p803, int32_t p804, int32_t p805, int32_t p806, int32_t p807 +, int32_t p808, int32_t p809, int32_t p810, int32_t p811, int32_t p812, int32_t p813, int32_t p814, int32_t p815 +, int32_t p816, int32_t p817, int32_t p818, int32_t p819, int32_t p820, int32_t p821, int32_t p822, int32_t p823 +, int32_t p824, int32_t p825, int32_t p826, int32_t p827, int32_t p828, int32_t p829, int32_t p830, int32_t p831 +, int32_t p832, int32_t p833, int32_t p834, int32_t p835, int32_t p836, int32_t p837, int32_t p838, int32_t p839 +, int32_t p840, int32_t p841, int32_t p842, int32_t p843, int32_t p844, int32_t p845, int32_t p846, int32_t p847 +, int32_t p848, int32_t p849, int32_t p850, int32_t p851, int32_t p852, int32_t p853, int32_t p854, int32_t p855 +, int32_t p856, int32_t p857, int32_t p858, int32_t p859, int32_t p860, int32_t p861, int32_t p862, int32_t p863 +, int32_t p864, int32_t p865, int32_t p866, int32_t p867, int32_t p868, int32_t p869, int32_t p870, int32_t p871 +, int32_t p872, int32_t p873, int32_t p874, int32_t p875, int32_t p876, int32_t p877, int32_t p878, int32_t p879 +, int32_t p880, int32_t p881, int32_t p882, int32_t p883, int32_t p884, int32_t p885, int32_t p886, int32_t p887 +, int32_t p888, int32_t p889, int32_t p890, int32_t p891, int32_t p892, int32_t p893, int32_t p894, int32_t p895 +, int32_t p896, int32_t p897, int32_t p898, int32_t p899, int32_t p900, int32_t p901, int32_t p902, int32_t p903 +, int32_t p904, int32_t p905, int32_t p906, int32_t p907, int32_t p908, int32_t p909, int32_t p910, int32_t p911 +, int32_t p912, int32_t p913, int32_t p914, int32_t p915, int32_t p916, int32_t p917, int32_t p918, int32_t p919 +, int32_t p920, int32_t p921, int32_t p922, int32_t p923, int32_t p924, int32_t p925, int32_t p926, int32_t p927 +, int32_t p928, int32_t p929, int32_t p930, int32_t p931, int32_t p932, int32_t p933, int32_t p934, int32_t p935 +, int32_t p936, int32_t p937, int32_t p938, int32_t p939, int32_t p940, int32_t p941, int32_t p942, int32_t p943 +, int32_t p944, int32_t p945, int32_t p946, int32_t p947, int32_t p948, int32_t p949, int32_t p950, int32_t p951 +, int32_t p952, int32_t p953, int32_t p954, int32_t p955, int32_t p956, int32_t p957, int32_t p958, int32_t p959 +, int32_t p960, int32_t p961, int32_t p962, int32_t p963, int32_t p964, int32_t p965, int32_t p966, int32_t p967 +, int32_t p968, int32_t p969, int32_t p970, int32_t p971, int32_t p972, int32_t p973, int32_t p974, int32_t p975 +, int32_t p976, int32_t p977, int32_t p978, int32_t p979, int32_t p980, int32_t p981, int32_t p982, int32_t p983 +, int32_t p984, int32_t p985, int32_t p986, int32_t p987, int32_t p988, int32_t p989, int32_t p990, int32_t p991 +, int32_t p992, int32_t p993, int32_t p994, int32_t p995, int32_t p996, int32_t p997, int32_t p998, int32_t p999 +) +{ + int32_t x; + x = p0 + p1 + p2 + p3 + p4 + p5 + p6 + p7 + + p8 + p9 + p10 + p11 + p12 + p13 + p14 + p15 + + p16 + p17 + p18 + p19 + p20 + p21 + p22 + p23 + + p24 + p25 + p26 + p27 + p28 + p29 + p30 + p31 + + p32 + p33 + p34 + p35 + p36 + p37 + p38 + p39 + + p40 + p41 + p42 + p43 + p44 + p45 + p46 + p47 + + p48 + p49 + p50 + p51 + p52 + p53 + p54 + p55 + + p56 + p57 + p58 + p59 + p60 + p61 + p62 + p63 + + p64 + p65 + p66 + p67 + p68 + p69 + p70 + p71 + + p72 + p73 + p74 + p75 + p76 + p77 + p78 + p79 + + p80 + p81 + p82 + p83 + p84 + p85 + p86 + p87 + + p88 + p89 + p90 + p91 + p92 + p93 + p94 + p95 + + p96 + p97 + p98 + p99 + p100 + p101 + p102 + p103 + + p104 + p105 + p106 + p107 + p108 + p109 + p110 + p111 + + p112 + p113 + p114 + p115 + p116 + p117 + p118 + p119 + + p120 + p121 + p122 + p123 + p124 + p125 + p126 + p127 + + p128 + p129 + p130 + p131 + p132 + p133 + p134 + p135 + + p136 + p137 + p138 + p139 + p140 + p141 + p142 + p143 + + p144 + p145 + p146 + p147 + p148 + p149 + p150 + p151 + + p152 + p153 + p154 + p155 + p156 + p157 + p158 + p159 + + p160 + p161 + p162 + p163 + p164 + p165 + p166 + p167 + + p168 + p169 + p170 + p171 + p172 + p173 + p174 + p175 + + p176 + p177 + p178 + p179 + p180 + p181 + p182 + p183 + + p184 + p185 + p186 + p187 + p188 + p189 + p190 + p191 + + p192 + p193 + p194 + p195 + p196 + p197 + p198 + p199 + + p200 + p201 + p202 + p203 + p204 + p205 + p206 + p207 + + p208 + p209 + p210 + p211 + p212 + p213 + p214 + p215 + + p216 + p217 + p218 + p219 + p220 + p221 + p222 + p223 + + p224 + p225 + p226 + p227 + p228 + p229 + p230 + p231 + + p232 + p233 + p234 + p235 + p236 + p237 + p238 + p239 + + p240 + p241 + p242 + p243 + p244 + p245 + p246 + p247 + + p248 + p249 + p250 + p251 + p252 + p253 + p254 + p255 + + p256 + p257 + p258 + p259 + p260 + p261 + p262 + p263 + + p264 + p265 + p266 + p267 + p268 + p269 + p270 + p271 + + p272 + p273 + p274 + p275 + p276 + p277 + p278 + p279 + + p280 + p281 + p282 + p283 + p284 + p285 + p286 + p287 + + p288 + p289 + p290 + p291 + p292 + p293 + p294 + p295 + + p296 + p297 + p298 + p299 + p300 + p301 + p302 + p303 + + p304 + p305 + p306 + p307 + p308 + p309 + p310 + p311 + + p312 + p313 + p314 + p315 + p316 + p317 + p318 + p319 + + p320 + p321 + p322 + p323 + p324 + p325 + p326 + p327 + + p328 + p329 + p330 + p331 + p332 + p333 + p334 + p335 + + p336 + p337 + p338 + p339 + p340 + p341 + p342 + p343 + + p344 + p345 + p346 + p347 + p348 + p349 + p350 + p351 + + p352 + p353 + p354 + p355 + p356 + p357 + p358 + p359 + + p360 + p361 + p362 + p363 + p364 + p365 + p366 + p367 + + p368 + p369 + p370 + p371 + p372 + p373 + p374 + p375 + + p376 + p377 + p378 + p379 + p380 + p381 + p382 + p383 + + p384 + p385 + p386 + p387 + p388 + p389 + p390 + p391 + + p392 + p393 + p394 + p395 + p396 + p397 + p398 + p399 + + p400 + p401 + p402 + p403 + p404 + p405 + p406 + p407 + + p408 + p409 + p410 + p411 + p412 + p413 + p414 + p415 + + p416 + p417 + p418 + p419 + p420 + p421 + p422 + p423 + + p424 + p425 + p426 + p427 + p428 + p429 + p430 + p431 + + p432 + p433 + p434 + p435 + p436 + p437 + p438 + p439 + + p440 + p441 + p442 + p443 + p444 + p445 + p446 + p447 + + p448 + p449 + p450 + p451 + p452 + p453 + p454 + p455 + + p456 + p457 + p458 + p459 + p460 + p461 + p462 + p463 + + p464 + p465 + p466 + p467 + p468 + p469 + p470 + p471 + + p472 + p473 + p474 + p475 + p476 + p477 + p478 + p479 + + p480 + p481 + p482 + p483 + p484 + p485 + p486 + p487 + + p488 + p489 + p490 + p491 + p492 + p493 + p494 + p495 + + p496 + p497 + p498 + p499 + p500 + p501 + p502 + p503 + + p504 + p505 + p506 + p507 + p508 + p509 + p510 + p511 + + p512 + p513 + p514 + p515 + p516 + p517 + p518 + p519 + + p520 + p521 + p522 + p523 + p524 + p525 + p526 + p527 + + p528 + p529 + p530 + p531 + p532 + p533 + p534 + p535 + + p536 + p537 + p538 + p539 + p540 + p541 + p542 + p543 + + p544 + p545 + p546 + p547 + p548 + p549 + p550 + p551 + + p552 + p553 + p554 + p555 + p556 + p557 + p558 + p559 + + p560 + p561 + p562 + p563 + p564 + p565 + p566 + p567 + + p568 + p569 + p570 + p571 + p572 + p573 + p574 + p575 + + p576 + p577 + p578 + p579 + p580 + p581 + p582 + p583 + + p584 + p585 + p586 + p587 + p588 + p589 + p590 + p591 + + p592 + p593 + p594 + p595 + p596 + p597 + p598 + p599 + + p600 + p601 + p602 + p603 + p604 + p605 + p606 + p607 + + p608 + p609 + p610 + p611 + p612 + p613 + p614 + p615 + + p616 + p617 + p618 + p619 + p620 + p621 + p622 + p623 + + p624 + p625 + p626 + p627 + p628 + p629 + p630 + p631 + + p632 + p633 + p634 + p635 + p636 + p637 + p638 + p639 + + p640 + p641 + p642 + p643 + p644 + p645 + p646 + p647 + + p648 + p649 + p650 + p651 + p652 + p653 + p654 + p655 + + p656 + p657 + p658 + p659 + p660 + p661 + p662 + p663 + + p664 + p665 + p666 + p667 + p668 + p669 + p670 + p671 + + p672 + p673 + p674 + p675 + p676 + p677 + p678 + p679 + + p680 + p681 + p682 + p683 + p684 + p685 + p686 + p687 + + p688 + p689 + p690 + p691 + p692 + p693 + p694 + p695 + + p696 + p697 + p698 + p699 + p700 + p701 + p702 + p703 + + p704 + p705 + p706 + p707 + p708 + p709 + p710 + p711 + + p712 + p713 + p714 + p715 + p716 + p717 + p718 + p719 + + p720 + p721 + p722 + p723 + p724 + p725 + p726 + p727 + + p728 + p729 + p730 + p731 + p732 + p733 + p734 + p735 + + p736 + p737 + p738 + p739 + p740 + p741 + p742 + p743 + + p744 + p745 + p746 + p747 + p748 + p749 + p750 + p751 + + p752 + p753 + p754 + p755 + p756 + p757 + p758 + p759 + + p760 + p761 + p762 + p763 + p764 + p765 + p766 + p767 + + p768 + p769 + p770 + p771 + p772 + p773 + p774 + p775 + + p776 + p777 + p778 + p779 + p780 + p781 + p782 + p783 + + p784 + p785 + p786 + p787 + p788 + p789 + p790 + p791 + + p792 + p793 + p794 + p795 + p796 + p797 + p798 + p799 + + p800 + p801 + p802 + p803 + p804 + p805 + p806 + p807 + + p808 + p809 + p810 + p811 + p812 + p813 + p814 + p815 + + p816 + p817 + p818 + p819 + p820 + p821 + p822 + p823 + + p824 + p825 + p826 + p827 + p828 + p829 + p830 + p831 + + p832 + p833 + p834 + p835 + p836 + p837 + p838 + p839 + + p840 + p841 + p842 + p843 + p844 + p845 + p846 + p847 + + p848 + p849 + p850 + p851 + p852 + p853 + p854 + p855 + + p856 + p857 + p858 + p859 + p860 + p861 + p862 + p863 + + p864 + p865 + p866 + p867 + p868 + p869 + p870 + p871 + + p872 + p873 + p874 + p875 + p876 + p877 + p878 + p879 + + p880 + p881 + p882 + p883 + p884 + p885 + p886 + p887 + + p888 + p889 + p890 + p891 + p892 + p893 + p894 + p895 + + p896 + p897 + p898 + p899 + p900 + p901 + p902 + p903 + + p904 + p905 + p906 + p907 + p908 + p909 + p910 + p911 + + p912 + p913 + p914 + p915 + p916 + p917 + p918 + p919 + + p920 + p921 + p922 + p923 + p924 + p925 + p926 + p927 + + p928 + p929 + p930 + p931 + p932 + p933 + p934 + p935 + + p936 + p937 + p938 + p939 + p940 + p941 + p942 + p943 + + p944 + p945 + p946 + p947 + p948 + p949 + p950 + p951 + + p952 + p953 + p954 + p955 + p956 + p957 + p958 + p959 + + p960 + p961 + p962 + p963 + p964 + p965 + p966 + p967 + + p968 + p969 + p970 + p971 + p972 + p973 + p974 + p975 + + p976 + p977 + p978 + p979 + p980 + p981 + p982 + p983 + + p984 + p985 + p986 + p987 + p988 + p989 + p990 + p991 + + p992 + p993 + p994 + p995 + p996 + p997 + p998 + p999; + return x; +} + +// clang-format on diff --git a/src/test/app/wasm_fixtures/updateData.c b/src/test/app/wasm_fixtures/updateData.c new file mode 100644 index 00000000000..0c506474922 --- /dev/null +++ b/src/test/app/wasm_fixtures/updateData.c @@ -0,0 +1,11 @@ +#include + +int32_t update_data(uint8_t const *, int32_t); + +int finish() +{ + uint8_t buf[] = "Data"; + update_data(buf, sizeof(buf) - 1); + + return -256; +} diff --git a/src/test/app/wasm_fixtures/wat/custom_page_sizes.wat b/src/test/app/wasm_fixtures/wat/custom_page_sizes.wat new file mode 100644 index 00000000000..a9d404fadc0 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/custom_page_sizes.wat @@ -0,0 +1,13 @@ +(module + ;; Define a memory with 1 initial page. + ;; CRITICAL: We explicitly set the page size to 1 kilobyte. + ;; Standard Wasm implies (pagesize 65536). + (memory 1 (pagesize 1024)) + + (func $finish (result i32) + ;; If this module instantiates, the runtime accepted the custom page size. + i32.const 1 + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/deep_recursion.wat b/src/test/app/wasm_fixtures/wat/deep_recursion.wat new file mode 100644 index 00000000000..8e1f02c0012 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/deep_recursion.wat @@ -0,0 +1,29 @@ +(module + ;; Define a Mutable Global Variable to act as our counter. + ;; We initialize it to 1,000,000. + (global $counter (mut i32) (i32.const 1000000)) + + (func $finish (result i32) + ;; 1. Check if counter == 0 (Base Case) + global.get $counter + i32.eqz + if + ;; If counter is 0, we are done. Return 1. + i32.const 1 + return + end + + ;; 2. Decrement the Global Counter + global.get $counter + i32.const 1 + i32.sub + global.set $counter + + ;; 3. Recursive Step: Call SELF + ;; This puts an i32 (1) on the stack when it returns. + call $finish + ) + + ;; Export the only function we have + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/functions_5k.zip b/src/test/app/wasm_fixtures/wat/functions_5k.zip new file mode 100644 index 00000000000..e2617605455 Binary files /dev/null and b/src/test/app/wasm_fixtures/wat/functions_5k.zip differ diff --git a/src/test/app/wasm_fixtures/wat/locals_10k.zip b/src/test/app/wasm_fixtures/wat/locals_10k.zip new file mode 100644 index 00000000000..22aad2db51c Binary files /dev/null and b/src/test/app/wasm_fixtures/wat/locals_10k.zip differ diff --git a/src/test/app/wasm_fixtures/wat/memory64.wat b/src/test/app/wasm_fixtures/wat/memory64.wat new file mode 100644 index 00000000000..02c8f4f5901 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory64.wat @@ -0,0 +1,21 @@ +(module + ;; Define a 64-bit memory (index type i64) + ;; Start with 1 page. + (memory i64 1) + + (func $finish (result i32) + ;; 1. Perform a store using a 64-bit address. + ;; Even if the value is small (0), the type MUST be i64. + i64.const 0 ;; Address (64-bit) + i32.const 42 ;; Value (32-bit) + i32.store8 ;; Opcode doesn't change, but validation rules do. + + ;; 2. check memory size + ;; memory.size now returns an i64. + memory.size + i64.const 1 + i64.eq ;; Returns i32 (1 if true) + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_end_of_word_over_limit.wat b/src/test/app/wasm_fixtures/wat/memory_end_of_word_over_limit.wat new file mode 100644 index 00000000000..dc5427563fc --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_end_of_word_over_limit.wat @@ -0,0 +1,28 @@ +(module + ;; 1. Define Memory: 1 Page = 64KB = 65,536 bytes + (memory 1) + + ;; Export memory so the host can inspect it if needed + (export "memory" (memory 0)) + + (func $test_straddle (result i32) + ;; Push the address onto the stack. + ;; 65534 is valid, but it is only 2 bytes away from the end. + i32.const 65534 + + ;; Attempt to load an i32 (4 bytes) from that address. + ;; This requires bytes 65534, 65535, 65536, and 65537. + ;; Since 65536 is the first invalid byte, this MUST trap. + i32.load + + ;; Clean up the stack. + ;; The load pushed a value, but we don't care what it is. + drop + + ;; Return 1 to signal "I survived the memory access" + i32.const 1 + ) + + ;; Export the function so you can call it from your host (JS, Python, etc.) + (export "finish" (func $test_straddle)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_grow_0_page_more_than_8MB.wat b/src/test/app/wasm_fixtures/wat/memory_grow_0_page_more_than_8MB.wat new file mode 100644 index 00000000000..d9c149530f8 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_grow_0_page_more_than_8MB.wat @@ -0,0 +1,29 @@ +(module + ;; Start at your limit: 128 pages (8MB) + (memory 128) + (export "memory" (memory 0)) + + (func $try_grow_beyond_limit (result i32) + ;; Attempt to grow by 0 page + i32.const 0 + memory.grow + + ;; memory.grow returns: + ;; -1 if the growth failed (Correct behavior for your limit) + ;; 128 (old size) if growth succeeded (Means limit was bypassed) + + ;; Check if result == -1 + i32.const -1 + i32.eq + if + ;; Growth FAILED (Host blocked it). Return -1. + i32.const -1 + return + end + + ;; Growth SUCCEEDED (Host allowed it). Return 1. + i32.const 1 + ) + + (export "finish" (func $try_grow_beyond_limit)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_grow_0_to_1.wat b/src/test/app/wasm_fixtures/wat/memory_grow_0_to_1.wat new file mode 100644 index 00000000000..ff822ffbdce --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_grow_0_to_1.wat @@ -0,0 +1,26 @@ +(module + ;; 1. Define Memory: Start with 0 pages + (memory 0) + + ;; Export memory to host + (export "memory" (memory 0)) + + (func $grow_from_zero (result i32) + ;; We have 0 pages. We want to add 1 page. + ;; Push delta (1) onto stack. + i32.const 1 + + ;; Grow the memory. + ;; If successful: memory becomes 64KB, returns old size (0). + ;; If failed: memory stays 0, returns -1. + memory.grow + + ;; Drop the return value of memory.grow + drop + + ;; Return 1 (as requested) + i32.const 1 + ) + + (export "finish" (func $grow_from_zero)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_grow_1_page_more_than_8MB.wat b/src/test/app/wasm_fixtures/wat/memory_grow_1_page_more_than_8MB.wat new file mode 100644 index 00000000000..4a245a521c2 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_grow_1_page_more_than_8MB.wat @@ -0,0 +1,29 @@ +(module + ;; Start at your limit: 128 pages (8MB) + (memory 128) + (export "memory" (memory 0)) + + (func $try_grow_beyond_limit (result i32) + ;; Attempt to grow by 1 page + i32.const 1 + memory.grow + + ;; memory.grow returns: + ;; -1 if the growth failed (Correct behavior for your limit) + ;; 128 (old size) if growth succeeded (Means limit was bypassed) + + ;; Check if result == -1 + i32.const -1 + i32.eq + if + ;; Growth FAILED (Host blocked it). Return -1. + i32.const -1 + return + end + + ;; Growth SUCCEEDED (Host allowed it). Return 1. + i32.const 1 + ) + + (export "finish" (func $try_grow_beyond_limit)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_grow_1_to_0.wat b/src/test/app/wasm_fixtures/wat/memory_grow_1_to_0.wat new file mode 100644 index 00000000000..fa25154f099 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_grow_1_to_0.wat @@ -0,0 +1,33 @@ +(module + ;; 1. Define Memory: Start with 1 page (64KB) + (memory 1) + + ;; Export memory to host + (export "memory" (memory 0)) + + (func $grow_negative (result i32) + ;; The user pushed -1. In Wasm, this is interpreted as unsigned MAX_UINT32. + ;; This is requesting to add 4,294,967,295 pages (approx 256 TB). + ;; A secure runtime MUST fail this request (return -1) without crashing. + i32.const -1 + + ;; Grow the memory. + ;; Returns: old_size if success, -1 if failure. + memory.grow + + ;; Check if result == -1 (Failure) + i32.const -1 + i32.eq + if + ;; If memory.grow returned -1, we return -1 to signal "Correctly failed". + i32.const -1 + return + end + + ;; If we are here, memory.grow somehow SUCCEEDED (Vulnerability). + ;; We return 1 to signal "Unexpected Success". + i32.const 1 + ) + + (export "finish" (func $grow_negative)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_init_1_page_more_than_8MB.wat b/src/test/app/wasm_fixtures/wat/memory_init_1_page_more_than_8MB.wat new file mode 100644 index 00000000000..5fd1d09ed14 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_init_1_page_more_than_8MB.wat @@ -0,0 +1,27 @@ +(module + ;; Define memory: 129 pages (> 8MB limit) min, 129 pages max + (memory 129 129) + + ;; Export memory so host can verify size + (export "memory" (memory 0)) + + ;; access last byte of 8MB limit + (func $access_last_byte (result i32) + ;; Math: 128 pages * 64,536 bytes/page = 8,388,608 bytes + ;; Valid indices: 0 to 8,388,607 + + ;; Push the address of the LAST valid byte + i32.const 8388607 + + ;; Load byte from that address + i32.load8_u + + ;; Drop the value (we don't care what it is, just that we could read it) + drop + + ;; Return 1 to indicate success + i32.const 1 + ) + + (export "finish" (func $access_last_byte)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_last_byte_of_8MB.wat b/src/test/app/wasm_fixtures/wat/memory_last_byte_of_8MB.wat new file mode 100644 index 00000000000..a59ec062947 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_last_byte_of_8MB.wat @@ -0,0 +1,26 @@ +(module + ;; Define memory: 128 pages (8MB) min, 128 pages max + (memory 128 128) + + ;; Export memory so host can verify size + (export "memory" (memory 0)) + + (func $access_last_byte (result i32) + ;; Math: 128 pages * 64,536 bytes/page = 8,388,608 bytes + ;; Valid indices: 0 to 8,388,607 + + ;; Push the address of the LAST valid byte + i32.const 8388607 + + ;; Load byte from that address + i32.load8_u + + ;; Drop the value (we don't care what it is, just that we could read it) + drop + + ;; Return 1 to indicate success + i32.const 1 + ) + + (export "finish" (func $access_last_byte)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_negative_address.wat b/src/test/app/wasm_fixtures/wat/memory_negative_address.wat new file mode 100644 index 00000000000..02c9cb5a3bb --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_negative_address.wat @@ -0,0 +1,23 @@ +(module + ;; Define memory: 128 pages (8MB) min, 128 pages max + (memory 128 128) + + ;; Export memory so host can verify size + (export "memory" (memory 0)) + + (func $access_last_byte (result i32) + ;; Push a negative address + i32.const -1 + + ;; Load byte from that address + i32.load8_u + + ;; Drop the value + drop + + ;; Return 1 to indicate success + i32.const 1 + ) + + (export "finish" (func $access_last_byte)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_offset_over_limit.wat b/src/test/app/wasm_fixtures/wat/memory_offset_over_limit.wat new file mode 100644 index 00000000000..d502ddf2919 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_offset_over_limit.wat @@ -0,0 +1,27 @@ +(module + ;; 1. Define Memory: 1 Page = 64KB + (memory 1) + + (export "memory" (memory 0)) + + (func $test_offset_overflow (result i32) + ;; 1. Push the base address onto the stack. + ;; We use '0', which is the safest, most valid address possible. + i32.const 0 + + ;; 2. Attempt to load using a static offset. + ;; syntax: i32.load offset=N align=N + ;; We set the offset to 65536 (the size of the memory). + ;; The effective address becomes 0 + 65536 = 65536. + i32.load offset=65536 + + ;; Clean up the stack. + ;; The load pushed a value, but we don't care what it is. + drop + + ;; Return 1 to signal "I survived the memory access" + i32.const 1 + ) + + (export "finish" (func $test_offset_overflow)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_pointer_at_limit.wat b/src/test/app/wasm_fixtures/wat/memory_pointer_at_limit.wat new file mode 100644 index 00000000000..c72cd437398 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_pointer_at_limit.wat @@ -0,0 +1,22 @@ +(module + ;; Define 1 page of memory (64KB = 65,536 bytes) + (memory 1) + + (func $read_edge (result i32) + ;; Push the index of the LAST valid byte + i32.const 65535 + + ;; Load 1 byte (unsigned) + i32.load8_u + + ;; Clean up the stack. + ;; The load pushed a value, but we don't care what it is. + drop + + ;; Return 1 to signal "I survived the memory access" + i32.const 1 + ) + + ;; Export as "finish" as requested + (export "finish" (func $read_edge)) +) diff --git a/src/test/app/wasm_fixtures/wat/memory_pointer_over_limit.wat b/src/test/app/wasm_fixtures/wat/memory_pointer_over_limit.wat new file mode 100644 index 00000000000..12f6ea01a22 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/memory_pointer_over_limit.wat @@ -0,0 +1,23 @@ +(module + ;; Define 1 page of memory (64KB = 65,536 bytes) + (memory 1) + + (func $read_overflow (result i32) + ;; Push the index of the FIRST invalid byte + ;; Memory is 0..65535, so 65536 is out of bounds. + i32.const 65536 + + ;; Load 1 byte (unsigned) + i32.load8_u + + ;; Clean up the stack. + ;; The load pushed a value, but we don't care what it is. + drop + + ;; Return 1 to signal "I survived the memory access" + i32.const 1 + ) + + ;; Export as "finish" as requested + (export "finish" (func $read_overflow)) + ) diff --git a/src/test/app/wasm_fixtures/wat/multi_memory.wat b/src/test/app/wasm_fixtures/wat/multi_memory.wat new file mode 100644 index 00000000000..c2f36e253ff --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/multi_memory.wat @@ -0,0 +1,16 @@ +(module + ;; Memory 0: Index 0 (Empty) + (memory 0) + + ;; Memory 1: Index 1 (Size 1 page) + ;; If multi-memory is disabled, this line causes a validation error (max 1 memory). + (memory 1) + + (func $finish (result i32) + ;; Query size of Memory Index 1. + ;; Should return 1 (success). + memory.size 1 + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/opc_reserved.wat b/src/test/app/wasm_fixtures/wat/opc_reserved.wat new file mode 100644 index 00000000000..0bc61b52c3d --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/opc_reserved.wat @@ -0,0 +1,98 @@ +(module + + ;; Type for call_indirect + (type (func (result i32))) + + ;; Memory and table declarations + (memory 1) + (table 1 funcref) + (data (i32.const 0) "test") + (elem (i32.const 0) $test_func) + + ;; Global declarations + (global $g0 (mut i32) (i32.const 0)) + (global $g1 (mut i64) (i64.const 0)) + + ;; Test function for call/call_indirect + (func $test_func (result i32) + i32.const 42 + ) + + + ;; Main function with all instructions in hex order + (func $all_instructions (export "all_instructions") (result i32) + (local $l0 i32) + (local $l1 i64) + + ;; 0x01: nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + i32.const 11 + ) +) diff --git a/src/test/app/wasm_fixtures/wat/proposal_bulk_memory.wat b/src/test/app/wasm_fixtures/wat/proposal_bulk_memory.wat new file mode 100644 index 00000000000..1010c60a613 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_bulk_memory.wat @@ -0,0 +1,25 @@ +(module + ;; Define 1 page of memory + (memory 1) + (export "memory" (memory 0)) + + (func $test_bulk_ops (result i32) + ;; Setup: Write value 42 at index 0 so we have something to copy + (i32.store8 (i32.const 0) (i32.const 42)) + + ;; Test memory.copy (Opcode 0xFC 0x0A) + ;; Copy 1 byte from offset 0 to offset 100 + (memory.copy + (i32.const 100) ;; Destination Offset + (i32.const 0) ;; Source Offset + (i32.const 1) ;; Size (bytes) + ) + + ;; Verify: Read byte at offset 100. Should be 42. + (i32.load8_u (i32.const 100)) + (i32.const 42) + i32.eq + ) + + (export "finish" (func $test_bulk_ops)) +) diff --git a/src/test/app/wasm_fixtures/wat/proposal_extended_const.wat b/src/test/app/wasm_fixtures/wat/proposal_extended_const.wat new file mode 100644 index 00000000000..1b24f8ff450 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_extended_const.wat @@ -0,0 +1,15 @@ +(module + ;; 1. Define a global using an EXTENDED constant expression. + ;; MVP only allows (i32.const X). + ;; This proposal allows (i32.add (i32.const X) (i32.const Y)). + (global $g i32 (i32.add (i32.const 10) (i32.const 32))) + + (func $finish (result i32) + ;; 2. verify the global equals 42 + global.get $g + i32.const 42 + i32.eq + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/proposal_float_to_int.wat b/src/test/app/wasm_fixtures/wat/proposal_float_to_int.wat new file mode 100644 index 00000000000..3d52bfa3996 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_float_to_int.wat @@ -0,0 +1,18 @@ +(module + (func $test_saturation (result i32) + ;; 1. Push a float that is too big for a 32-bit integer + ;; 1e10 (10 billion) > 2.14 billion (Max i32) + f32.const 1.0e10 + + ;; 2. Attempt saturating conversion (Opcode 0xFC 0x00) + ;; If supported: Clamps to MAX_I32. + ;; If disabled: Validation error (unknown instruction). + i32.trunc_sat_f32_s + + ;; 3. Check if result is MAX_I32 (2147483647) + i32.const 2147483647 + i32.eq + ) + + (export "finish" (func $test_saturation)) +) diff --git a/src/test/app/wasm_fixtures/wat/proposal_gc_struct_new.wat b/src/test/app/wasm_fixtures/wat/proposal_gc_struct_new.wat new file mode 100644 index 00000000000..b9a1e5c5198 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_gc_struct_new.wat @@ -0,0 +1,12 @@ +;; generated by wasm-tools print gc_test.wasm that has the following hex +;; 0061736d01000000010b026000017f5f027f017f0103020100070a010666696e69736800000a0a010800fb01011a41010b +(module + (type (;0;) (func (result i32))) + (type (;1;) (struct (field (mut i32)) (field (mut i32)))) + (export "finish" (func 0)) + (func (;0;) (type 0) (result i32) + struct.new_default 1 + drop + i32.const 1 + ) +) diff --git a/src/test/app/wasm_fixtures/wat/proposal_multi_value.wat b/src/test/app/wasm_fixtures/wat/proposal_multi_value.wat new file mode 100644 index 00000000000..30dda4ce125 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_multi_value.wat @@ -0,0 +1,22 @@ +(module + ;; 1. Function returning TWO values (Multi-Value feature) + (func $get_numbers (result i32 i32) + i32.const 10 + i32.const 20 + ) + + (func $finish (result i32) + ;; Call pushes [10, 20] onto the stack + call $get_numbers + + ;; 2. Block taking TWO parameters (Multi-Value feature) + ;; It consumes the [10, 20] from the stack. + block (param i32 i32) (result i32) + i32.add ;; 10 + 20 = 30 + i32.const 30 ;; Expected result + i32.eq ;; Compare: returns 1 if equal + end + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/proposal_mutable_global.wat b/src/test/app/wasm_fixtures/wat/proposal_mutable_global.wat new file mode 100644 index 00000000000..2b1b5ff6483 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_mutable_global.wat @@ -0,0 +1,25 @@ +(module + ;; Define a mutable global initialized to 0 + (global $counter (mut i32) (i32.const 0)) + + ;; EXPORTING a mutable global is the key feature of this proposal. + ;; In strict MVP, exported globals had to be immutable (const). + (export "counter" (global $counter)) + + (func $finish (result i32) + ;; 1. Get current value + global.get $counter + + ;; 2. Add 1 + i32.const 1 + i32.add + + ;; 3. Set new value (Mutation) + global.set $counter + + ;; 4. Return 1 for success + i32.const 1 + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/proposal_ref_types.wat b/src/test/app/wasm_fixtures/wat/proposal_ref_types.wat new file mode 100644 index 00000000000..c339b9176fe --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_ref_types.wat @@ -0,0 +1,18 @@ +(module + ;; Import a table from the host that holds externrefs + (import "env" "table" (table 1 externref)) + + (func $test_ref_types (result i32) + ;; Store a null externref into the table at index 0 + ;; If reference_types is disabled, 'externref' and 'ref.null' will fail parsing. + (table.set + (i32.const 0) ;; Index + (ref.null extern) ;; Value (Null External Reference) + ) + + ;; Return 1 (Success) + i32.const 1 + ) + + (export "finish" (func $test_ref_types)) +) diff --git a/src/test/app/wasm_fixtures/wat/proposal_sign_ext.wat b/src/test/app/wasm_fixtures/wat/proposal_sign_ext.wat new file mode 100644 index 00000000000..ab9061fbb15 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_sign_ext.wat @@ -0,0 +1,18 @@ +(module + (func $test_sign_ext (result i32) + ;; Push 255 (0x000000FF) onto the stack + i32.const 255 + + ;; Sign-extend from 8-bit to 32-bit + ;; If 255 is treated as an i8, it is -1. + ;; Result should be -1 (0xFFFFFFFF). + ;; Without this proposal, this opcode (0xC0) causes a validation error. + i32.extend8_s + + ;; Check if result is -1 + i32.const -1 + i32.eq + ) + + (export "finish" (func $test_sign_ext)) +) diff --git a/src/test/app/wasm_fixtures/wat/proposal_stringref.wat b/src/test/app/wasm_fixtures/wat/proposal_stringref.wat new file mode 100644 index 00000000000..c9f8030fafd --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_stringref.wat @@ -0,0 +1 @@ +;;hard to generate diff --git a/src/test/app/wasm_fixtures/wat/proposal_tail_call.wat b/src/test/app/wasm_fixtures/wat/proposal_tail_call.wat new file mode 100644 index 00000000000..f2c883931a5 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/proposal_tail_call.wat @@ -0,0 +1,15 @@ +(module + ;; Define a simple function we can tail-call + (func $target (result i32) + i32.const 1 + ) + + (func $finish (result i32) + ;; Try to use the 'return_call' instruction (Opcode 0x12) + ;; If Tail Call proposal is disabled, this fails to Compile/Validate. + ;; If enabled, it jumps to $target, which returns 1. + return_call $target + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/start_loop.wat b/src/test/app/wasm_fixtures/wat/start_loop.wat new file mode 100644 index 00000000000..c00d173ea45 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/start_loop.wat @@ -0,0 +1,22 @@ +(module + ;; Function 1: The Infinite Loop + (func $run_forever + (loop $infinite + br $infinite + ) + ) + + ;; Function 2: Finish + (func $finish (result i32) + i32.const 1 + ) + + ;; 1. EXPORT the functions (optional, if you want to call them later) + (export "start" (func $run_forever)) + (export "finish" (func $finish)) + + ;; 2. The special start section + ;; This tells the VM: "Run function $run_forever immediately + ;; when this module is instantiated." + (start $run_forever) +) diff --git a/src/test/app/wasm_fixtures/wat/table_0_elements.wat b/src/test/app/wasm_fixtures/wat/table_0_elements.wat new file mode 100644 index 00000000000..7f2d1abc648 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/table_0_elements.wat @@ -0,0 +1,10 @@ +(module + ;; Define a table with exactly 0 entries + (table 0 funcref) + + ;; Standard finish function + (func $finish (result i32) + i32.const 1 + ) + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/table_2_tables.wat b/src/test/app/wasm_fixtures/wat/table_2_tables.wat new file mode 100644 index 00000000000..fd007f410ac --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/table_2_tables.wat @@ -0,0 +1,24 @@ +(module + ;; Define a dummy function to put in the tables + (func $dummy) + + ;; TABLE 0: The default table (allowed in MVP) + ;; Size: 1 initial, 1 max + (table $t0 1 1 funcref) + + ;; Initialize Table 0 at index 0 + (elem (table $t0) (i32.const 0) $dummy) + + ;; TABLE 1: The second table (Requires Reference Types proposal) + ;; If strict MVP is enforced, the parser should error here. + (table $t1 1 1 funcref) + + ;; Initialize Table 1 at index 0 + (elem (table $t1) (i32.const 0) $dummy) + + (func $finish (result i32) + ;; If we successfully loaded a module with 2 tables, return 1. + i32.const 1 + ) + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/table_64_elements.wat b/src/test/app/wasm_fixtures/wat/table_64_elements.wat new file mode 100644 index 00000000000..3ff990b2282 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/table_64_elements.wat @@ -0,0 +1,25 @@ +(module + ;; Define a table with exactly 64 entries + (table 64 funcref) + + ;; A dummy function to reference + (func $dummy) + + ;; Initialize the table at offset 0 with 64 references to $dummy + (elem (i32.const 0) + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 8 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 16 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 24 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 32 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 40 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 48 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 56 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 64 + ) + + ;; Standard finish function + (func $finish (result i32) + i32.const 1 + ) + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/table_65_elements.wat b/src/test/app/wasm_fixtures/wat/table_65_elements.wat new file mode 100644 index 00000000000..db3efa0a18f --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/table_65_elements.wat @@ -0,0 +1,25 @@ +(module + ;; Define a table with exactly 65 entries + (table 65 funcref) + + ;; A dummy function to reference + (func $dummy) + + ;; Initialize the table at offset 0 with 65 references to $dummy + (elem (i32.const 0) + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 8 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 16 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 24 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 32 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 40 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 48 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 56 + $dummy $dummy $dummy $dummy $dummy $dummy $dummy $dummy ;; 64 + $dummy ;; 65 (The one that breaks the camel's back) + ) + + (func $finish (result i32) + i32.const 1 + ) + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/table_uint_max.wat b/src/test/app/wasm_fixtures/wat/table_uint_max.wat new file mode 100644 index 00000000000..31c15b8be22 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/table_uint_max.wat @@ -0,0 +1,15 @@ +(module + ;; Definition: (table ) + ;; We use 0xFFFFFFFF (4,294,967,295), which is the unsigned equivalent of -1. + ;; This tests if the runtime handles the maximum possible u32 value + ;; without integer overflows or attempting a massive allocation. + ;; + ;; Note that using -1 as the table size cannot be parsed by wasm-tools or wat2wasm + (table 0xFFFFFFFF funcref) + + (func $finish (result i32) + ;; If the module loads despite the massive table, return 1. + i32.const 1 + ) + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/trap_divide_by_0.wat b/src/test/app/wasm_fixtures/wat/trap_divide_by_0.wat new file mode 100644 index 00000000000..8fda7bdae07 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/trap_divide_by_0.wat @@ -0,0 +1,15 @@ +(module + (func $finish (export "finish") (result i32) + ;; Setup for Requirement 2: Divide an i32 by 0 + i32.const 42 ;; Push numerator + i32.const 0 ;; Push denominator (0) + i32.div_s ;; Perform signed division (42 / 0) + + ;; --- NOTE: Execution usually traps (crashes) at the line above --- + + ;; Logic to satisfy Requirement 1: Return i32 = 1 + ;; If execution continued, we would drop the division result and return 1 + drop ;; Clear the stack + i32.const 1 ;; Push the return value + ) +) diff --git a/src/test/app/wasm_fixtures/wat/trap_func_signature_mismatch.wat b/src/test/app/wasm_fixtures/wat/trap_func_signature_mismatch.wat new file mode 100644 index 00000000000..a712d1ac8dd --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/trap_func_signature_mismatch.wat @@ -0,0 +1,33 @@ +(module + ;; Define a table with 1 slot + (table 1 funcref) + + ;; Define Type A: Takes nothing, returns nothing + (type $type_void (func)) + + ;; Define Type B: Takes nothing, returns i32 + (type $type_i32 (func (result i32))) + + ;; Define a function of Type A + (func $void_func (type $type_void) + nop + ) + + ;; Put Type A function into Table[0] + (elem (i32.const 0) $void_func) + + (func $finish (result i32) + ;; Attempt to call Index 0, but CLAIM we expect Type B (result i32). + ;; The function at Index 0 matches Type A. + ;; TRAP: "indirect call type mismatch" + + ;; 1. Push the table index (0) onto the stack + i32.const 0 + + ;; 2. Call indirect using Type B signature. + ;; This pops the index (0) from the stack. + call_indirect (type $type_i32) + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/trap_int_overflow.wat b/src/test/app/wasm_fixtures/wat/trap_int_overflow.wat new file mode 100644 index 00000000000..810236df791 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/trap_int_overflow.wat @@ -0,0 +1,18 @@ +(module + (func $test_int_overflow (result i32) + ;; 1. Push INT_MIN (-2147483648) + ;; In Hex: 0x80000000 + i32.const -2147483648 + + ;; 2. Push -1 + i32.const -1 + + ;; 3. Signed Division + ;; This specific case is the ONLY integer arithmetic operation + ;; (besides divide by zero) that traps in the spec. + ;; Result would be +2147483648, which is too big for signed i32. + i32.div_s + ) + + (export "finish" (func $test_int_overflow)) +) diff --git a/src/test/app/wasm_fixtures/wat/trap_null_call.wat b/src/test/app/wasm_fixtures/wat/trap_null_call.wat new file mode 100644 index 00000000000..f806d922f7e --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/trap_null_call.wat @@ -0,0 +1,22 @@ +(module + ;; Table size is 1, so Index 0 is VALID bounds. + ;; However, we do NOT initialize it, so it contains 'ref.null'. + (table 1 funcref) + + (type $t (func (result i32))) + + (func $finish (result i32) + ;; Call Index 0. + ;; Bounds check passes (0 < 1). + ;; Null check fails. + ;; TRAP: "uninitialized element" or "undefined element" + + ;; 1. Push the index (0) onto the stack first + i32.const 0 + + ;; 2. Perform the call. This pops the index. + call_indirect (type $t) + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/trap_unreachable.wat b/src/test/app/wasm_fixtures/wat/trap_unreachable.wat new file mode 100644 index 00000000000..4bc15569fc3 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/trap_unreachable.wat @@ -0,0 +1,12 @@ +(module + (func $finish (result i32) + ;; This instruction explicitly causes a trap. + ;; It consumes no fuel (beyond the instruction itself) and stops execution. + unreachable + + ;; This code is dead and never reached + i32.const 1 + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/wasi_get_time.wat b/src/test/app/wasm_fixtures/wat/wasi_get_time.wat new file mode 100644 index 00000000000..049ecf0be10 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/wasi_get_time.wat @@ -0,0 +1,38 @@ +(module + ;; Import clock_time_get from WASI + ;; Signature: (param clock_id precision return_ptr) (result errno) + (import "wasi_snapshot_preview1" "clock_time_get" + (func $clock_time_get (param i32 i64 i32) (result i32)) + ) + + (memory 1) + (export "memory" (memory 0)) + + (func $finish (result i32) + ;; We will store the timestamp (a 64-bit integer) at address 0. + ;; No setup required in memory beforehand! + + ;; Call the function + (call $clock_time_get + (i32.const 0) ;; clock_id: 0 = Realtime (Wallclock) + (i64.const 1000) ;; precision: 1000ns (hint to OS) + (i32.const 0) ;; result_ptr: Write the time to address 0 + ) + + ;; The function returns an 'errno' (error code). + ;; 0 = Success. Anything else = Error. + + ;; Check if errno (top of stack) is 0 + i32.eqz + if (result i32) + ;; Success! The time is now stored in heap[0..8]. + ;; We return 1 as requested. + i32.const 1 + else + ;; Failed (maybe WASI is disabled or clock is missing) + i32.const -1 + end + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/wasi_print.wat b/src/test/app/wasm_fixtures/wat/wasi_print.wat new file mode 100644 index 00000000000..18e8a088f48 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/wasi_print.wat @@ -0,0 +1,59 @@ +(module + ;; Import WASI fd_write + ;; Signature: (fd, iovs_ptr, iovs_len, nwritten_ptr) -> errno + (import "wasi_snapshot_preview1" "fd_write" + (func $fd_write (param i32 i32 i32 i32) (result i32)) + ) + + (memory 1) + (export "memory" (memory 0)) + + ;; --- DATA SEGMENTS --- + + ;; 1. The String Data "Hello\n" placed at offset 16 + ;; We assume offset 0-16 is reserved for the IOVec struct + (data (i32.const 16) "Hello\n") + + ;; 2. The IO Vector (struct iovec) placed at offset 0 + ;; Structure: { buf_ptr: u32, buf_len: u32 } + + ;; Field 1: buf_ptr = 16 (Location of "Hello\n") + ;; Encoded in little-endian: 10 00 00 00 + (data (i32.const 0) "\10\00\00\00") + + ;; Field 2: buf_len = 6 (Length of "Hello\n") + ;; Encoded in little-endian: 06 00 00 00 + (data (i32.const 4) "\06\00\00\00") + + (func $finish (result i32) + (local $nwritten_ptr i32) + + ;; We will ask WASI to write the "number of bytes written" to address 24 + ;; (safely after our string data) + i32.const 24 + local.set $nwritten_ptr + + ;; Call fd_write + (call $fd_write + (i32.const 1) ;; fd: 1 = STDOUT + (i32.const 0) ;; iovs_ptr: Address 0 (where we defined the struct) + (i32.const 1) ;; iovs_len: We are passing 1 vector + (local.get $nwritten_ptr) ;; nwritten_ptr: Address 24 + ) + + ;; The function returns an 'errno' (i32). + ;; 0 means Success. + + ;; Check if errno == 0 + i32.eqz + if (result i32) + ;; Success: Return 1 + i32.const 1 + else + ;; Failure: Return -1 + i32.const -1 + end + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/app/wasm_fixtures/wat/wide_arithmetic.wat b/src/test/app/wasm_fixtures/wat/wide_arithmetic.wat new file mode 100644 index 00000000000..cd796162ae1 --- /dev/null +++ b/src/test/app/wasm_fixtures/wat/wide_arithmetic.wat @@ -0,0 +1,22 @@ +(module + (func $finish (result i32) + ;; 1. Push operands + i64.const 1 + i64.const 2 + + ;; 2. Execute Wide Multiplication + ;; If the feature is DISABLED, the parser/validator will trap here + ;; with "unknown instruction" or "invalid opcode". + ;; Input: [i64, i64] -> Output: [i64, i64] + i64.mul_wide_u + + ;; 3. Clean up the stack (drop the two i64 results) + drop + drop + + ;; 4. Return 1 to signal that validation passed + i32.const 1 + ) + + (export "finish" (func $finish)) +) diff --git a/src/test/basics/Number_test.cpp b/src/test/basics/Number_test.cpp index 2a4e176ae5a..0ac6927081a 100644 --- a/src/test/basics/Number_test.cpp +++ b/src/test/basics/Number_test.cpp @@ -1551,6 +1551,76 @@ class Number_test : public beast::unit_test::Suite } } + void + test_log10() + { + auto const scale = Number::getMantissaScale(); + testcase << "test_lg " << to_string(scale); + + using Case = std::tuple; + auto test = [this](auto const& c) { + for (auto const& [x, z] : c) + { + auto const result = log10(x); + std::stringstream ss; + ss << "lg(" << x << ") = " << result << ". Expected: " << z; + // std::cout << ss.str() << std::endl; + BEAST_EXPECTS(result == z, ss.str()); + } + }; + + auto const cSmall = std::to_array( + {{Number{2}, Number{3'010'299'956'639'811ll, -16}}, + {Number{2'000'000}, Number{6'301'029'995'663'985ll, -15}}, + {Number{2, -30}, Number{-2'969'897'000'433'602ll, -14}}, + {Number{1}, Number{0}}, + {Number{1'000'000'000'000'000ll}, Number{15}}, + {Number{5625, -4}, Number{-2'498'774'732'165'998, -16}}}); + + auto const cLarge = std::to_array( + {{Number{false, Number::maxMantissa() - 9, -1, Number::normalized{}}, + Number{false, 1'746'901'684'478'673'451ll, -17, Number::normalized{}}}, + {Number{false, Number::maxMantissa() - 9, 0, Number::normalized{}}, + Number{false, 1'846'901'684'478'673'451ll, -17, Number::normalized{}}}, + {Number{Number::maxRep}, + Number{false, 1'861'728'612'932'620'011ll, -17, Number::normalized{}}}}); + + if (Number::getMantissaScale() == MantissaRange::small) + { + test(cSmall); + } + else + { + NumberRoundModeGuard const mg(Number::towards_zero); + test(cLarge); + } + + { + bool caught = false; + try + { + log10(Number{-2}); + } + catch (std::runtime_error const&) + { + caught = true; + } + BEAST_EXPECT(caught); + caught = false; + + try + { + log10(Number()); + } + catch (std::runtime_error const&) + { + caught = true; + } + BEAST_EXPECT(caught); + caught = false; + } + } + void run() override { @@ -1579,6 +1649,7 @@ class Number_test : public beast::unit_test::Suite testTruncate(); testRounding(); testInt64(); + test_log10(); } } }; diff --git a/src/test/jtx.h b/src/test/jtx.h index d4b88b0b9ef..fa5a0a466c0 100644 --- a/src/test/jtx.h +++ b/src/test/jtx.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include diff --git a/src/test/jtx/contract.h b/src/test/jtx/contract.h new file mode 100644 index 00000000000..2cebb3c82d1 --- /dev/null +++ b/src/test/jtx/contract.h @@ -0,0 +1,164 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include "test/jtx/SignerUtils.h" + +#include +#include +#include + +namespace xrpl { +namespace test { +namespace jtx { + +/** Contract operations */ +namespace contract { + +Json::Value +create(jtx::Account const& account, std::string const& contractCode); + +Json::Value +create(jtx::Account const& account, uint256 const& contractHash); + +Json::Value +modify( + jtx::Account const& account, + jtx::Account const& contractAccount, + std::string const& contractCode); + +Json::Value +modify( + jtx::Account const& account, + jtx::Account const& contractAccount, + uint256 const& contractHash); + +Json::Value +modify(jtx::Account const& account, jtx::Account const& contractAccount, jtx::Account const& owner); + +Json::Value +del(jtx::Account const& account, jtx::Account const& contractAccount); + +Json::Value +call( + jtx::Account const& account, + jtx::Account const& contractAccount, + std::string const& functionName); + +Json::Value +userDelete(jtx::Account const& account, jtx::Account const& contractAccount); + +/** Add Function on a JTx. */ +class add_function +{ +private: + std::string const name_; + std::vector> call_params_; + +public: + explicit add_function( + std::string const& name, + std::vector> call_params) + : name_{name}, call_params_{std::move(call_params)} + { + } + + void + operator()(Env&, JTx& jtx) const; +}; + +/** Add Instance Parameter on a JTx. */ +template +class add_instance_param +{ +private: + std::uint32_t flags_; + std::string name_; + std::string type_; + T value_; + +public: + explicit add_instance_param( + std::uint32_t flags, + std::string const& name, + std::string const& type, + T value) + : flags_{flags}, name_{name}, type_{type}, value_{value} + { + } + + void + operator()(Env&, JTx& jtx) const + { + if (jtx.jv.isMember(sfContractCode.fieldName)) + { + // Add instance Parameters + if (!jtx.jv.isMember(sfInstanceParameters.fieldName)) + { + jtx.jv[sfInstanceParameters.fieldName] = Json::Value(Json::arrayValue); + } + Json::Value param = Json::Value(Json::objectValue); + param[sfInstanceParameter.fieldName][sfParameterFlag.fieldName] = flags_; + param[sfInstanceParameter.fieldName][sfParameterType.fieldName][jss::type] = type_; + jtx.jv[sfInstanceParameters.fieldName].append(param); + } + + // Add instance Parameter Values + if (!jtx.jv.isMember(sfInstanceParameterValues.fieldName)) + { + jtx.jv[sfInstanceParameterValues.fieldName] = Json::Value(Json::arrayValue); + } + Json::Value param = Json::Value(Json::objectValue); + param[sfInstanceParameterValue.fieldName][sfParameterFlag.fieldName] = flags_; + param[sfInstanceParameterValue.fieldName][sfParameterValue.fieldName][jss::type] = type_; + param[sfInstanceParameterValue.fieldName][sfParameterValue.fieldName][jss::value] = value_; + jtx.jv[sfInstanceParameterValues.fieldName].append(param); + } +}; + +/** Add Parameter Value on a JTx. */ +template +class add_param +{ +private: + std::uint32_t flags_; + std::string name_; + std::string type_; + T value_; + +public: + explicit add_param( + std::uint32_t flags, + std::string const& name, + std::string const& type, + T value) + : flags_(flags), name_(name), type_(type), value_(value) + { + } + + void + operator()(Env&, JTx& jtx) const + { + Json::Value param = Json::Value(Json::objectValue); + param[sfParameter] = Json::Value(Json::objectValue); + param[sfParameter][sfParameterFlag] = flags_; + param[sfParameter][sfParameterValue] = Json::Value(Json::objectValue); + param[sfParameter][sfParameterValue][jss::type] = type_; + param[sfParameter][sfParameterValue][jss::value] = value_; + jtx.jv[sfParameters].append(param); + } +}; + +} // namespace contract + +} // namespace jtx + +} // namespace test +} // namespace xrpl diff --git a/src/test/jtx/escrow.h b/src/test/jtx/escrow.h index 58be9b701ae..6b8c9d724c9 100644 --- a/src/test/jtx/escrow.h +++ b/src/test/jtx/escrow.h @@ -75,4 +75,73 @@ auto const kCondition = JTxFieldWrapper(sfCondition); auto const kFulfillment = JTxFieldWrapper(sfFulfillment); +struct finish_function +{ +private: + std::string value_; + +public: + explicit finish_function(std::string func) : value_(func) + { + } + + explicit finish_function(Slice const& func) : value_(strHex(func)) + { + } + + template + explicit finish_function(std::array const& f) : finish_function(makeSlice(f)) + { + } + + void + operator()(Env&, JTx& jt) const + { + jt.jv[sfFinishFunction.jsonName] = value_; + } +}; + +struct data +{ +private: + std::string value_; + +public: + explicit data(std::string func) : value_(func) + { + } + + explicit data(Slice const& func) : value_(strHex(func)) + { + } + + template + explicit data(std::array const& f) : data(makeSlice(f)) + { + } + + void + operator()(Env&, JTx& jt) const + { + jt.jv[sfData.jsonName] = value_; + } +}; + +struct comp_allowance +{ +private: + std::uint32_t value_; + +public: + explicit comp_allowance(std::uint32_t const& value) : value_(value) + { + } + + void + operator()(Env&, JTx& jt) const + { + jt.jv[sfComputationAllowance.jsonName] = value_; + } +}; + } // namespace xrpl::test::jtx::escrow diff --git a/src/test/jtx/impl/contract.cpp b/src/test/jtx/impl/contract.cpp new file mode 100644 index 00000000000..f57b3921b4c --- /dev/null +++ b/src/test/jtx/impl/contract.cpp @@ -0,0 +1,134 @@ +#include +#include + +#include +#include + +namespace xrpl { +namespace test { +namespace jtx { + +namespace contract { + +Json::Value +create(jtx::Account const& account, std::string const& contractCode) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = account.human(); + jv[sfContractCode] = contractCode; + return jv; +} + +Json::Value +create(jtx::Account const& account, uint256 const& contractHash) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCreate; + jv[jss::Account] = account.human(); + jv[sfContractHash] = to_string(contractHash); + return jv; +} + +Json::Value +modify( + jtx::Account const& account, + jtx::Account const& contractAccount, + std::string const& contractCode) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::ContractModify; + jv[jss::Account] = account.human(); + jv[sfContractAccount] = contractAccount.human(); + jv[sfContractCode] = contractCode; + return jv; +} + +Json::Value +modify( + jtx::Account const& account, + jtx::Account const& contractAccount, + uint256 const& contractHash) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::ContractModify; + jv[jss::Account] = account.human(); + jv[sfContractAccount] = contractAccount.human(); + jv[sfContractHash] = to_string(contractHash); + return jv; +} + +Json::Value +modify(jtx::Account const& account, jtx::Account const& contractAccount, jtx::Account const& owner) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::ContractModify; + jv[jss::Account] = account.human(); + jv[sfContractAccount] = contractAccount.human(); + jv[sfOwner] = owner.human(); + return jv; +} + +Json::Value +del(jtx::Account const& account, jtx::Account const& contractAccount) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::ContractDelete; + jv[jss::Account] = account.human(); + jv[sfContractAccount] = contractAccount.human(); + return jv; +} + +Json::Value +call( + jtx::Account const& account, + jtx::Account const& contractAccount, + std::string const& functionName) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::ContractCall; + jv[jss::Account] = account.human(); + jv[sfContractAccount] = contractAccount.human(); + jv[sfFunctionName] = strHex(functionName); + jv[sfParameters] = Json::Value(Json::arrayValue); + return jv; +} + +Json::Value +userDelete(jtx::Account const& account, jtx::Account const& contractAccount) +{ + Json::Value jv; + jv[jss::TransactionType] = jss::ContractUserDelete; + jv[jss::Account] = account.human(); + jv[sfContractAccount] = contractAccount.human(); + return jv; +} + +Json::Value +addCallParam(std::uint32_t const& flags, std::string const& name, std::string const& typeName) +{ + Json::Value param = Json::Value(Json::objectValue); + param[sfParameter][sfParameterFlag] = flags; + param[sfParameter][sfParameterType][jss::type] = typeName; + return param; +}; + +void +add_function::operator()(Env&, JTx& jt) const +{ + auto const index = jt.jv[sfFunctions].size(); + Json::Value& function = jt.jv[sfFunctions][index]; + + function = Json::Value{}; + function[sfFunction][sfFunctionName] = strHex(name_); + for (auto const& [p_flags, p_name, p_type] : call_params_) + { + function[sfFunction][sfParameters].append(addCallParam(p_flags, p_name, p_type)); + } +} + +} // namespace contract + +} // namespace jtx +} // namespace test +} // namespace xrpl diff --git a/src/test/jtx/impl/envconfig.cpp b/src/test/jtx/impl/envconfig.cpp index 56197e10785..f5acc96a7d2 100644 --- a/src/test/jtx/impl/envconfig.cpp +++ b/src/test/jtx/impl/envconfig.cpp @@ -20,9 +20,14 @@ setupConfigForUnitTests(Config& cfg) using namespace jtx; // Default fees to old values, so tests don't have to worry about changes in // Config.h + // NOTE: For new `FEES` fields, you need to wait for the first flag ledger + // to close for the values to be activated. cfg.fees.referenceFee = UNIT_TEST_REFERENCE_FEE; cfg.fees.accountReserve = XRP(200).value().xrp().drops(); cfg.fees.ownerReserve = XRP(50).value().xrp().drops(); + cfg.fees.extension_compute_limit = 1'000'000; + cfg.fees.extension_size_limit = 100'000; + cfg.fees.gas_price = 1'000'000; // 1 drop = 1,000,000 micro-drops // The Beta API (currently v2) is always available to tests cfg.betaRpcApi = true; diff --git a/src/test/protocol/Hooks_test.cpp b/src/test/protocol/Hooks_test.cpp deleted file mode 100644 index 082507aca79..00000000000 --- a/src/test/protocol/Hooks_test.cpp +++ /dev/null @@ -1,189 +0,0 @@ - - -#include // IWYU pragma: keep - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace xrpl { - -class Hooks_test : public beast::unit_test::Suite -{ - /** - * This unit test was requested here: - * https://github.com/XRPLF/rippled/pull/4089#issuecomment-1050274539 - * These are tests that exercise facilities that are reserved for when Hooks - * is merged in the future. - **/ - - void - testHookFields() - { - testcase("Test Hooks fields"); - - using namespace test::jtx; - - std::vector> const fieldsToTest = { - sfHookResult, - sfHookStateChangeCount, - sfHookEmitCount, - sfHookExecutionIndex, - sfHookApiVersion, - sfHookStateCount, - sfEmitGeneration, - sfHookOn, - sfHookInstructionCount, - sfEmitBurden, - sfHookReturnCode, - sfReferenceCount, - sfEmitParentTxnID, - sfEmitNonce, - sfEmitHookHash, - sfHookStateKey, - sfHookHash, - sfHookNamespace, - sfHookSetTxnID, - sfHookStateData, - sfHookReturnString, - sfHookParameterName, - sfHookParameterValue, - sfEmitCallback, - sfHookAccount, - sfEmittedTxn, - sfHook, - sfHookDefinition, - sfHookParameter, - sfHookGrant, - sfEmitDetails, - sfHookExecutions, - sfHookExecution, - sfHookParameters, - sfHooks, - sfHookGrants}; - - for (auto const& rf : fieldsToTest) - { - SField const& f = rf.get(); - - STObject dummy{sfGeneric}; - - BEAST_EXPECT(!dummy.isFieldPresent(f)); - - switch (f.fieldType) - { - case STI_UINT8: { - dummy.setFieldU8(f, 0); - BEAST_EXPECT(dummy.getFieldU8(f) == 0); - - dummy.setFieldU8(f, 255); - BEAST_EXPECT(dummy.getFieldU8(f) == 255); - - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - case STI_UINT16: { - dummy.setFieldU16(f, 0); - BEAST_EXPECT(dummy.getFieldU16(f) == 0); - - dummy.setFieldU16(f, 0xFFFFU); - BEAST_EXPECT(dummy.getFieldU16(f) == 0xFFFFU); - - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - case STI_UINT32: { - dummy.setFieldU32(f, 0); - BEAST_EXPECT(dummy.getFieldU32(f) == 0); - - dummy.setFieldU32(f, 0xFFFFFFFFU); - BEAST_EXPECT(dummy.getFieldU32(f) == 0xFFFFFFFFU); - - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - case STI_UINT64: { - dummy.setFieldU64(f, 0); - BEAST_EXPECT(dummy.getFieldU64(f) == 0); - - dummy.setFieldU64(f, 0xFFFFFFFFFFFFFFFFU); - BEAST_EXPECT(dummy.getFieldU64(f) == 0xFFFFFFFFFFFFFFFFU); - - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - case STI_UINT256: { - uint256 const u = uint256::fromVoid( - "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBE" - "EFDEADBEEF"); - dummy.setFieldH256(f, u); - BEAST_EXPECT(dummy.getFieldH256(f) == u); - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - case STI_VL: { - std::vector const v{1, 2, 3}; - dummy.setFieldVL(f, v); - BEAST_EXPECT(dummy.getFieldVL(f) == v); - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - case STI_ACCOUNT: { - // NOLINTBEGIN(bugprone-unchecked-optional-access) - AccountID const id = - *parseBase58("rwfSjJNK2YQuN64bSWn7T2eY9FJAyAPYJT"); - // NOLINTEND(bugprone-unchecked-optional-access) - dummy.setAccountID(f, id); - BEAST_EXPECT(dummy.getAccountID(f) == id); - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - case STI_OBJECT: { - dummy.emplaceBack(STObject{f}); - BEAST_EXPECT(dummy.getField(f).getFName() == f); - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - case STI_ARRAY: { - STArray dummy2{f, 2}; - dummy2.pushBack(STObject{sfGeneric}); - dummy2.pushBack(STObject{sfGeneric}); - dummy.setFieldArray(f, dummy2); - BEAST_EXPECT(dummy.getFieldArray(f) == dummy2); - BEAST_EXPECT(dummy.isFieldPresent(f)); - break; - } - - default: - BEAST_EXPECT(false); - } - } - } - -public: - void - run() override - { - using namespace test::jtx; - testHookFields(); - } -}; - -BEAST_DEFINE_TESTSUITE(Hooks, protocol, xrpl); - -} // namespace xrpl diff --git a/src/test/protocol/STDataType_test.cpp b/src/test/protocol/STDataType_test.cpp new file mode 100644 index 00000000000..a74e8c53b4c --- /dev/null +++ b/src/test/protocol/STDataType_test.cpp @@ -0,0 +1,641 @@ +#include +#include +#include +#include +#include + +namespace xrpl { +struct STDataType_test : public beast::unit_test::suite +{ + void + testConstructors() + { + testcase("constructors"); + + auto const& sf = sfParameterType; + + // Test default constructor + { + STDataType const dt1(sf); + BEAST_EXPECT(dt1.getInnerSType() == STI_NOTPRESENT); + BEAST_EXPECT(dt1.getSType() == STI_DATATYPE); + BEAST_EXPECT(dt1.getFName() == sf); + } + + // Test constructor with SerializedTypeID + { + STDataType const dt2(sf, STI_UINT32); + BEAST_EXPECT(dt2.getInnerSType() == STI_UINT32); + BEAST_EXPECT(!dt2.isDefault()); + } + + // Test deserialization constructor + { + Serializer s; + s.add16(STI_UINT64); + SerialIter sit(s.slice()); + STDataType const dt3(sit, sf); + BEAST_EXPECT(dt3.getInnerSType() == STI_UINT64); + } + } + + void + testCopyMove() + { + testcase("copy and move"); + + auto const& sf = sfParameterType; + + // Test copy + { + STDataType const original(sf, STI_UINT32); + + // Use aligned storage for placement new + alignas(STDataType) char buffer[sizeof(STDataType)]; + STBase* copied = original.copy(sizeof(buffer), buffer); + + BEAST_EXPECT(copied != nullptr); + auto* dt_copy = dynamic_cast(copied); + BEAST_EXPECT(dt_copy != nullptr); + BEAST_EXPECT(dt_copy->getInnerSType() == STI_UINT32); + BEAST_EXPECT(dt_copy->getFName() == sf); + + // Clean up + dt_copy->~STDataType(); + } + + // Test move + { + STDataType original(sf, STI_UINT64); + + alignas(STDataType) char buffer[sizeof(STDataType)]; + STBase* moved = original.move(sizeof(buffer), buffer); + + BEAST_EXPECT(moved != nullptr); + auto* dt_moved = dynamic_cast(moved); + BEAST_EXPECT(dt_moved != nullptr); + BEAST_EXPECT(dt_moved->getInnerSType() == STI_UINT64); + BEAST_EXPECT(dt_moved->getFName() == sf); + + // Clean up + dt_moved->~STDataType(); + } + } + + void + testSerialization() + { + testcase("serialization"); + + auto const& sf = sfParameterType; + + // Test all type serializations + struct TypeTest + { + SerializedTypeID type; + std::string expectedHex; + }; + + TypeTest const tests[] = { + {STI_UINT16, "0001"}, + {STI_UINT32, "0002"}, + {STI_UINT64, "0003"}, + {STI_UINT128, "0004"}, + {STI_UINT256, "0005"}, + {STI_AMOUNT, "0006"}, + {STI_VL, "0007"}, + {STI_ACCOUNT, "0008"}, + {STI_UINT8, "0010"}, + {STI_UINT160, "0011"}, + {STI_PATHSET, "0012"}, + {STI_VECTOR256, "0013"}, + {STI_OBJECT, "000E"}, + {STI_ARRAY, "000F"}, + {STI_ISSUE, "0018"}, + {STI_XCHAIN_BRIDGE, "0019"}, + {STI_CURRENCY, "001A"}, + {STI_UINT192, "0015"}, + {STI_NUMBER, "0009"}}; + + for (auto const& test : tests) + { + Serializer s; + STDataType dt(sf); + dt.setInnerSType(test.type); + BEAST_EXPECT(dt.getInnerSType() == test.type); + dt.add(s); + BEAST_EXPECT(strHex(s) == test.expectedHex); + } + } + + void + testEquivalence() + { + testcase("equivalence"); + + auto const& sf1 = sfParameterType; + + // Test equivalent objects + { + STDataType const dt1(sf1, STI_UINT32); + STDataType const dt2(sf1, STI_UINT32); + BEAST_EXPECT(dt1.isEquivalent(dt2)); + } + + // Test non-equivalent objects (different inner types) + { + STDataType const dt1(sf1, STI_UINT32); + STDataType const dt2(sf1, STI_UINT64); + BEAST_EXPECT(!dt1.isEquivalent(dt2)); + } + + // Test non-equivalent objects (different default states) + { + STDataType const dt1(sf1); + STDataType const dt2(sf1, STI_NOTPRESENT); + // dt1 has default_ = true (implicit from first constructor) + // dt2 has default_ = false (set in second constructor) + BEAST_EXPECT(!dt1.isEquivalent(dt2)); + } + + // Test with non-STDataType object + { + STDataType const dt1(sf1, STI_UINT32); + // Create a dummy STBase-derived object for comparison + // Since we can't easily create other STBase types here, + // we'll test that isEquivalent returns false for nullptr cast + struct DummySTBase : public STBase + { + DummySTBase() : STBase(sfInvalid) + { + } + SerializedTypeID + getSType() const override + { + return STI_NOTPRESENT; + } + void + add(Serializer&) const override + { + } + bool + isEquivalent(STBase const&) const override + { + return false; + } + bool + isDefault() const override + { + return true; + } + STBase* + copy(std::size_t, void*) const override + { + return nullptr; + } + STBase* + move(std::size_t, void*) override + { + return nullptr; + } + }; + DummySTBase const dummy; + BEAST_EXPECT(!dt1.isEquivalent(dummy)); + } + } + + void + testDefault() + { + testcase("isDefault"); + + auto const& sf = sfParameterType; + + // Test default state + { + STDataType const dt1(sf); + // First constructor doesn't set default_ explicitly, + // so it should be true (member initialization) + BEAST_EXPECT(dt1.isDefault()); + } + + { + STDataType const dt2(sf, STI_UINT32); + BEAST_EXPECT(!dt2.isDefault()); + } + } + + void + testGetText() + { + testcase("getText"); + + auto const& sf = sfParameterType; + + // Test known types + { + STDataType const dt(sf, STI_UINT8); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: UINT8}"); + } + + { + STDataType const dt(sf, STI_UINT16); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: UINT16}"); + } + + { + STDataType const dt(sf, STI_UINT32); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: UINT32}"); + } + + { + STDataType const dt(sf, STI_UINT64); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: UINT64}"); + } + + { + STDataType const dt(sf, STI_UINT128); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: UINT128}"); + } + + { + STDataType const dt(sf, STI_UINT160); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: UINT160}"); + } + + { + STDataType const dt(sf, STI_UINT192); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: UINT192}"); + } + + { + STDataType const dt(sf, STI_UINT256); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: UINT256}"); + } + + { + STDataType const dt(sf, STI_VL); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: VL}"); + } + + { + STDataType const dt(sf, STI_ACCOUNT); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: ACCOUNT}"); + } + + { + STDataType const dt(sf, STI_AMOUNT); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: AMOUNT}"); + } + + { + STDataType const dt(sf, STI_ISSUE); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: ISSUE}"); + } + + { + STDataType const dt(sf, STI_CURRENCY); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: CURRENCY}"); + } + + { + STDataType const dt(sf, STI_NUMBER); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: NUMBER}"); + } + + // Test unknown type (should return numeric string) + { + STDataType const dt(sf, static_cast(999)); + BEAST_EXPECT(dt.getText() == "STDataType{InnerType: 999}"); + } + } + + void + testGetJson() + { + testcase("getJson"); + + auto const& sf = sfParameterType; + + // Test JSON output for various types + { + STDataType const dt(sf, STI_UINT32); + Json::Value json = dt.getJson(JsonOptions::none); + BEAST_EXPECT(json.isObject()); + BEAST_EXPECT(json[jss::type].asString() == "UINT32"); + } + + { + STDataType const dt(sf, STI_AMOUNT); + Json::Value json = dt.getJson(JsonOptions::none); + BEAST_EXPECT(json.isObject()); + BEAST_EXPECT(json[jss::type].asString() == "AMOUNT"); + } + + { + STDataType const dt(sf, STI_ACCOUNT); + Json::Value json = dt.getJson(JsonOptions::none); + BEAST_EXPECT(json.isObject()); + BEAST_EXPECT(json[jss::type].asString() == "ACCOUNT"); + } + + // Test unknown type + { + STDataType const dt(sf, static_cast(999)); + Json::Value json = dt.getJson(JsonOptions::none); + BEAST_EXPECT(json.isObject()); + BEAST_EXPECT(json[jss::type].asString() == "999"); + } + } + + void + testDataTypeFromJson() + { + testcase("dataTypeFromJson"); + + auto const& sf = sfParameterType; + + // Test all valid type strings + { + Json::Value v; + v[jss::type] = "UINT8"; + STDataType const dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_UINT8); + } + + { + Json::Value v; + v[jss::type] = "UINT16"; + STDataType const dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_UINT16); + } + + { + Json::Value v; + v[jss::type] = "UINT32"; + STDataType const dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_UINT32); + } + + { + Json::Value v; + v[jss::type] = "UINT64"; + STDataType const dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_UINT64); + } + + { + Json::Value v; + v[jss::type] = "UINT128"; + STDataType const dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_UINT128); + } + + { + Json::Value v; + v[jss::type] = "UINT160"; + STDataType const dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_UINT160); + } + + { + Json::Value v; + v[jss::type] = "UINT192"; + STDataType const dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_UINT192); + } + + { + Json::Value v; + v[jss::type] = "UINT256"; + STDataType const dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_UINT256); + } + + { + Json::Value v; + v[jss::type] = "VL"; + STDataType const dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_VL); + } + + { + Json::Value v; + v[jss::type] = "ACCOUNT"; + STDataType const dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_ACCOUNT); + } + + { + Json::Value v; + v[jss::type] = "AMOUNT"; + STDataType const dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_AMOUNT); + } + + { + Json::Value v; + v[jss::type] = "ISSUE"; + STDataType const dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_ISSUE); + } + + { + Json::Value v; + v[jss::type] = "CURRENCY"; + STDataType const dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_CURRENCY); + } + + { + Json::Value v; + v[jss::type] = "NUMBER"; + STDataType const dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(dt.getInnerSType() == STI_NUMBER); + } + + // Test error cases + + // Non-object JSON should throw + { + Json::Value const v = "not an object"; + try + { + STDataType const dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(false); // Should not reach here + } + catch (std::runtime_error const& e) + { + BEAST_EXPECT(std::string(e.what()) == "STData: expected object"); + } + } + + // Unknown type string should throw + { + Json::Value v; + v[jss::type] = "UNKNOWN_TYPE"; + try + { + STDataType const dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(false); // Should not reach here + } + catch (std::runtime_error const& e) + { + BEAST_EXPECT( + std::string(e.what()) == "STData: unsupported type string: UNKNOWN_TYPE"); + } + } + + // Empty type string should throw + { + Json::Value v; + v[jss::type] = ""; + try + { + STDataType const dt = dataTypeFromJson(sf, v); + BEAST_EXPECT(false); // Should not reach here + } + catch (std::runtime_error const& e) + { + BEAST_EXPECT(std::string(e.what()) == "STData: unsupported type string: "); + } + } + } + + void + testRoundTrip() + { + testcase("round trip serialization"); + + auto const& sf = sfParameterType; + + // Test serialization and deserialization round trip + for (auto typeId : + {STI_UINT8, + STI_UINT16, + STI_UINT32, + STI_UINT64, + STI_UINT128, + STI_UINT160, + STI_UINT192, + STI_UINT256, + STI_VL, + STI_ACCOUNT, + STI_AMOUNT, + STI_ISSUE, + STI_CURRENCY, + STI_NUMBER}) + { + // Create original + STDataType const original(sf, typeId); + + // Serialize + Serializer s; + original.add(s); + + // Deserialize + SerialIter sit(s.slice()); + STDataType const deserialized(sit, sf); + + // Compare + BEAST_EXPECT(deserialized.getInnerSType() == typeId); + BEAST_EXPECT(original.isEquivalent(deserialized)); + } + } + + void + testJsonRoundTrip() + { + testcase("JSON round trip"); + + auto const& sf = sfParameterType; + + std::vector const typeStrings = { + "UINT8", + "UINT16", + "UINT32", + "UINT64", + "UINT128", + "UINT160", + "UINT192", + "UINT256", + "VL", + "ACCOUNT", + "AMOUNT", + "ISSUE", + "CURRENCY", + "NUMBER"}; + + for (auto const& typeStr : typeStrings) + { + // Create from JSON + Json::Value input; + input[jss::type] = typeStr; + STDataType const dt = dataTypeFromJson(sf, input); + + // Convert back to JSON + Json::Value output = dt.getJson(JsonOptions::none); + + // Verify + BEAST_EXPECT(output[jss::type].asString() == typeStr); + } + } + + void + testGetInnerTypeString() + { + testcase("getInnerTypeString"); + + auto const& sf = sfParameterType; + + struct TypeStringTest + { + SerializedTypeID type; + std::string expected; + }; + + TypeStringTest const tests[] = { + {STI_UINT8, "UINT8"}, + {STI_UINT16, "UINT16"}, + {STI_UINT32, "UINT32"}, + {STI_UINT64, "UINT64"}, + {STI_UINT128, "UINT128"}, + {STI_UINT160, "UINT160"}, + {STI_UINT192, "UINT192"}, + {STI_UINT256, "UINT256"}, + {STI_VL, "VL"}, + {STI_ACCOUNT, "ACCOUNT"}, + {STI_AMOUNT, "AMOUNT"}, + {STI_ISSUE, "ISSUE"}, + {STI_CURRENCY, "CURRENCY"}, + {STI_NUMBER, "NUMBER"}, + {static_cast(999), "999"} // Unknown type + }; + + for (auto const& test : tests) + { + STDataType const dt(sf, test.type); + BEAST_EXPECT(dt.getInnerTypeString() == test.expected); + } + } + + void + run() override + { + testConstructors(); + testCopyMove(); + testSerialization(); + testEquivalence(); + testDefault(); + testGetText(); + testGetJson(); + testDataTypeFromJson(); + testRoundTrip(); + testJsonRoundTrip(); + testGetInnerTypeString(); + } +}; + +BEAST_DEFINE_TESTSUITE(STDataType, protocol, xrpl); + +} // namespace xrpl diff --git a/src/test/protocol/STData_test.cpp b/src/test/protocol/STData_test.cpp new file mode 100644 index 00000000000..c1451fa6f1e --- /dev/null +++ b/src/test/protocol/STData_test.cpp @@ -0,0 +1,1290 @@ +#include +#include +#include +#include +#include + +namespace xrpl { + +struct STData_test : public beast::unit_test::suite +{ + void + testConstructors() + { + testcase("Constructors"); + + auto const& sf = sfParameterValue; + + // Default constructor + { + STData const data(sf); + BEAST_EXPECT(data.getSType() == STI_DATA); + BEAST_EXPECT(data.isDefault()); + } + + // Type-specific constructors + { + // UINT8 + STData const data_u8(sf, static_cast(8)); + BEAST_EXPECT(data_u8.getFieldU8() == 8); + BEAST_EXPECT(data_u8.getInnerTypeString() == "UINT8"); + BEAST_EXPECT(data_u8.isDefault()); + + // UINT16 + STData const data_u16(sf, static_cast(16)); + BEAST_EXPECT(data_u16.getFieldU16() == 16); + BEAST_EXPECT(data_u16.getInnerTypeString() == "UINT16"); + + // UINT32 + STData const data_u32(sf, static_cast(32)); + BEAST_EXPECT(data_u32.getFieldU32() == 32); + BEAST_EXPECT(data_u32.getInnerTypeString() == "UINT32"); + + // UINT64 + STData const data_u64(sf, static_cast(64)); + BEAST_EXPECT(data_u64.getFieldU64() == 64); + BEAST_EXPECT(data_u64.getInnerTypeString() == "UINT64"); + + // UINT128 + uint128 const val128 = uint128(1); + STData const data_u128(sf, val128); + BEAST_EXPECT(data_u128.getFieldH128() == val128); + BEAST_EXPECT(data_u128.getInnerTypeString() == "UINT128"); + + // UINT160 + uint160 const val160 = uint160(1); + STData const data_u160(sf, val160); + BEAST_EXPECT(data_u160.getFieldH160() == val160); + BEAST_EXPECT(data_u160.getInnerTypeString() == "UINT160"); + + // UINT192 + uint192 const val192 = uint192(1); + STData const data_u192(sf, val192); + BEAST_EXPECT(data_u192.getFieldH192() == val192); + BEAST_EXPECT(data_u192.getInnerTypeString() == "UINT192"); + + // UINT256 + uint256 const val256 = uint256(1); + STData const data_u256(sf, val256); + BEAST_EXPECT(data_u256.getFieldH256() == val256); + BEAST_EXPECT(data_u256.getInnerTypeString() == "UINT256"); + + // Blob + Blob const blob = strUnHex("DEADBEEFCAFEBABE").value(); + STData const data_blob(sf, blob); + BEAST_EXPECT(data_blob.getFieldVL() == blob); + BEAST_EXPECT(data_blob.getInnerTypeString() == "VL"); + + // Slice + std::string test_str = "Hello World"; + Slice const slice(test_str.data(), test_str.size()); + STData const data_slice(sf, slice); + Blob const expected_blob(test_str.begin(), test_str.end()); + BEAST_EXPECT(data_slice.getFieldVL() == expected_blob); + + // AccountID + AccountID const account(0x123456789ABCDEF0); + STData const data_account(sf, account); + BEAST_EXPECT(data_account.getAccountID() == account); + BEAST_EXPECT(data_account.getInnerTypeString() == "ACCOUNT"); + + // STAmount (Native) + STAmount const amount_native(1000); + STData const data_amount_native(sf, amount_native); + BEAST_EXPECT(data_amount_native.getFieldAmount() == amount_native); + BEAST_EXPECT(data_amount_native.getInnerTypeString() == "AMOUNT"); + + // STAmount (IOU) + IOUAmount const iou_amount(5000); + Issue const usd( + Currency(0x5553440000000000), + parseBase58("rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn").value()); + STAmount const amount_iou(iou_amount, usd); + STData const data_amount_iou(sf, amount_iou); + BEAST_EXPECT(data_amount_iou.getFieldAmount() == amount_iou); + } + } + + void + testSerializationDeserialization() + { + testcase("Serialization/Deserialization"); + + auto const& sf = sfParameterValue; + + // Test each type's serialization and deserialization round-trip + { + // UINT8 + std::uint8_t const original_u8 = 8; + STData const data_u8(sf, original_u8); + + Serializer s; + data_u8.add(s); + + SerialIter sit(s.slice()); + STData const deserialized_u8(sit, sf); + + BEAST_EXPECT(deserialized_u8.getFieldU8() == original_u8); + BEAST_EXPECT(deserialized_u8.getInnerTypeString() == "UINT8"); + } + + { + // UINT16 + std::uint16_t const original_u16 = 16; + STData const data_u16(sf, original_u16); + + Serializer s; + data_u16.add(s); + + SerialIter sit(s.slice()); + STData const deserialized_u16(sit, sf); + + BEAST_EXPECT(deserialized_u16.getFieldU16() == original_u16); + BEAST_EXPECT(deserialized_u16.getInnerTypeString() == "UINT16"); + } + + { + // UINT32 + std::uint32_t const original_u32 = 32; + STData const data_u32(sf, original_u32); + + Serializer s; + data_u32.add(s); + + SerialIter sit(s.slice()); + STData const deserialized_u32(sit, sf); + + BEAST_EXPECT(deserialized_u32.getFieldU32() == original_u32); + BEAST_EXPECT(deserialized_u32.getInnerTypeString() == "UINT32"); + } + + { + // UINT64 + std::uint64_t const original_u64 = 64; + STData const data_u64(sf, original_u64); + + Serializer s; + data_u64.add(s); + + SerialIter sit(s.slice()); + STData const deserialized_u64(sit, sf); + + BEAST_EXPECT(deserialized_u64.getFieldU64() == original_u64); + BEAST_EXPECT(deserialized_u64.getInnerTypeString() == "UINT64"); + } + + { + // UINT128 + uint128 const original_u128 = uint128(1); + STData const data_u128(sf, original_u128); + + Serializer s; + data_u128.add(s); + + SerialIter sit(s.slice()); + STData const deserialized_u128(sit, sf); + + BEAST_EXPECT(deserialized_u128.getFieldH128() == original_u128); + BEAST_EXPECT(deserialized_u128.getInnerTypeString() == "UINT128"); + } + + { + // UINT160 + uint160 const original_u160 = uint160(1); + STData const data_u160(sf, original_u160); + + Serializer s; + data_u160.add(s); + + SerialIter sit(s.slice()); + STData const deserialized_u160(sit, sf); + + BEAST_EXPECT(deserialized_u160.getFieldH160() == original_u160); + BEAST_EXPECT(deserialized_u160.getInnerTypeString() == "UINT160"); + } + + { + // UINT192 + uint192 const original_u192 = uint192(1); + STData const data_u192(sf, original_u192); + + Serializer s; + data_u192.add(s); + + SerialIter sit(s.slice()); + STData const deserialized_u192(sit, sf); + + BEAST_EXPECT(deserialized_u192.getFieldH192() == original_u192); + BEAST_EXPECT(deserialized_u192.getInnerTypeString() == "UINT192"); + } + + { + // UINT256 + uint256 const original_u256 = uint256(1); + STData const data_u256(sf, original_u256); + + Serializer s; + data_u256.add(s); + + SerialIter sit(s.slice()); + STData const deserialized_u256(sit, sf); + + BEAST_EXPECT(deserialized_u256.getFieldH256() == original_u256); + BEAST_EXPECT(deserialized_u256.getInnerTypeString() == "UINT256"); + } + + { + // VL (Variable Length) + Blob const original_blob = strUnHex("DEADBEEFCAFEBABE1234567890ABCDEF").value(); + STData const data_vl(sf, original_blob); + + Serializer s; + data_vl.add(s); + + SerialIter sit(s.slice()); + STData const deserialized_vl(sit, sf); + + BEAST_EXPECT(deserialized_vl.getFieldVL() == original_blob); + BEAST_EXPECT(deserialized_vl.getInnerTypeString() == "VL"); + } + + { + // ACCOUNT + AccountID const original_account(0xFEDCBA9876543210); + STData const data_account(sf, original_account); + + Serializer s; + data_account.add(s); + + SerialIter sit(s.slice()); + STData const deserialized_account(sit, sf); + + BEAST_EXPECT(deserialized_account.getAccountID() == original_account); + BEAST_EXPECT(deserialized_account.getInnerTypeString() == "ACCOUNT"); + } + + { + // AMOUNT (Native) + STAmount const original_amount(99999); + STData const data_amount(sf, original_amount); + + Serializer s; + data_amount.add(s); + + SerialIter sit(s.slice()); + STData const deserialized_amount(sit, sf); + + BEAST_EXPECT(deserialized_amount.getFieldAmount() == original_amount); + BEAST_EXPECT(deserialized_amount.getInnerTypeString() == "AMOUNT"); + } + + { + // CURRENCY + } + + { + // ISSUE + } + + { + // NUMBER + } + } + + void + testSettersAndGetters() + { + testcase("Setters and Getters"); + + auto const& sf = sfParameterValue; + STData data(sf); + + // Test all setter/getter combinations + { + // UINT8 + unsigned char const val_u8 = 8; + data.setFieldU8(val_u8); + BEAST_EXPECT(data.getFieldU8() == val_u8); + BEAST_EXPECT(data.getInnerTypeString() == "UINT8"); + } + + { + // UINT16 + std::uint16_t const val_u16 = 16; + data.setFieldU16(val_u16); + BEAST_EXPECT(data.getFieldU16() == val_u16); + BEAST_EXPECT(data.getInnerTypeString() == "UINT16"); + } + + { + // UINT32 + std::uint32_t const val_u32 = 32; + data.setFieldU32(val_u32); + BEAST_EXPECT(data.getFieldU32() == val_u32); + BEAST_EXPECT(data.getInnerTypeString() == "UINT32"); + } + + { + // UINT64 + std::uint64_t const val_u64 = 64; + data.setFieldU64(val_u64); + BEAST_EXPECT(data.getFieldU64() == val_u64); + BEAST_EXPECT(data.getInnerTypeString() == "UINT64"); + } + + { + // UINT128 + uint128 const val_u128 = uint128(1); + data.setFieldH128(val_u128); + BEAST_EXPECT(data.getFieldH128() == val_u128); + BEAST_EXPECT(data.getInnerTypeString() == "UINT128"); + } + + { + // UINT160 + uint160 const val_u160 = uint160(1); + data.setFieldH160(val_u160); + BEAST_EXPECT(data.getFieldH160() == val_u160); + BEAST_EXPECT(data.getInnerTypeString() == "UINT160"); + } + + { + // UINT192 + uint192 const val_u192 = uint192(1); + data.setFieldH192(val_u192); + BEAST_EXPECT(data.getFieldH192() == val_u192); + BEAST_EXPECT(data.getInnerTypeString() == "UINT192"); + } + + { + // UINT256 + uint256 const val_u256 = uint256(1); + data.setFieldH256(val_u256); + BEAST_EXPECT(data.getFieldH256() == val_u256); + BEAST_EXPECT(data.getInnerTypeString() == "UINT256"); + } + + { + // VL (Variable Length) - Blob + Blob const val_blob = strUnHex("0102030405060708090A0B0C0D0E0F10").value(); + data.setFieldVL(val_blob); + BEAST_EXPECT(data.getFieldVL() == val_blob); + BEAST_EXPECT(data.getInnerTypeString() == "VL"); + } + + { + // VL (Variable Length) - Slice + std::string test_str = "Test String for Slice"; + Slice const val_slice(test_str.data(), test_str.size()); + data.setFieldVL(val_slice); + Blob const expected_blob(test_str.begin(), test_str.end()); + BEAST_EXPECT(data.getFieldVL() == expected_blob); + BEAST_EXPECT(data.getInnerTypeString() == "VL"); + } + + { + // ACCOUNT + AccountID const val_account(0x123456789ABCDEF0); + data.setAccountID(val_account); + BEAST_EXPECT(data.getAccountID() == val_account); + BEAST_EXPECT(data.getInnerTypeString() == "ACCOUNT"); + } + + { + // AMOUNT + STAmount const val_amount(777777); + data.setFieldAmount(val_amount); + BEAST_EXPECT(data.getFieldAmount() == val_amount); + BEAST_EXPECT(data.getInnerTypeString() == "AMOUNT"); + } + + { + // CURRENCY + } + + { + // ISSUE + } + + { + // NUMBER + } + } + + void + testJsonConversion() + { + testcase("JSON Conversion"); + + auto const& sf = sfParameterValue; + + // Test JSON serialization for each type + { + // UINT8 + STData const data_u8(sf, static_cast(8)); + Json::Value json_u8 = data_u8.getJson(JsonOptions::none); + BEAST_EXPECT(json_u8[jss::type].asString() == "UINT8"); + BEAST_EXPECT(json_u8[jss::value].asUInt() == 8); + } + + { + // UINT16 + STData const data_u16(sf, static_cast(16)); + Json::Value json_u16 = data_u16.getJson(JsonOptions::none); + BEAST_EXPECT(json_u16[jss::type].asString() == "UINT16"); + BEAST_EXPECT(json_u16[jss::value].asUInt() == 16); + } + + { + // UINT32 + STData const data_u32(sf, static_cast(32)); + Json::Value json_u32 = data_u32.getJson(JsonOptions::none); + BEAST_EXPECT(json_u32[jss::type].asString() == "UINT32"); + BEAST_EXPECT(json_u32[jss::value].asUInt() == 32); + } + + { + // UINT64 + STData const data_u64(sf, static_cast(64)); + Json::Value json_u64 = data_u64.getJson(JsonOptions::none); + BEAST_EXPECT(json_u64[jss::type].asString() == "UINT64"); + BEAST_EXPECT(json_u64[jss::value].asString() == "40"); + } + + { + // UINT128 + uint128 const val_u128 = uint128(1); + STData const data_u128(sf, val_u128); + Json::Value json_u128 = data_u128.getJson(JsonOptions::none); + BEAST_EXPECT(json_u128[jss::type].asString() == "UINT128"); + BEAST_EXPECT(json_u128[jss::value].asString() == "00000000000000000000000000000001"); + } + + { + // UINT160 + uint160 const val_u160 = uint160(1); + STData const data_u160(sf, val_u160); + Json::Value json_u160 = data_u160.getJson(JsonOptions::none); + BEAST_EXPECT(json_u160[jss::type].asString() == "UINT160"); + BEAST_EXPECT( + json_u160[jss::value].asString() == "0000000000000000000000000000000000000001"); + } + + { + // UINT192 + uint192 const val_u192 = uint192(1); + STData const data_u192(sf, val_u192); + Json::Value json_u192 = data_u192.getJson(JsonOptions::none); + BEAST_EXPECT(json_u192[jss::type].asString() == "UINT192"); + BEAST_EXPECT( + json_u192[jss::value].asString() == + "000000000000000000000000000000000000000000000001"); + } + + { + // UINT256 + uint256 const val_u256 = uint256(1); + STData const data_u256(sf, val_u256); + Json::Value json_u256 = data_u256.getJson(JsonOptions::none); + BEAST_EXPECT(json_u256[jss::type].asString() == "UINT256"); + BEAST_EXPECT( + json_u256[jss::value].asString() == + "00000000000000000000000000000000000000000000000000000000000000" + "01"); + } + + { + // VL + Blob const blob = strUnHex("DEADBEEF").value(); + STData const data_vl(sf, blob); + Json::Value json_vl = data_vl.getJson(JsonOptions::none); + BEAST_EXPECT(json_vl[jss::type].asString() == "VL"); + BEAST_EXPECT(json_vl[jss::value].asString() == "DEADBEEF"); + } + + { + // ACCOUNT + AccountID const account(0x123456789ABCDEF0); + STData const data_account(sf, account); + Json::Value json_account = data_account.getJson(JsonOptions::none); + BEAST_EXPECT(json_account[jss::type].asString() == "ACCOUNT"); + BEAST_EXPECT(json_account[jss::value].asString() == "rrrrrrrrrrrrrLveWzSkxhcH3hGw6"); + } + + { + // AMOUNT + STAmount const amount(1000); + STData const data_amount(sf, amount); + Json::Value json_amount = data_amount.getJson(JsonOptions::none); + BEAST_EXPECT(json_amount[jss::type].asString() == "AMOUNT"); + BEAST_EXPECT(json_amount[jss::value].asString() == "1000"); + } + + { + // CURRENCY + } + + { + // ISSUE + } + + { + // NUMBER + } + } + + void + testDataFromJson() + { + testcase("Data From JSON"); + + auto const& sf = sfParameterValue; + + // Test JSON deserialization for each type + { + // UINT8 + Json::Value json_u8(Json::objectValue); + json_u8[jss::type] = "UINT8"; + json_u8[jss::value] = 8; + + STData const data_u8 = dataFromJson(sf, json_u8); + BEAST_EXPECT(data_u8.getFieldU8() == 8); + BEAST_EXPECT(data_u8.getInnerTypeString() == "UINT8"); + } + + { + // UINT16 + Json::Value json_u16(Json::objectValue); + json_u16[jss::type] = "UINT16"; + json_u16[jss::value] = 16; + + STData const data_u16 = dataFromJson(sf, json_u16); + BEAST_EXPECT(data_u16.getFieldU16() == 16); + BEAST_EXPECT(data_u16.getInnerTypeString() == "UINT16"); + } + + { + // UINT32 + Json::Value json_u32(Json::objectValue); + json_u32[jss::type] = "UINT32"; + json_u32[jss::value] = 32; + + STData const data_u32 = dataFromJson(sf, json_u32); + BEAST_EXPECT(data_u32.getFieldU32() == 32); + BEAST_EXPECT(data_u32.getInnerTypeString() == "UINT32"); + } + + { + // UINT64 + Json::Value json_u64(Json::objectValue); + json_u64[jss::type] = "UINT64"; + json_u64[jss::value] = 64; + STData const data_u64 = dataFromJson(sf, json_u64); + BEAST_EXPECT(data_u64.getFieldU64() == 64); + BEAST_EXPECT(data_u64.getInnerTypeString() == "UINT64"); + } + + { + // UINT128 + Json::Value json_u128(Json::objectValue); + json_u128[jss::type] = "UINT128"; + json_u128[jss::value] = "00000000000000000000000000000001"; + STData const data_u128 = dataFromJson(sf, json_u128); + uint128 expected; + bool const ok = expected.parseHex("00000000000000000000000000000001"); + BEAST_EXPECT(ok); + BEAST_EXPECT(data_u128.getFieldH128() == expected); + BEAST_EXPECT(data_u128.getInnerTypeString() == "UINT128"); + } + + { + // UINT160 + Json::Value json_u160(Json::objectValue); + json_u160[jss::type] = "UINT160"; + json_u160[jss::value] = "0000000000000000000000000000000000000001"; + STData const data_u160 = dataFromJson(sf, json_u160); + uint160 expected; + bool const ok = expected.parseHex("0000000000000000000000000000000000000001"); + BEAST_EXPECT(ok); + BEAST_EXPECT(data_u160.getFieldH160() == expected); + BEAST_EXPECT(data_u160.getInnerTypeString() == "UINT160"); + } + + { + // UINT192 + Json::Value json_u192(Json::objectValue); + json_u192[jss::type] = "UINT192"; + json_u192[jss::value] = "000000000000000000000000000000000000000000000001"; + STData const data_u192 = dataFromJson(sf, json_u192); + uint192 expected; + bool const ok = expected.parseHex("000000000000000000000000000000000000000000000001"); + BEAST_EXPECT(ok); + BEAST_EXPECT(data_u192.getFieldH192() == expected); + BEAST_EXPECT(data_u192.getInnerTypeString() == "UINT192"); + } + + { + // UINT256 + Json::Value json_u256(Json::objectValue); + json_u256[jss::type] = "UINT256"; + json_u256[jss::value] = + "00000000000000000000000000000000000000000000000000000000000000" + "01"; + STData const data_u256 = dataFromJson(sf, json_u256); + uint256 expected; + bool const ok = expected.parseHex( + "00000000000000000000000000000000000000000000000000000000000000" + "01"); + BEAST_EXPECT(ok); + BEAST_EXPECT(data_u256.getFieldH256() == expected); + BEAST_EXPECT(data_u256.getInnerTypeString() == "UINT256"); + } + + { + // VL + Json::Value json_vl(Json::objectValue); + json_vl[jss::type] = "VL"; + json_vl[jss::value] = "DEADBEEFCAFEBABE"; + + STData const data_vl = dataFromJson(sf, json_vl); + Blob const expected_blob = strUnHex("DEADBEEFCAFEBABE").value(); + BEAST_EXPECT(data_vl.getFieldVL() == expected_blob); + BEAST_EXPECT(data_vl.getInnerTypeString() == "VL"); + } + + { + // ACCOUNT + Json::Value json_account(Json::objectValue); + json_account[jss::type] = "ACCOUNT"; + json_account[jss::value] = "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn"; + + STData const data_account = dataFromJson(sf, json_account); + AccountID const expected_account = + parseBase58("rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn").value(); + BEAST_EXPECT(data_account.getAccountID() == expected_account); + BEAST_EXPECT(data_account.getInnerTypeString() == "ACCOUNT"); + } + + { + // AMOUNT + Json::Value json_amount(Json::objectValue); + json_amount[jss::type] = "AMOUNT"; + json_amount[jss::value] = "1000"; + + STData const data_amount = dataFromJson(sf, json_amount); + STAmount const expected_amount(1000); + BEAST_EXPECT(data_amount.getFieldAmount() == expected_amount); + BEAST_EXPECT(data_amount.getInnerTypeString() == "AMOUNT"); + } + + { + // CURRENCY + } + + { + // ISSUE + } + + { + // NUMBER + } + } + + // void + // testErrorCases() + // { + // testcase("Error Cases"); + + // auto const& sf = sfParameterValue; + + // // Test JSON parsing errors + // { + // // Missing type + // Json::Value json_no_type(Json::objectValue); + // json_no_type[jss::value] = 123; + + // try { + // STData data = dataFromJson(sf, json_no_type); + // fail("Expected exception for missing type"); + // } catch (std::runtime_error const& e) { + // pass(); + // } + // } + + // { + // // Missing value + // Json::Value json_no_value(Json::objectValue); + // json_no_value[jss::type] = "UINT8"; + + // try { + // STData data = dataFromJson(sf, json_no_value); + // fail("Expected exception for missing value"); + // } catch (std::runtime_error const& e) { + // pass(); + // } + // } + + // { + // // Invalid type string + // Json::Value json_invalid_type(Json::objectValue); + // json_invalid_type[jss::type] = "INVALID_TYPE"; + // json_invalid_type[jss::value] = 123; + + // try { + // STData data = dataFromJson(sf, json_invalid_type); + // fail("Expected exception for invalid type"); + // } catch (std::runtime_error const& e) { + // pass(); + // } + // } + + // { + // // Invalid UINT256 hex + // Json::Value json_invalid_hex(Json::objectValue); + // json_invalid_hex[jss::type] = "UINT256"; + // json_invalid_hex[jss::value] = "INVALID_HEX_STRING"; + + // try { + // STData data = dataFromJson(sf, json_invalid_hex); + // fail("Expected exception for invalid hex"); + // } catch (std::runtime_error const& e) { + // pass(); + // } + // } + + // { + // // Invalid VL hex + // Json::Value json_invalid_vl(Json::objectValue); + // json_invalid_vl[jss::type] = "VL"; + // json_invalid_vl[jss::value] = "INVALID_HEX"; + + // try { + // STData data = dataFromJson(sf, json_invalid_vl); + // fail("Expected exception for invalid VL data"); + // } catch (std::invalid_argument const& e) { + // pass(); + // } + // } + + // { + // // Invalid account + // Json::Value json_invalid_account(Json::objectValue); + // json_invalid_account[jss::type] = "ACCOUNT"; + // json_invalid_account[jss::value] = "INVALID_ACCOUNT_STRING"; + + // try { + // STData data = dataFromJson(sf, json_invalid_account); + // fail("Expected exception for invalid account"); + // } catch (std::runtime_error const& e) { + // pass(); + // } + // } + + // { + // // Non-object JSON + // Json::Value json_not_object = "not an object"; + + // try { + // STData data = dataFromJson(sf, json_not_object); + // fail("Expected exception for non-object JSON"); + // } catch (std::runtime_error const& e) { + // pass(); + // } + // } + // } + + // void + // testEquivalence() + // { + // testcase("Equivalence"); + + // auto const& sf = sfParameterValue; + + // // Test equivalence for same types and values + // { + // STData data1(sf, static_cast(42)); + // STData data2(sf, static_cast(42)); + // BEAST_EXPECT(data1.isEquivalent(data2)); + // } + + // // Test non-equivalence for different values + // { + // STData data1(sf, static_cast(42)); + // STData data2(sf, static_cast(43)); + // BEAST_EXPECT(!data1.isEquivalent(data2)); + // } + + // // Test non-equivalence for different types + // { + // STData data1(sf, static_cast(42)); + // STData data2(sf, static_cast(42)); + // BEAST_EXPECT(!data1.isEquivalent(data2)); + // } + + // // Test equivalence with complex types + // { + // uint256 val; + // val.parseHex("123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0"); + // STData data1(sf, val); + // STData data2(sf, val); + // BEAST_EXPECT(data1.isEquivalent(data2)); + // } + // } + + // void + // testSize() + // { + // testcase("Size Calculation"); + + // auto const& sf = sfParameterValue; + + // // Test size calculation for each type + // { + // STData data_u8(sf, static_cast(42)); + // BEAST_EXPECT(data_u8.size() == sizeof(uint8_t)); + // } + + // { + // STData data_u16(sf, static_cast(1234)); + // BEAST_EXPECT(data_u16.size() == sizeof(uint16_t)); + // } + + // { + // STData data_u32(sf, static_cast(123456)); + // BEAST_EXPECT(data_u32.size() == sizeof(uint32_t)); + // } + + // { + // STData data_u64(sf, static_cast(123456789)); + // BEAST_EXPECT(data_u64.size() == sizeof(uint64_t)); + // } + + // { + // uint128 val_u128(0x12345678, 0x9ABCDEF0); + // STData data_u128(sf, val_u128); + // BEAST_EXPECT(data_u128.size() == uint128::size()); + // } + + // { + // uint256 val_u256; + // val_u256.parseHex("123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0"); + // STData data_u256(sf, val_u256); + // BEAST_EXPECT(data_u256.size() == uint256::size()); + // } + + // { + // Blob blob = strUnHex("DEADBEEFCAFEBABE").value(); + // STData data_vl(sf, blob); + // BEAST_EXPECT(data_vl.size() == blob.size()); + // } + + // { + // AccountID account(0x123456789ABCDEF0); + // STData data_account(sf, account); + // BEAST_EXPECT(data_account.size() == uint160::size()); + // } + + // { + // // Native amount + // STAmount amount_native(1000); + // STData data_amount_native(sf, amount_native); + // BEAST_EXPECT(data_amount_native.size() == 8); // Native amounts + // are 8 bytes + // } + + // { + // // IOU amount + // IOUAmount iou_amount(5000); + // Issue const usd( + // Currency(0x5553440000000000), + // parseBase58("rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn").value()); + // STAmount amount_iou(iou_amount, usd); + // STData data_amount_iou(sf, amount_iou); + // BEAST_EXPECT(data_amount_iou.size() == 48); // IOU amounts are 48 + // bytes + // } + // } + + // void + // testTextRepresentation() + // { + // testcase("Text Representation"); + + // auto const& sf = sfParameterValue; + + // // Test getText() for various types + // { + // STData data_u8(sf, static_cast(42)); + // std::string text = data_u8.getText(); + // BEAST_EXPECT(text.find("STData") != std::string::npos); + // BEAST_EXPECT(text.find("UINT8") != std::string::npos); + // } + + // { + // uint256 val; + // val.parseHex("123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0"); + // STData data_u256(sf, val); + // std::string text = data_u256.getText(); + // BEAST_EXPECT(text.find("STData") != std::string::npos); + // BEAST_EXPECT(text.find("UINT256") != std::string::npos); + // } + + // { + // Blob blob = strUnHex("DEADBEEF").value(); + // STData data_vl(sf, blob); + // std::string text = data_vl.getText(); + // BEAST_EXPECT(text.find("STData") != std::string::npos); + // BEAST_EXPECT(text.find("VL") != std::string::npos); + // } + // } + + // void + // testCopyAndMove() + // { + // testcase("Copy and Move Operations"); + + // auto const& sf = sfParameterValue; + + // // Test copy functionality + // { + // uint256 val; + // val.parseHex("123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0"); + // STData original(sf, val); + + // // Test copy + // char buffer[1024]; + // STBase* copied = original.copy(sizeof(buffer), buffer); + // BEAST_EXPECT(copied != nullptr); + + // STData* copied_data = dynamic_cast(copied); + // BEAST_EXPECT(copied_data != nullptr); + // BEAST_EXPECT(copied_data->getFieldH256() == val); + // BEAST_EXPECT(copied_data->getInnerTypeString() == "UINT256"); + // } + + // // Test move functionality + // { + // Blob blob = strUnHex("DEADBEEFCAFEBABE").value(); + // STData original(sf, blob); + + // char buffer[1024]; + // STBase* moved = original.move(sizeof(buffer), buffer); + // BEAST_EXPECT(moved != nullptr); + + // STData* moved_data = dynamic_cast(moved); + // BEAST_EXPECT(moved_data != nullptr); + // BEAST_EXPECT(moved_data->getFieldVL() == blob); + // BEAST_EXPECT(moved_data->getInnerTypeString() == "VL"); + // } + // } + + // void + // testBoundaryValues() + // { + // testcase("Boundary Values"); + + // auto const& sf = sfParameterValue; + + // // Test minimum and maximum values for each numeric type + // { + // // UINT8 boundaries + // STData data_u8_min(sf, static_cast(0)); + // BEAST_EXPECT(data_u8_min.getFieldU8() == 0); + + // STData data_u8_max(sf, static_cast(255)); + // BEAST_EXPECT(data_u8_max.getFieldU8() == 255); + // } + + // { + // // UINT16 boundaries + // STData data_u16_min(sf, static_cast(0)); + // BEAST_EXPECT(data_u16_min.getFieldU16() == 0); + + // STData data_u16_max(sf, static_cast(65535)); + // BEAST_EXPECT(data_u16_max.getFieldU16() == 65535); + // } + + // { + // // UINT32 boundaries + // STData data_u32_min(sf, static_cast(0)); + // BEAST_EXPECT(data_u32_min.getFieldU32() == 0); + + // STData data_u32_max(sf, static_cast(0xFFFFFFFF)); + // BEAST_EXPECT(data_u32_max.getFieldU32() == 0xFFFFFFFF); + // } + + // { + // // UINT64 boundaries + // STData data_u64_min(sf, static_cast(0)); + // BEAST_EXPECT(data_u64_min.getFieldU64() == 0); + + // STData data_u64_max(sf, + // static_cast(0xFFFFFFFFFFFFFFFF)); + // BEAST_EXPECT(data_u64_max.getFieldU64() == 0xFFFFFFFFFFFFFFFF); + // } + + // { + // // Empty blob + // Blob empty_blob; + // STData data_empty_vl(sf, empty_blob); + // BEAST_EXPECT(data_empty_vl.getFieldVL() == empty_blob); + // BEAST_EXPECT(data_empty_vl.size() == 0); + // } + + // { + // // Large blob (test with reasonably sized data) + // Blob large_blob(1000, 0xAB); // 1000 bytes of 0xAB + // STData data_large_vl(sf, large_blob); + // BEAST_EXPECT(data_large_vl.getFieldVL() == large_blob); + // BEAST_EXPECT(data_large_vl.size() == 1000); + // } + // } + + // void + // testSpecialSerializationCases() + // { + // testcase("Special Serialization Cases"); + + // auto const& sf = sfParameterValue; + + // // Test serialization format compliance + // { + // // Verify type prefix is included in serialization + // STData data_u8(sf, static_cast(0x42)); + // Serializer s; + // data_u8.add(s); + + // // Should start with type identifier (STI_UINT8 = 0x0010) + // auto slice = s.slice(); + // BEAST_EXPECT(slice.size() >= 2); + // std::uint16_t type_id = (static_cast(slice[0]) << + // 8) | slice[1]; BEAST_EXPECT(type_id == STI_UINT8); + // } + + // { + // // Test VL serialization includes length + // Blob test_blob = strUnHex("DEADBEEF").value(); + // STData data_vl(sf, test_blob); + // Serializer s; + // data_vl.add(s); + + // auto slice = s.slice(); + // BEAST_EXPECT(slice.size() >= 2); + // std::uint16_t type_id = (static_cast(slice[0]) << + // 8) | slice[1]; BEAST_EXPECT(type_id == STI_VL); + // } + // } + + // void + // testTypeConsistency() + // { + // testcase("Type Consistency"); + + // auto const& sf = sfParameterValue; + + // // Verify that changing types properly updates internal state + // { + // STData data(sf); + + // // Start with UINT8 + // data.setFieldU8(42); + // BEAST_EXPECT(data.getInnerTypeString() == "UINT8"); + // BEAST_EXPECT(data.getFieldU8() == 42); + + // // Change to UINT16 + // data.setFieldU16(1234); + // BEAST_EXPECT(data.getInnerTypeString() == "UINT16"); + // BEAST_EXPECT(data.getFieldU16() == 1234); + + // // Change to VL + // Blob blob = strUnHex("DEADBEEF").value(); + // data.setFieldVL(blob); + // BEAST_EXPECT(data.getInnerTypeString() == "VL"); + // BEAST_EXPECT(data.getFieldVL() == blob); + + // // Change to UINT256 + // uint256 val; + // val.parseHex("123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0"); + // data.setFieldH256(val); + // BEAST_EXPECT(data.getInnerTypeString() == "UINT256"); + // BEAST_EXPECT(data.getFieldH256() == val); + // } + // } + + // void + // testComplexAmountTypes() + // { + // testcase("Complex Amount Types"); + + // auto const& sf = sfParameterValue; + + // // Test various STAmount configurations + // { + // // Zero native amount + // STAmount zero_native(0); + // STData data_zero(sf, zero_native); + // BEAST_EXPECT(data_zero.getFieldAmount() == zero_native); + // BEAST_EXPECT(data_zero.size() == 8); // Native amounts are 8 + // bytes + // } + + // { + // // Maximum native amount + // STAmount max_native(100000000000000000ULL); // Max XRP in drops + // STData data_max(sf, max_native); + // BEAST_EXPECT(data_max.getFieldAmount() == max_native); + // } + + // { + // // IOU with zero value + // IOUAmount zero_iou(0); + // Issue const eur( + // Currency(0x4555520000000000), + // parseBase58("rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn").value()); + // STAmount zero_iou_amount(zero_iou, eur); + // STData data_zero_iou(sf, zero_iou_amount); + // BEAST_EXPECT(data_zero_iou.getFieldAmount() == zero_iou_amount); + // BEAST_EXPECT(data_zero_iou.size() == 48); // IOU amounts are 48 + // bytes + // } + // } + + // void + // testJsonRoundTrip() + // { + // testcase("JSON Round Trip"); + + // auto const& sf = sfParameterValue; + + // // Test complete round trip: STData -> JSON -> STData + // { + // // UINT8 + // STData original_u8(sf, static_cast(123)); + // Json::Value json_u8 = original_u8.getJson(JsonOptions::none); + // STData restored_u8 = dataFromJson(sf, json_u8); + // BEAST_EXPECT(original_u8.isEquivalent(restored_u8)); + // } + + // { + // // UINT256 + // uint256 val; + // val.parseHex("FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210"); + // STData original_u256(sf, val); + // Json::Value json_u256 = original_u256.getJson(JsonOptions::none); + // STData restored_u256 = dataFromJson(sf, json_u256); + // BEAST_EXPECT(original_u256.isEquivalent(restored_u256)); + // } + + // { + // // VL + // Blob blob = strUnHex("0123456789ABCDEF").value(); + // STData original_vl(sf, blob); + // Json::Value json_vl = original_vl.getJson(JsonOptions::none); + // STData restored_vl = dataFromJson(sf, json_vl); + // BEAST_EXPECT(original_vl.isEquivalent(restored_vl)); + // } + + // { + // // ACCOUNT + // AccountID account_id = + // parseBase58("rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn").value(); + // STData original_account(sf, account_id); + // Json::Value json_account = + // original_account.getJson(JsonOptions::none); STData + // restored_account = dataFromJson(sf, json_account); + // BEAST_EXPECT(original_account.isEquivalent(restored_account)); + // } + // } + + // void + // testSerializationRoundTrip() + // { + // testcase("Serialization Round Trip"); + + // auto const& sf = sfParameterValue; + + // // Test complete serialization round trip for all types + // std::vector test_data; + + // // Populate test data with various types + // test_data.emplace_back(sf, static_cast(0xFF)); + // test_data.emplace_back(sf, static_cast(0xFFFF)); + // test_data.emplace_back(sf, static_cast(0xFFFFFFFF)); + // test_data.emplace_back(sf, + // static_cast(0xFFFFFFFFFFFFFFFF)); + + // uint128 val128(0xFFFFFFFF, 0xFFFFFFFF); + // test_data.emplace_back(sf, val128); + + // uint256 val256; + // val256.parseHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + // test_data.emplace_back(sf, val256); + + // Blob blob = strUnHex("DEADBEEFCAFEBABE1234567890ABCDEF").value(); + // test_data.emplace_back(sf, blob); + + // AccountID account(0xFFFFFFFFFFFFFFFF); + // test_data.emplace_back(sf, account); + + // STAmount amount(999999); + // test_data.emplace_back(sf, amount); + + // // Test round trip for each + // for (auto const& original : test_data) + // { + // Serializer s; + // original.add(s); + + // SerialIter sit(s.slice()); + // STData deserialized(sit, sf); + + // BEAST_EXPECT(original.isEquivalent(deserialized)); + // BEAST_EXPECT(original.getInnerTypeString() == + // deserialized.getInnerTypeString()); + // } + // } + + // void + // testMakeFieldPresent() + // { + // testcase("Make Field Present"); + + // auto const& sf = sfParameterValue; + + // // Test makeFieldPresent functionality + // { + // STData data(sf); + // STBase* field = data.makeFieldPresent(); + // BEAST_EXPECT(field != nullptr); + + // // Field should now be present (not STI_NOTPRESENT) + // BEAST_EXPECT(field->getSType() != STI_NOTPRESENT); + // } + // } + + void + run() override + { + testConstructors(); + testSerializationDeserialization(); + testSettersAndGetters(); + testJsonConversion(); + testDataFromJson(); + // testErrorCases(); + // testEquivalence(); + // testSize(); + // testTextRepresentation(); + // testCopyAndMove(); + // testBoundaryValues(); + // testSpecialSerializationCases(); + // testTypeConsistency(); + // testComplexAmountTypes(); + // testJsonRoundTrip(); + // testSerializationRoundTrip(); + // testMakeFieldPresent(); + } +}; + +BEAST_DEFINE_TESTSUITE(STData, protocol, xrpl); + +} // namespace xrpl diff --git a/src/test/protocol/STJson_test.cpp b/src/test/protocol/STJson_test.cpp new file mode 100644 index 00000000000..f90c5b4e81e --- /dev/null +++ b/src/test/protocol/STJson_test.cpp @@ -0,0 +1,795 @@ +#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 { + +struct STJson_test : public beast::unit_test::suite +{ + void + testDefaultConstructor() + { + testcase("Default constructor"); + STJson const json; + BEAST_EXPECT(json.isObject()); + BEAST_EXPECT(!json.isArray()); + BEAST_EXPECT(json.getMap().empty()); + } + + void + testSetAndGet() + { + testcase("setObjectField() and getObjectField()"); + STJson json; + auto value = std::make_shared(sfLedgerIndex, 12345); + json.setObjectField("foo", value); + + auto retrieved = json.getObjectField("foo"); + BEAST_EXPECT(retrieved.has_value()); + BEAST_EXPECT((*retrieved)->getSType() == STI_UINT32); + BEAST_EXPECT(std::dynamic_pointer_cast(*retrieved)->value() == 12345); + + // Test non-existent key + auto missing = json.getObjectField("bar"); + BEAST_EXPECT(!missing.has_value()); + } + + void + testMoveConstructor() + { + testcase("Move constructor (Object)"); + STJson::Map map; + map["bar"] = std::make_shared(sfTransactionType, 42); + STJson const json(std::move(map)); + BEAST_EXPECT(json.isObject()); + BEAST_EXPECT(json.getMap().size() == 1); + BEAST_EXPECT(std::dynamic_pointer_cast(json.getMap().at("bar"))->value() == 42); + } + + void + testArrayConstruction() + { + testcase("Array constructor"); + STJson::Array arr; + arr.push_back(std::make_shared(sfNetworkID, 100)); + arr.push_back(std::make_shared(sfNetworkID, 200)); + + STJson const json(std::move(arr)); + BEAST_EXPECT(json.isArray()); + BEAST_EXPECT(!json.isObject()); + BEAST_EXPECT(json.arraySize() == 2); + + auto elem0 = json.getArrayElement(0); + BEAST_EXPECT(elem0.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*elem0)->value() == 100); + } + + void + testTypeChecking() + { + testcase("Type checking methods"); + STJson const objJson; + BEAST_EXPECT(objJson.isObject()); + BEAST_EXPECT(!objJson.isArray()); + BEAST_EXPECT(objJson.getType() == STJson::JsonType::Object); + + STJson const arrJson(STJson::Array{}); + BEAST_EXPECT(arrJson.isArray()); + BEAST_EXPECT(!arrJson.isObject()); + BEAST_EXPECT(arrJson.getType() == STJson::JsonType::Array); + } + + void + testArrayOperations() + { + testcase("Array operations"); + STJson json(STJson::Array{}); + + // Test push + json.pushArrayElement(std::make_shared(sfCloseResolution, 10)); + json.pushArrayElement(std::make_shared(sfCloseResolution, 20)); + json.pushArrayElement(std::make_shared(sfCloseResolution, 30)); + + BEAST_EXPECT(json.arraySize() == 3); + + // Test get + auto elem1 = json.getArrayElement(1); + BEAST_EXPECT(elem1.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*elem1)->value() == 20); + + // Test set (replace) + json.setArrayElement(1, std::make_shared(sfCloseResolution, 25)); + auto elem1Updated = json.getArrayElement(1); + BEAST_EXPECT(std::dynamic_pointer_cast(*elem1Updated)->value() == 25); + + // Test out of bounds + auto missing = json.getArrayElement(10); + BEAST_EXPECT(!missing.has_value()); + } + + void + testArrayAutoResize() + { + testcase("Array auto-resize"); + STJson json(STJson::Array{}); + + // Set element at index 5 (should auto-resize with nulls) + json.setArrayElement(5, std::make_shared(sfNetworkID, 999)); + + BEAST_EXPECT(json.arraySize() == 6); + + // Check nulls were added + for (size_t i = 0; i < 5; ++i) + { + auto elem = json.getArrayElement(i); + BEAST_EXPECT(elem.has_value()); + BEAST_EXPECT(*elem == nullptr); + } + + // Check value at index 5 + auto elem5 = json.getArrayElement(5); + BEAST_EXPECT(elem5.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*elem5)->value() == 999); + } + + void + testArrayElementFields() + { + testcase("Array element field operations"); + STJson json(STJson::Array{}); + + // Set field in array element (auto-creates object) + json.setArrayElementField(0, "name", std::make_shared(sfNetworkID, 42)); + json.setArrayElementField(0, "value", std::make_shared(sfNetworkID, 100)); + + // Get fields + auto name = json.getArrayElementField(0, "name"); + BEAST_EXPECT(name.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*name)->value() == 42); + + auto value = json.getArrayElementField(0, "value"); + BEAST_EXPECT(value.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*value)->value() == 100); + + // Set field at higher index (auto-resize) + json.setArrayElementField(3, "test", std::make_shared(sfCloseResolution, 99)); + BEAST_EXPECT(json.arraySize() == 4); + + auto test = json.getArrayElementField(3, "test"); + BEAST_EXPECT(test.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*test)->value() == 99); + } + + void + testNestedObjectField() + { + testcase("Nested object field operations"); + STJson json; + + json.setNestedObjectField("user", "id", std::make_shared(sfNetworkID, 123)); + json.setNestedObjectField("user", "name", std::make_shared(sfNetworkID, 456)); + + auto id = json.getNestedObjectField("user", "id"); + BEAST_EXPECT(id.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*id)->value() == 123); + + auto name = json.getNestedObjectField("user", "name"); + BEAST_EXPECT(name.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*name)->value() == 456); + + // Test non-existent nested key + auto missing = json.getNestedObjectField("user", "age"); + BEAST_EXPECT(!missing.has_value()); + } + + void + testNestedArrayOperations() + { + testcase("Nested array operations"); + STJson json; + + // Set entire elements in nested array + json.setNestedArrayElement("items", 0, std::make_shared(sfNetworkID, 10)); + json.setNestedArrayElement("items", 1, std::make_shared(sfNetworkID, 20)); + json.setNestedArrayElement("items", 2, std::make_shared(sfNetworkID, 30)); + + // Get elements + auto item0 = json.getNestedArrayElement("items", 0); + BEAST_EXPECT(item0.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*item0)->value() == 10); + + auto item2 = json.getNestedArrayElement("items", 2); + BEAST_EXPECT(item2.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*item2)->value() == 30); + + // Auto-resize test + json.setNestedArrayElement("items", 5, std::make_shared(sfNetworkID, 60)); + auto item5 = json.getNestedArrayElement("items", 5); + BEAST_EXPECT(item5.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*item5)->value() == 60); + } + + void + testNestedArrayElementFields() + { + testcase("Nested array element field operations"); + STJson json; + + // Set fields in nested array elements + json.setNestedArrayElementField( + "users", 0, "id", std::make_shared(sfNetworkID, 100)); + json.setNestedArrayElementField( + "users", 0, "name", std::make_shared(sfNetworkID, 200)); + json.setNestedArrayElementField( + "users", 1, "id", std::make_shared(sfNetworkID, 101)); + json.setNestedArrayElementField( + "users", 1, "name", std::make_shared(sfNetworkID, 201)); + + // Get fields + auto user0id = json.getNestedArrayElementField("users", 0, "id"); + BEAST_EXPECT(user0id.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*user0id)->value() == 100); + + auto user1name = json.getNestedArrayElementField("users", 1, "name"); + BEAST_EXPECT(user1name.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*user1name)->value() == 201); + + // Test missing field + auto missing = json.getNestedArrayElementField("users", 0, "age"); + BEAST_EXPECT(!missing.has_value()); + } + + void + testDepthValidation() + { + testcase("Depth validation (max 1 level)"); + + // Valid: Object with nested object (depth 1) + { + STJson json; + auto nested = std::make_shared(); + nested->setObjectField("x", std::make_shared(sfNetworkID, 42)); + + try + { + json.setObjectField("nested", nested); + pass(); + } + catch (...) + { + fail("Should allow depth 1 nesting"); + } + } + + // Invalid: Object with nested object containing nested object (depth 2) + { + STJson json; + auto nested1 = std::make_shared(); + auto nested2 = std::make_shared(); + nested2->setObjectField("x", std::make_shared(sfNetworkID, 42)); + nested1->setObjectField("nested", nested2); + + try + { + json.setObjectField("nested", nested1); + fail("Should reject depth 2 nesting"); + } + catch (std::runtime_error const& e) + { + pass(); + } + } + + // Valid: Array with object elements (depth 1) + { + STJson json(STJson::Array{}); + auto elem = std::make_shared(); + elem->setObjectField("x", std::make_shared(sfNetworkID, 42)); + + try + { + json.pushArrayElement(elem); + pass(); + } + catch (...) + { + fail("Should allow depth 1 in array"); + } + } + + // Invalid: Array with nested arrays (depth 2) + { + STJson json(STJson::Array{}); + auto innerArray = std::make_shared(STJson::Array{}); + innerArray->pushArrayElement(std::make_shared(sfNetworkID, 42)); + + try + { + json.pushArrayElement(innerArray); + fail("Should reject array of arrays"); + } + catch (std::runtime_error const& e) + { + pass(); + } + } + + // Valid: Object with nested array (depth 1) + { + STJson json; + auto arr = std::make_shared(STJson::Array{}); + arr->pushArrayElement(std::make_shared(sfNetworkID, 42)); + + try + { + json.setObjectField("arr", arr); + pass(); + } + catch (...) + { + fail("Should allow object with array"); + } + } + + // Test depth validation in setNestedObjectField + { + STJson json; + auto nested = std::make_shared(); + nested->setObjectField("x", std::make_shared(sfNetworkID, 42)); + + try + { + json.setNestedObjectField("outer", "inner", nested); + fail("Should reject depth 2 via setNestedObjectField"); + } + catch (std::runtime_error const& e) + { + pass(); + } + } + } + + void + testAddAndFromBlob() + { + testcase("add() and fromBlob() for objects"); + STJson json; + json.setObjectField("a", std::make_shared(sfCloseResolution, 7)); + json.setObjectField("b", std::make_shared(sfNetworkID, 123456)); + + Serializer s; + json.add(s); + + auto blob = s.peekData(); + auto parsed = STJson::fromBlob(blob.data(), blob.size()); + BEAST_EXPECT(parsed->isObject()); + BEAST_EXPECT(parsed->getMap().size() == 2); + + auto a = parsed->getObjectField("a"); + BEAST_EXPECT(a.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*a)->value() == 7); + + auto b = parsed->getObjectField("b"); + BEAST_EXPECT(b.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*b)->value() == 123456); + } + + void + testArraySerialization() + { + testcase("Array serialization and deserialization"); + STJson json(STJson::Array{}); + json.pushArrayElement(std::make_shared(sfCloseResolution, 10)); + json.pushArrayElement(std::make_shared(sfNetworkID, 20)); + json.pushArrayElement(std::make_shared(sfIndexNext, 30)); + + Serializer s; + json.add(s); + + auto blob = s.peekData(); + auto parsed = STJson::fromBlob(blob.data(), blob.size()); + + BEAST_EXPECT(parsed->isArray()); + BEAST_EXPECT(parsed->arraySize() == 3); + + auto elem0 = parsed->getArrayElement(0); + BEAST_EXPECT(std::dynamic_pointer_cast(*elem0)->value() == 10); + + auto elem1 = parsed->getArrayElement(1); + BEAST_EXPECT(std::dynamic_pointer_cast(*elem1)->value() == 20); + + auto elem2 = parsed->getArrayElement(2); + BEAST_EXPECT(std::dynamic_pointer_cast(*elem2)->value() == 30); + } + + void + testFromSerialIter() + { + testcase("fromSerialIter()"); + STJson json; + json.setObjectField("x", std::make_shared(sfCloseResolution, 99)); + Serializer s; + json.add(s); + + SerialIter sit(s.peekData().data(), s.peekData().size()); + auto parsed = STJson::fromSerialIter(sit); + BEAST_EXPECT(parsed->isObject()); + BEAST_EXPECT(parsed->getMap().size() == 1); + + auto x = parsed->getObjectField("x"); + BEAST_EXPECT(x.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*x)->value() == 99); + } + + void + testFromSField() + { + testcase("Constructor from SField"); + STJson json; + json.setObjectField("x", std::make_shared(sfCloseResolution, 99)); + Serializer s; + json.add(s); + + SerialIter sit(s.peekData().data(), s.peekData().size()); + auto parsed = STJson{sit, sfContractCode}; + BEAST_EXPECT(parsed.isObject()); + BEAST_EXPECT(parsed.getMap().size() == 1); + + auto x = parsed.getObjectField("x"); + BEAST_EXPECT(x.has_value()); + BEAST_EXPECT(std::dynamic_pointer_cast(*x)->value() == 99); + } + + void + testGetJson() + { + testcase("getJson() for objects"); + STJson json; + json.setObjectField("foo", std::make_shared(sfTransactionType, 65535)); + json.setObjectField("bar", nullptr); // test null value + json.setNestedObjectField("meta", "version", std::make_shared(sfNetworkID, 2)); + + Json::Value jv = json.getJson(JsonOptions::none); + BEAST_EXPECT(jv.isObject()); + BEAST_EXPECT(jv[strHex(std::string{"foo"})].asUInt() == 65535); + BEAST_EXPECT(jv[strHex(std::string{"bar"})].isNull()); + BEAST_EXPECT(jv[strHex(std::string{"meta"})][strHex(std::string{"version"})].asUInt() == 2); + } + + void + testGetJsonArray() + { + testcase("getJson() for arrays"); + STJson json(STJson::Array{}); + json.pushArrayElement(std::make_shared(sfNetworkID, 100)); + json.pushArrayElement(std::make_shared(sfNetworkID, 200)); + json.pushArrayElement(nullptr); // null element + + Json::Value jv = json.getJson(JsonOptions::none); + BEAST_EXPECT(jv.isArray()); + BEAST_EXPECT(jv.size() == 3); + BEAST_EXPECT(jv[Json::UInt(0)].asUInt() == 100); + BEAST_EXPECT(jv[Json::UInt(1)].asUInt() == 200); + BEAST_EXPECT(jv[Json::UInt(2)].isNull()); + } + + void + testMakeValueFromVLWithType() + { + testcase("makeValueFromVLWithType()"); + Serializer s; + s.add8(STI_UINT32); + s.add32(0xDEADBEEF); + SerialIter sit(s.peekData().data(), s.peekData().size()); + auto value = STJson::makeValueFromVLWithType(sit); + BEAST_EXPECT(value->getSType() == STI_UINT32); + BEAST_EXPECT(std::dynamic_pointer_cast(value)->value() == 0xDEADBEEF); + } + + void + testMixedStructures() + { + testcase("Mixed structures (objects with arrays)"); + STJson json; + + // Add simple fields + json.setObjectField("id", std::make_shared(sfNetworkID, 1)); + + // Add nested object + json.setNestedObjectField( + "metadata", "version", std::make_shared(sfNetworkID, 2)); + + // Add nested array with objects + json.setNestedArrayElementField( + "users", 0, "name", std::make_shared(sfNetworkID, 100)); + json.setNestedArrayElementField( + "users", 0, "age", std::make_shared(sfNetworkID, 25)); + json.setNestedArrayElementField( + "users", 1, "name", std::make_shared(sfNetworkID, 101)); + + // Serialize and deserialize + Serializer s; + json.add(s); + auto parsed = STJson::fromBlob(s.peekData().data(), s.peekData().size()); + + // Verify structure + BEAST_EXPECT(parsed->isObject()); + + auto id = parsed->getObjectField("id"); + BEAST_EXPECT(std::dynamic_pointer_cast(*id)->value() == 1); + + auto version = parsed->getNestedObjectField("metadata", "version"); + BEAST_EXPECT(std::dynamic_pointer_cast(*version)->value() == 2); + + auto user0name = parsed->getNestedArrayElementField("users", 0, "name"); + BEAST_EXPECT(std::dynamic_pointer_cast(*user0name)->value() == 100); + + auto user1name = parsed->getNestedArrayElementField("users", 1, "name"); + BEAST_EXPECT(std::dynamic_pointer_cast(*user1name)->value() == 101); + } + + void + testSTTypes() + { + testcase("All STypes roundtrip"); + + // STI_UINT8 + { + STJson json; + json.setObjectField("u8", std::make_shared(sfCloseResolution, 200)); + Serializer s; + json.add(s); + auto parsed = STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto u8 = parsed->getObjectField("u8"); + BEAST_EXPECT(std::dynamic_pointer_cast(*u8)->value() == 200); + } + + // STI_UINT16 + { + STJson json; + json.setObjectField("u16", std::make_shared(sfSignerWeight, 4242)); + Serializer s; + json.add(s); + auto parsed = STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto u16 = parsed->getObjectField("u16"); + BEAST_EXPECT(std::dynamic_pointer_cast(*u16)->value() == 4242); + } + + // STI_UINT32 + { + STJson json; + json.setObjectField("u32", std::make_shared(sfNetworkID, 0xABCDEF01)); + Serializer s; + json.add(s); + auto parsed = STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto u32 = parsed->getObjectField("u32"); + BEAST_EXPECT(std::dynamic_pointer_cast(*u32)->value() == 0xABCDEF01); + } + + // STI_UINT64 + { + STJson json; + json.setObjectField( + "u64", std::make_shared(sfIndexNext, 0x123456789ABCDEF0ull)); + Serializer s; + json.add(s); + auto parsed = STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto u64 = parsed->getObjectField("u64"); + BEAST_EXPECT( + std::dynamic_pointer_cast(*u64)->value() == 0x123456789ABCDEF0ull); + } + + // STI_UINT160 + { + STJson json; + uint160 val; + val.data()[0] = 0x01; + val.data()[19] = 0xFF; + json.setObjectField("u160", std::make_shared(sfTakerPaysCurrency, val)); + Serializer s; + json.add(s); + auto parsed = STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto u160 = parsed->getObjectField("u160"); + BEAST_EXPECT(std::dynamic_pointer_cast(*u160)->value() == val); + } + + // STI_UINT256 + { + STJson json; + uint256 val; + val.data()[0] = 0xAA; + val.data()[31] = 0xBB; + json.setObjectField("u256", std::make_shared(sfLedgerHash, val)); + Serializer s; + json.add(s); + auto parsed = STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto u256 = parsed->getObjectField("u256"); + BEAST_EXPECT(std::dynamic_pointer_cast(*u256)->value() == val); + } + + // STI_AMOUNT + { + STJson json; + // XRP amount + STAmount const xrp(sfAmount, static_cast(123456789u)); + json.setObjectField("amount", std::make_shared(xrp)); + Serializer s; + json.add(s); + auto parsed = STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto amount = parsed->getObjectField("amount"); + auto parsedAmt = std::dynamic_pointer_cast(*amount); + BEAST_EXPECT(parsedAmt->mantissa() == 123456789u); + BEAST_EXPECT(parsedAmt->issue() == xrp.issue()); + } + + // STI_VL (STBlob) + { + STJson json; + std::vector blobData = {0xDE, 0xAD, 0xBE, 0xEF}; + json.setObjectField( + "blob", std::make_shared(sfPublicKey, blobData.data(), blobData.size())); + Serializer s; + json.add(s); + auto parsed = STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto blob = parsed->getObjectField("blob"); + auto parsedBlob = std::dynamic_pointer_cast(*blob); + BEAST_EXPECT(parsedBlob->size() == blobData.size()); + BEAST_EXPECT(std::memcmp(parsedBlob->data(), blobData.data(), blobData.size()) == 0); + } + + // STI_ACCOUNT + { + STJson json; + // Use a known AccountID (20 bytes) + AccountID const acct = AccountID{}; + json.setObjectField("acct", std::make_shared(sfAccount, acct)); + Serializer s; + json.add(s); + auto parsed = STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto account = parsed->getObjectField("acct"); + auto parsedAcct = std::dynamic_pointer_cast(*account); + BEAST_EXPECT(parsedAcct->value() == acct); + } + + // STI_CURRENCY (STCurrency) + { + STJson json; + Currency cur; + cur.data()[0] = 0xAA; + cur.data()[19] = 0xBB; + json.setObjectField("currency", std::make_shared(sfGeneric, cur)); + Serializer s; + json.add(s); + auto parsed = STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto currency = parsed->getObjectField("currency"); + auto parsedCur = std::dynamic_pointer_cast(*currency); + BEAST_EXPECT(parsedCur->value() == cur); + } + + // STI_JSON (STJson) Nested JSON + { + STJson innerJson; + // XRP amount + STAmount const xrp(sfAmount, static_cast(123456789u)); + innerJson.setObjectField("amount", std::make_shared(xrp)); + + STJson json; + json.setObjectField("nested", std::make_shared(innerJson)); + Serializer s; + json.add(s); + + auto parsed = STJson::fromBlob(s.peekData().data(), s.peekData().size()); + auto nested = parsed->getObjectField("nested"); + auto parsedNested = std::dynamic_pointer_cast(*nested); + auto amount = parsedNested->getObjectField("amount"); + BEAST_EXPECT(std::dynamic_pointer_cast(*amount)->mantissa() == 123456789u); + } + } + + void + testEdgeCases() + { + testcase("Edge cases"); + + // Empty object + { + STJson const json; + Serializer s; + json.add(s); + auto parsed = STJson::fromBlob(s.peekData().data(), s.peekData().size()); + BEAST_EXPECT(parsed->isObject()); + BEAST_EXPECT(parsed->getMap().empty()); + } + + // Empty array + { + STJson const json(STJson::Array{}); + Serializer s; + json.add(s); + auto parsed = STJson::fromBlob(s.peekData().data(), s.peekData().size()); + BEAST_EXPECT(parsed->isArray()); + BEAST_EXPECT(parsed->arraySize() == 0); + } + + // Array with null elements + { + STJson json(STJson::Array{}); + json.pushArrayElement(nullptr); + json.pushArrayElement(std::make_shared(sfNetworkID, 42)); + json.pushArrayElement(nullptr); + + BEAST_EXPECT(json.arraySize() == 3); + + auto elem0 = json.getArrayElement(0); + BEAST_EXPECT(elem0.has_value()); + BEAST_EXPECT(*elem0 == nullptr); + + auto elem1 = json.getArrayElement(1); + BEAST_EXPECT(elem1.has_value()); + BEAST_EXPECT(*elem1 != nullptr); + } + + // Object with null value - getObjectField treats null as absent + { + STJson json; + json.setObjectField("null_field", nullptr); + + auto val = json.getObjectField("null_field"); + BEAST_EXPECT(!val.has_value()); + } + } + + void + run() override + { + testDefaultConstructor(); + testSetAndGet(); + testMoveConstructor(); + testArrayConstruction(); + testTypeChecking(); + testArrayOperations(); + testArrayAutoResize(); + testArrayElementFields(); + testNestedObjectField(); + testNestedArrayOperations(); + testNestedArrayElementFields(); + testDepthValidation(); + testAddAndFromBlob(); + testArraySerialization(); + testFromSerialIter(); + testFromSField(); + testGetJson(); + testGetJsonArray(); + testMakeValueFromVLWithType(); + testMixedStructures(); + testSTTypes(); + testEdgeCases(); + } +}; + +BEAST_DEFINE_TESTSUITE(STJson, protocol, xrpl); + +} // namespace xrpl diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index 090a599e7cc..bbae3351a0f 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -511,6 +511,28 @@ class Subscribe_test : public beast::unit_test::Suite if (jv.isMember(jss::reserve_inc) != isFlagLedger) return false; + if (env.closed()->rules().enabled(featureSmartEscrow)) + { + if (jv.isMember(jss::extension_compute) != isFlagLedger) + return false; + + if (jv.isMember(jss::extension_size) != isFlagLedger) + return false; + + if (jv.isMember(jss::gas_price) != isFlagLedger) + return false; + } + else + { + if (jv.isMember(jss::extension_compute)) + return false; + + if (jv.isMember(jss::extension_size)) + return false; + + if (jv.isMember(jss::gas_price)) + return false; + } return true; }; @@ -1558,7 +1580,8 @@ class Subscribe_test : public beast::unit_test::Suite testTransactionsAPIv1(); testTransactionsAPIv2(); testManifests(); - testValidations(all - xrpFees); + testValidations(all - featureXRPFees - featureSmartEscrow); + testValidations(all - featureSmartEscrow); testValidations(all); testSubErrors(true); testSubErrors(false); diff --git a/src/tests/libxrpl/protocol_autogen/TestHelpers.h b/src/tests/libxrpl/protocol_autogen/TestHelpers.h index a32ab5e20c5..88a3dcc07e5 100644 --- a/src/tests/libxrpl/protocol_autogen/TestHelpers.h +++ b/src/tests/libxrpl/protocol_autogen/TestHelpers.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -148,6 +149,13 @@ canonical_XCHAIN_BRIDGE() return XChainBridgeValue{xrpAccount(), xrpIssue(), xrpAccount(), xrpIssue()}; } +using JsonValue = std::decay_t; +inline JsonValue +canonical_JSON() +{ + return JsonValue{}; +} + // Untyped field canonical values inline STArray diff --git a/src/tests/libxrpl/protocol_autogen/ledger_entries/AccountRootTests.cpp b/src/tests/libxrpl/protocol_autogen/ledger_entries/AccountRootTests.cpp index e967b614b70..3191f29da41 100644 --- a/src/tests/libxrpl/protocol_autogen/ledger_entries/AccountRootTests.cpp +++ b/src/tests/libxrpl/protocol_autogen/ledger_entries/AccountRootTests.cpp @@ -43,6 +43,7 @@ TEST(AccountRootTests, BuilderSettersRoundTrip) auto const aMMIDValue = canonical_UINT256(); auto const vaultIDValue = canonical_UINT256(); auto const loanBrokerIDValue = canonical_UINT256(); + auto const contractIDValue = canonical_UINT256(); AccountRootBuilder builder{ accountValue, @@ -70,6 +71,7 @@ TEST(AccountRootTests, BuilderSettersRoundTrip) builder.setAMMID(aMMIDValue); builder.setVaultID(vaultIDValue); builder.setLoanBrokerID(loanBrokerIDValue); + builder.setContractID(contractIDValue); builder.setLedgerIndex(index); builder.setFlags(0x1u); @@ -252,6 +254,14 @@ TEST(AccountRootTests, BuilderSettersRoundTrip) EXPECT_TRUE(entry.hasLoanBrokerID()); } + { + auto const& expected = contractIDValue; + auto const actualOpt = entry.getContractID(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfContractID"); + EXPECT_TRUE(entry.hasContractID()); + } + EXPECT_TRUE(entry.hasLedgerIndex()); auto const ledgerIndex = entry.getLedgerIndex(); ASSERT_TRUE(ledgerIndex.has_value()); @@ -288,6 +298,7 @@ TEST(AccountRootTests, BuilderFromSleRoundTrip) auto const aMMIDValue = canonical_UINT256(); auto const vaultIDValue = canonical_UINT256(); auto const loanBrokerIDValue = canonical_UINT256(); + auto const contractIDValue = canonical_UINT256(); auto sle = std::make_shared(AccountRoot::entryType, index); @@ -314,6 +325,7 @@ TEST(AccountRootTests, BuilderFromSleRoundTrip) sle->at(sfAMMID) = aMMIDValue; sle->at(sfVaultID) = vaultIDValue; sle->at(sfLoanBrokerID) = loanBrokerIDValue; + sle->at(sfContractID) = contractIDValue; AccountRootBuilder builderFromSle{sle}; EXPECT_TRUE(builderFromSle.validate()); @@ -605,6 +617,19 @@ TEST(AccountRootTests, BuilderFromSleRoundTrip) expectEqualField(expected, *fromBuilderOpt, "sfLoanBrokerID"); } + { + auto const& expected = contractIDValue; + + auto const fromSleOpt = entryFromSle.getContractID(); + auto const fromBuilderOpt = entryFromBuilder.getContractID(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfContractID"); + expectEqualField(expected, *fromBuilderOpt, "sfContractID"); + } + EXPECT_EQ(entryFromSle.getKey(), index); EXPECT_EQ(entryFromBuilder.getKey(), index); } @@ -703,5 +728,7 @@ TEST(AccountRootTests, OptionalFieldsReturnNullopt) EXPECT_FALSE(entry.getVaultID().has_value()); EXPECT_FALSE(entry.hasLoanBrokerID()); EXPECT_FALSE(entry.getLoanBrokerID().has_value()); + EXPECT_FALSE(entry.hasContractID()); + EXPECT_FALSE(entry.getContractID().has_value()); } } diff --git a/src/tests/libxrpl/protocol_autogen/ledger_entries/ContractDataTests.cpp b/src/tests/libxrpl/protocol_autogen/ledger_entries/ContractDataTests.cpp new file mode 100644 index 00000000000..eda0003568a --- /dev/null +++ b/src/tests/libxrpl/protocol_autogen/ledger_entries/ContractDataTests.cpp @@ -0,0 +1,223 @@ +// Auto-generated unit tests for ledger entry ContractData + + +#include + +#include + +#include +#include +#include + +#include + +namespace xrpl::ledger_entries { + +// 1 & 4) Set fields via builder setters, build, then read them back via +// wrapper getters. After build(), validate() should succeed for both the +// builder's STObject and the wrapper's SLE. +TEST(ContractDataTests, BuilderSettersRoundTrip) +{ + uint256 const index{1u}; + + auto const previousTxnIDValue = canonical_UINT256(); + auto const previousTxnLgrSeqValue = canonical_UINT32(); + auto const ownerNodeValue = canonical_UINT64(); + auto const ownerValue = canonical_ACCOUNT(); + auto const contractAccountValue = canonical_ACCOUNT(); + auto const contractJsonValue = canonical_JSON(); + + ContractDataBuilder builder{ + previousTxnIDValue, + previousTxnLgrSeqValue, + ownerNodeValue, + ownerValue, + contractAccountValue, + contractJsonValue + }; + + + builder.setLedgerIndex(index); + builder.setFlags(0x1u); + + EXPECT_TRUE(builder.validate()); + + auto const entry = builder.build(index); + + EXPECT_TRUE(entry.validate()); + + { + auto const& expected = previousTxnIDValue; + auto const actual = entry.getPreviousTxnID(); + expectEqualField(expected, actual, "sfPreviousTxnID"); + } + + { + auto const& expected = previousTxnLgrSeqValue; + auto const actual = entry.getPreviousTxnLgrSeq(); + expectEqualField(expected, actual, "sfPreviousTxnLgrSeq"); + } + + { + auto const& expected = ownerNodeValue; + auto const actual = entry.getOwnerNode(); + expectEqualField(expected, actual, "sfOwnerNode"); + } + + { + auto const& expected = ownerValue; + auto const actual = entry.getOwner(); + expectEqualField(expected, actual, "sfOwner"); + } + + { + auto const& expected = contractAccountValue; + auto const actual = entry.getContractAccount(); + expectEqualField(expected, actual, "sfContractAccount"); + } + + { + auto const& expected = contractJsonValue; + auto const actual = entry.getContractJson(); + expectEqualField(expected, actual, "sfContractJson"); + } + + EXPECT_TRUE(entry.hasLedgerIndex()); + auto const ledgerIndex = entry.getLedgerIndex(); + ASSERT_TRUE(ledgerIndex.has_value()); + EXPECT_EQ(*ledgerIndex, index); + EXPECT_EQ(entry.getKey(), index); +} + +// 2 & 4) Start from an SLE, set fields directly on it, construct a builder +// from that SLE, build a new wrapper, and verify all fields (and validate()). +TEST(ContractDataTests, BuilderFromSleRoundTrip) +{ + uint256 const index{2u}; + + auto const previousTxnIDValue = canonical_UINT256(); + auto const previousTxnLgrSeqValue = canonical_UINT32(); + auto const ownerNodeValue = canonical_UINT64(); + auto const ownerValue = canonical_ACCOUNT(); + auto const contractAccountValue = canonical_ACCOUNT(); + auto const contractJsonValue = canonical_JSON(); + + auto sle = std::make_shared(ContractData::entryType, index); + + sle->at(sfPreviousTxnID) = previousTxnIDValue; + sle->at(sfPreviousTxnLgrSeq) = previousTxnLgrSeqValue; + sle->at(sfOwnerNode) = ownerNodeValue; + sle->at(sfOwner) = ownerValue; + sle->at(sfContractAccount) = contractAccountValue; + sle->at(sfContractJson) = contractJsonValue; + + ContractDataBuilder builderFromSle{sle}; + EXPECT_TRUE(builderFromSle.validate()); + + auto const entryFromBuilder = builderFromSle.build(index); + + ContractData entryFromSle{sle}; + EXPECT_TRUE(entryFromBuilder.validate()); + EXPECT_TRUE(entryFromSle.validate()); + + { + auto const& expected = previousTxnIDValue; + + auto const fromSle = entryFromSle.getPreviousTxnID(); + auto const fromBuilder = entryFromBuilder.getPreviousTxnID(); + + expectEqualField(expected, fromSle, "sfPreviousTxnID"); + expectEqualField(expected, fromBuilder, "sfPreviousTxnID"); + } + + { + auto const& expected = previousTxnLgrSeqValue; + + auto const fromSle = entryFromSle.getPreviousTxnLgrSeq(); + auto const fromBuilder = entryFromBuilder.getPreviousTxnLgrSeq(); + + expectEqualField(expected, fromSle, "sfPreviousTxnLgrSeq"); + expectEqualField(expected, fromBuilder, "sfPreviousTxnLgrSeq"); + } + + { + auto const& expected = ownerNodeValue; + + auto const fromSle = entryFromSle.getOwnerNode(); + auto const fromBuilder = entryFromBuilder.getOwnerNode(); + + expectEqualField(expected, fromSle, "sfOwnerNode"); + expectEqualField(expected, fromBuilder, "sfOwnerNode"); + } + + { + auto const& expected = ownerValue; + + auto const fromSle = entryFromSle.getOwner(); + auto const fromBuilder = entryFromBuilder.getOwner(); + + expectEqualField(expected, fromSle, "sfOwner"); + expectEqualField(expected, fromBuilder, "sfOwner"); + } + + { + auto const& expected = contractAccountValue; + + auto const fromSle = entryFromSle.getContractAccount(); + auto const fromBuilder = entryFromBuilder.getContractAccount(); + + expectEqualField(expected, fromSle, "sfContractAccount"); + expectEqualField(expected, fromBuilder, "sfContractAccount"); + } + + { + auto const& expected = contractJsonValue; + + auto const fromSle = entryFromSle.getContractJson(); + auto const fromBuilder = entryFromBuilder.getContractJson(); + + expectEqualField(expected, fromSle, "sfContractJson"); + expectEqualField(expected, fromBuilder, "sfContractJson"); + } + + EXPECT_EQ(entryFromSle.getKey(), index); + EXPECT_EQ(entryFromBuilder.getKey(), index); +} + +// 3) Verify wrapper throws when constructed from wrong ledger entry type. +TEST(ContractDataTests, WrapperThrowsOnWrongEntryType) +{ + uint256 const index{3u}; + + // Build a valid ledger entry of a different type + // Ticket requires: Account, OwnerNode, TicketSequence, PreviousTxnID, PreviousTxnLgrSeq + // Check requires: Account, Destination, SendMax, Sequence, OwnerNode, DestinationNode, PreviousTxnID, PreviousTxnLgrSeq + TicketBuilder wrongBuilder{ + canonical_ACCOUNT(), + canonical_UINT64(), + canonical_UINT32(), + canonical_UINT256(), + canonical_UINT32()}; + auto wrongEntry = wrongBuilder.build(index); + + EXPECT_THROW(ContractData{wrongEntry.getSle()}, std::runtime_error); +} + +// 4) Verify builder throws when constructed from wrong ledger entry type. +TEST(ContractDataTests, BuilderThrowsOnWrongEntryType) +{ + uint256 const index{4u}; + + // Build a valid ledger entry of a different type + TicketBuilder wrongBuilder{ + canonical_ACCOUNT(), + canonical_UINT64(), + canonical_UINT32(), + canonical_UINT256(), + canonical_UINT32()}; + auto wrongEntry = wrongBuilder.build(index); + + EXPECT_THROW(ContractDataBuilder{wrongEntry.getSle()}, std::runtime_error); +} + +} diff --git a/src/tests/libxrpl/protocol_autogen/ledger_entries/ContractSourceTests.cpp b/src/tests/libxrpl/protocol_autogen/ledger_entries/ContractSourceTests.cpp new file mode 100644 index 00000000000..52e83a7fa54 --- /dev/null +++ b/src/tests/libxrpl/protocol_autogen/ledger_entries/ContractSourceTests.cpp @@ -0,0 +1,275 @@ +// Auto-generated unit tests for ledger entry ContractSource + + +#include + +#include + +#include +#include +#include + +#include + +namespace xrpl::ledger_entries { + +// 1 & 4) Set fields via builder setters, build, then read them back via +// wrapper getters. After build(), validate() should succeed for both the +// builder's STObject and the wrapper's SLE. +TEST(ContractSourceTests, BuilderSettersRoundTrip) +{ + uint256 const index{1u}; + + auto const previousTxnIDValue = canonical_UINT256(); + auto const previousTxnLgrSeqValue = canonical_UINT32(); + auto const contractHashValue = canonical_UINT256(); + auto const contractCodeValue = canonical_VL(); + auto const functionsValue = canonical_ARRAY(); + auto const instanceParametersValue = canonical_ARRAY(); + auto const referenceCountValue = canonical_UINT64(); + + ContractSourceBuilder builder{ + previousTxnIDValue, + previousTxnLgrSeqValue, + contractHashValue, + contractCodeValue, + functionsValue, + referenceCountValue + }; + + builder.setInstanceParameters(instanceParametersValue); + + builder.setLedgerIndex(index); + builder.setFlags(0x1u); + + EXPECT_TRUE(builder.validate()); + + auto const entry = builder.build(index); + + EXPECT_TRUE(entry.validate()); + + { + auto const& expected = previousTxnIDValue; + auto const actual = entry.getPreviousTxnID(); + expectEqualField(expected, actual, "sfPreviousTxnID"); + } + + { + auto const& expected = previousTxnLgrSeqValue; + auto const actual = entry.getPreviousTxnLgrSeq(); + expectEqualField(expected, actual, "sfPreviousTxnLgrSeq"); + } + + { + auto const& expected = contractHashValue; + auto const actual = entry.getContractHash(); + expectEqualField(expected, actual, "sfContractHash"); + } + + { + auto const& expected = contractCodeValue; + auto const actual = entry.getContractCode(); + expectEqualField(expected, actual, "sfContractCode"); + } + + { + auto const& expected = functionsValue; + auto const actual = entry.getFunctions(); + expectEqualField(expected, actual, "sfFunctions"); + } + + { + auto const& expected = referenceCountValue; + auto const actual = entry.getReferenceCount(); + expectEqualField(expected, actual, "sfReferenceCount"); + } + + { + auto const& expected = instanceParametersValue; + auto const actualOpt = entry.getInstanceParameters(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfInstanceParameters"); + EXPECT_TRUE(entry.hasInstanceParameters()); + } + + EXPECT_TRUE(entry.hasLedgerIndex()); + auto const ledgerIndex = entry.getLedgerIndex(); + ASSERT_TRUE(ledgerIndex.has_value()); + EXPECT_EQ(*ledgerIndex, index); + EXPECT_EQ(entry.getKey(), index); +} + +// 2 & 4) Start from an SLE, set fields directly on it, construct a builder +// from that SLE, build a new wrapper, and verify all fields (and validate()). +TEST(ContractSourceTests, BuilderFromSleRoundTrip) +{ + uint256 const index{2u}; + + auto const previousTxnIDValue = canonical_UINT256(); + auto const previousTxnLgrSeqValue = canonical_UINT32(); + auto const contractHashValue = canonical_UINT256(); + auto const contractCodeValue = canonical_VL(); + auto const functionsValue = canonical_ARRAY(); + auto const instanceParametersValue = canonical_ARRAY(); + auto const referenceCountValue = canonical_UINT64(); + + auto sle = std::make_shared(ContractSource::entryType, index); + + sle->at(sfPreviousTxnID) = previousTxnIDValue; + sle->at(sfPreviousTxnLgrSeq) = previousTxnLgrSeqValue; + sle->at(sfContractHash) = contractHashValue; + sle->at(sfContractCode) = contractCodeValue; + sle->setFieldArray(sfFunctions, functionsValue); + sle->setFieldArray(sfInstanceParameters, instanceParametersValue); + sle->at(sfReferenceCount) = referenceCountValue; + + ContractSourceBuilder builderFromSle{sle}; + EXPECT_TRUE(builderFromSle.validate()); + + auto const entryFromBuilder = builderFromSle.build(index); + + ContractSource entryFromSle{sle}; + EXPECT_TRUE(entryFromBuilder.validate()); + EXPECT_TRUE(entryFromSle.validate()); + + { + auto const& expected = previousTxnIDValue; + + auto const fromSle = entryFromSle.getPreviousTxnID(); + auto const fromBuilder = entryFromBuilder.getPreviousTxnID(); + + expectEqualField(expected, fromSle, "sfPreviousTxnID"); + expectEqualField(expected, fromBuilder, "sfPreviousTxnID"); + } + + { + auto const& expected = previousTxnLgrSeqValue; + + auto const fromSle = entryFromSle.getPreviousTxnLgrSeq(); + auto const fromBuilder = entryFromBuilder.getPreviousTxnLgrSeq(); + + expectEqualField(expected, fromSle, "sfPreviousTxnLgrSeq"); + expectEqualField(expected, fromBuilder, "sfPreviousTxnLgrSeq"); + } + + { + auto const& expected = contractHashValue; + + auto const fromSle = entryFromSle.getContractHash(); + auto const fromBuilder = entryFromBuilder.getContractHash(); + + expectEqualField(expected, fromSle, "sfContractHash"); + expectEqualField(expected, fromBuilder, "sfContractHash"); + } + + { + auto const& expected = contractCodeValue; + + auto const fromSle = entryFromSle.getContractCode(); + auto const fromBuilder = entryFromBuilder.getContractCode(); + + expectEqualField(expected, fromSle, "sfContractCode"); + expectEqualField(expected, fromBuilder, "sfContractCode"); + } + + { + auto const& expected = functionsValue; + + auto const fromSle = entryFromSle.getFunctions(); + auto const fromBuilder = entryFromBuilder.getFunctions(); + + expectEqualField(expected, fromSle, "sfFunctions"); + expectEqualField(expected, fromBuilder, "sfFunctions"); + } + + { + auto const& expected = referenceCountValue; + + auto const fromSle = entryFromSle.getReferenceCount(); + auto const fromBuilder = entryFromBuilder.getReferenceCount(); + + expectEqualField(expected, fromSle, "sfReferenceCount"); + expectEqualField(expected, fromBuilder, "sfReferenceCount"); + } + + { + auto const& expected = instanceParametersValue; + + auto const fromSleOpt = entryFromSle.getInstanceParameters(); + auto const fromBuilderOpt = entryFromBuilder.getInstanceParameters(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfInstanceParameters"); + expectEqualField(expected, *fromBuilderOpt, "sfInstanceParameters"); + } + + EXPECT_EQ(entryFromSle.getKey(), index); + EXPECT_EQ(entryFromBuilder.getKey(), index); +} + +// 3) Verify wrapper throws when constructed from wrong ledger entry type. +TEST(ContractSourceTests, WrapperThrowsOnWrongEntryType) +{ + uint256 const index{3u}; + + // Build a valid ledger entry of a different type + // Ticket requires: Account, OwnerNode, TicketSequence, PreviousTxnID, PreviousTxnLgrSeq + // Check requires: Account, Destination, SendMax, Sequence, OwnerNode, DestinationNode, PreviousTxnID, PreviousTxnLgrSeq + TicketBuilder wrongBuilder{ + canonical_ACCOUNT(), + canonical_UINT64(), + canonical_UINT32(), + canonical_UINT256(), + canonical_UINT32()}; + auto wrongEntry = wrongBuilder.build(index); + + EXPECT_THROW(ContractSource{wrongEntry.getSle()}, std::runtime_error); +} + +// 4) Verify builder throws when constructed from wrong ledger entry type. +TEST(ContractSourceTests, BuilderThrowsOnWrongEntryType) +{ + uint256 const index{4u}; + + // Build a valid ledger entry of a different type + TicketBuilder wrongBuilder{ + canonical_ACCOUNT(), + canonical_UINT64(), + canonical_UINT32(), + canonical_UINT256(), + canonical_UINT32()}; + auto wrongEntry = wrongBuilder.build(index); + + EXPECT_THROW(ContractSourceBuilder{wrongEntry.getSle()}, std::runtime_error); +} + +// 5) Build with only required fields and verify optional fields return nullopt. +TEST(ContractSourceTests, OptionalFieldsReturnNullopt) +{ + uint256 const index{3u}; + + auto const previousTxnIDValue = canonical_UINT256(); + auto const previousTxnLgrSeqValue = canonical_UINT32(); + auto const contractHashValue = canonical_UINT256(); + auto const contractCodeValue = canonical_VL(); + auto const functionsValue = canonical_ARRAY(); + auto const referenceCountValue = canonical_UINT64(); + + ContractSourceBuilder builder{ + previousTxnIDValue, + previousTxnLgrSeqValue, + contractHashValue, + contractCodeValue, + functionsValue, + referenceCountValue + }; + + auto const entry = builder.build(index); + + // Verify optional fields are not present + EXPECT_FALSE(entry.hasInstanceParameters()); + EXPECT_FALSE(entry.getInstanceParameters().has_value()); +} +} diff --git a/src/tests/libxrpl/protocol_autogen/ledger_entries/ContractTests.cpp b/src/tests/libxrpl/protocol_autogen/ledger_entries/ContractTests.cpp new file mode 100644 index 00000000000..a25cb558253 --- /dev/null +++ b/src/tests/libxrpl/protocol_autogen/ledger_entries/ContractTests.cpp @@ -0,0 +1,324 @@ +// Auto-generated unit tests for ledger entry Contract + + +#include + +#include + +#include +#include +#include + +#include + +namespace xrpl::ledger_entries { + +// 1 & 4) Set fields via builder setters, build, then read them back via +// wrapper getters. After build(), validate() should succeed for both the +// builder's STObject and the wrapper's SLE. +TEST(ContractTests, BuilderSettersRoundTrip) +{ + uint256 const index{1u}; + + auto const previousTxnIDValue = canonical_UINT256(); + auto const previousTxnLgrSeqValue = canonical_UINT32(); + auto const sequenceValue = canonical_UINT32(); + auto const ownerNodeValue = canonical_UINT64(); + auto const ownerValue = canonical_ACCOUNT(); + auto const contractAccountValue = canonical_ACCOUNT(); + auto const contractHashValue = canonical_UINT256(); + auto const instanceParameterValuesValue = canonical_ARRAY(); + auto const uRIValue = canonical_VL(); + + ContractBuilder builder{ + previousTxnIDValue, + previousTxnLgrSeqValue, + sequenceValue, + ownerNodeValue, + ownerValue, + contractAccountValue, + contractHashValue + }; + + builder.setInstanceParameterValues(instanceParameterValuesValue); + builder.setURI(uRIValue); + + builder.setLedgerIndex(index); + builder.setFlags(0x1u); + + EXPECT_TRUE(builder.validate()); + + auto const entry = builder.build(index); + + EXPECT_TRUE(entry.validate()); + + { + auto const& expected = previousTxnIDValue; + auto const actual = entry.getPreviousTxnID(); + expectEqualField(expected, actual, "sfPreviousTxnID"); + } + + { + auto const& expected = previousTxnLgrSeqValue; + auto const actual = entry.getPreviousTxnLgrSeq(); + expectEqualField(expected, actual, "sfPreviousTxnLgrSeq"); + } + + { + auto const& expected = sequenceValue; + auto const actual = entry.getSequence(); + expectEqualField(expected, actual, "sfSequence"); + } + + { + auto const& expected = ownerNodeValue; + auto const actual = entry.getOwnerNode(); + expectEqualField(expected, actual, "sfOwnerNode"); + } + + { + auto const& expected = ownerValue; + auto const actual = entry.getOwner(); + expectEqualField(expected, actual, "sfOwner"); + } + + { + auto const& expected = contractAccountValue; + auto const actual = entry.getContractAccount(); + expectEqualField(expected, actual, "sfContractAccount"); + } + + { + auto const& expected = contractHashValue; + auto const actual = entry.getContractHash(); + expectEqualField(expected, actual, "sfContractHash"); + } + + { + auto const& expected = instanceParameterValuesValue; + auto const actualOpt = entry.getInstanceParameterValues(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfInstanceParameterValues"); + EXPECT_TRUE(entry.hasInstanceParameterValues()); + } + + { + auto const& expected = uRIValue; + auto const actualOpt = entry.getURI(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfURI"); + EXPECT_TRUE(entry.hasURI()); + } + + EXPECT_TRUE(entry.hasLedgerIndex()); + auto const ledgerIndex = entry.getLedgerIndex(); + ASSERT_TRUE(ledgerIndex.has_value()); + EXPECT_EQ(*ledgerIndex, index); + EXPECT_EQ(entry.getKey(), index); +} + +// 2 & 4) Start from an SLE, set fields directly on it, construct a builder +// from that SLE, build a new wrapper, and verify all fields (and validate()). +TEST(ContractTests, BuilderFromSleRoundTrip) +{ + uint256 const index{2u}; + + auto const previousTxnIDValue = canonical_UINT256(); + auto const previousTxnLgrSeqValue = canonical_UINT32(); + auto const sequenceValue = canonical_UINT32(); + auto const ownerNodeValue = canonical_UINT64(); + auto const ownerValue = canonical_ACCOUNT(); + auto const contractAccountValue = canonical_ACCOUNT(); + auto const contractHashValue = canonical_UINT256(); + auto const instanceParameterValuesValue = canonical_ARRAY(); + auto const uRIValue = canonical_VL(); + + auto sle = std::make_shared(Contract::entryType, index); + + sle->at(sfPreviousTxnID) = previousTxnIDValue; + sle->at(sfPreviousTxnLgrSeq) = previousTxnLgrSeqValue; + sle->at(sfSequence) = sequenceValue; + sle->at(sfOwnerNode) = ownerNodeValue; + sle->at(sfOwner) = ownerValue; + sle->at(sfContractAccount) = contractAccountValue; + sle->at(sfContractHash) = contractHashValue; + sle->setFieldArray(sfInstanceParameterValues, instanceParameterValuesValue); + sle->at(sfURI) = uRIValue; + + ContractBuilder builderFromSle{sle}; + EXPECT_TRUE(builderFromSle.validate()); + + auto const entryFromBuilder = builderFromSle.build(index); + + Contract entryFromSle{sle}; + EXPECT_TRUE(entryFromBuilder.validate()); + EXPECT_TRUE(entryFromSle.validate()); + + { + auto const& expected = previousTxnIDValue; + + auto const fromSle = entryFromSle.getPreviousTxnID(); + auto const fromBuilder = entryFromBuilder.getPreviousTxnID(); + + expectEqualField(expected, fromSle, "sfPreviousTxnID"); + expectEqualField(expected, fromBuilder, "sfPreviousTxnID"); + } + + { + auto const& expected = previousTxnLgrSeqValue; + + auto const fromSle = entryFromSle.getPreviousTxnLgrSeq(); + auto const fromBuilder = entryFromBuilder.getPreviousTxnLgrSeq(); + + expectEqualField(expected, fromSle, "sfPreviousTxnLgrSeq"); + expectEqualField(expected, fromBuilder, "sfPreviousTxnLgrSeq"); + } + + { + auto const& expected = sequenceValue; + + auto const fromSle = entryFromSle.getSequence(); + auto const fromBuilder = entryFromBuilder.getSequence(); + + expectEqualField(expected, fromSle, "sfSequence"); + expectEqualField(expected, fromBuilder, "sfSequence"); + } + + { + auto const& expected = ownerNodeValue; + + auto const fromSle = entryFromSle.getOwnerNode(); + auto const fromBuilder = entryFromBuilder.getOwnerNode(); + + expectEqualField(expected, fromSle, "sfOwnerNode"); + expectEqualField(expected, fromBuilder, "sfOwnerNode"); + } + + { + auto const& expected = ownerValue; + + auto const fromSle = entryFromSle.getOwner(); + auto const fromBuilder = entryFromBuilder.getOwner(); + + expectEqualField(expected, fromSle, "sfOwner"); + expectEqualField(expected, fromBuilder, "sfOwner"); + } + + { + auto const& expected = contractAccountValue; + + auto const fromSle = entryFromSle.getContractAccount(); + auto const fromBuilder = entryFromBuilder.getContractAccount(); + + expectEqualField(expected, fromSle, "sfContractAccount"); + expectEqualField(expected, fromBuilder, "sfContractAccount"); + } + + { + auto const& expected = contractHashValue; + + auto const fromSle = entryFromSle.getContractHash(); + auto const fromBuilder = entryFromBuilder.getContractHash(); + + expectEqualField(expected, fromSle, "sfContractHash"); + expectEqualField(expected, fromBuilder, "sfContractHash"); + } + + { + auto const& expected = instanceParameterValuesValue; + + auto const fromSleOpt = entryFromSle.getInstanceParameterValues(); + auto const fromBuilderOpt = entryFromBuilder.getInstanceParameterValues(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfInstanceParameterValues"); + expectEqualField(expected, *fromBuilderOpt, "sfInstanceParameterValues"); + } + + { + auto const& expected = uRIValue; + + auto const fromSleOpt = entryFromSle.getURI(); + auto const fromBuilderOpt = entryFromBuilder.getURI(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfURI"); + expectEqualField(expected, *fromBuilderOpt, "sfURI"); + } + + EXPECT_EQ(entryFromSle.getKey(), index); + EXPECT_EQ(entryFromBuilder.getKey(), index); +} + +// 3) Verify wrapper throws when constructed from wrong ledger entry type. +TEST(ContractTests, WrapperThrowsOnWrongEntryType) +{ + uint256 const index{3u}; + + // Build a valid ledger entry of a different type + // Ticket requires: Account, OwnerNode, TicketSequence, PreviousTxnID, PreviousTxnLgrSeq + // Check requires: Account, Destination, SendMax, Sequence, OwnerNode, DestinationNode, PreviousTxnID, PreviousTxnLgrSeq + TicketBuilder wrongBuilder{ + canonical_ACCOUNT(), + canonical_UINT64(), + canonical_UINT32(), + canonical_UINT256(), + canonical_UINT32()}; + auto wrongEntry = wrongBuilder.build(index); + + EXPECT_THROW(Contract{wrongEntry.getSle()}, std::runtime_error); +} + +// 4) Verify builder throws when constructed from wrong ledger entry type. +TEST(ContractTests, BuilderThrowsOnWrongEntryType) +{ + uint256 const index{4u}; + + // Build a valid ledger entry of a different type + TicketBuilder wrongBuilder{ + canonical_ACCOUNT(), + canonical_UINT64(), + canonical_UINT32(), + canonical_UINT256(), + canonical_UINT32()}; + auto wrongEntry = wrongBuilder.build(index); + + EXPECT_THROW(ContractBuilder{wrongEntry.getSle()}, std::runtime_error); +} + +// 5) Build with only required fields and verify optional fields return nullopt. +TEST(ContractTests, OptionalFieldsReturnNullopt) +{ + uint256 const index{3u}; + + auto const previousTxnIDValue = canonical_UINT256(); + auto const previousTxnLgrSeqValue = canonical_UINT32(); + auto const sequenceValue = canonical_UINT32(); + auto const ownerNodeValue = canonical_UINT64(); + auto const ownerValue = canonical_ACCOUNT(); + auto const contractAccountValue = canonical_ACCOUNT(); + auto const contractHashValue = canonical_UINT256(); + + ContractBuilder builder{ + previousTxnIDValue, + previousTxnLgrSeqValue, + sequenceValue, + ownerNodeValue, + ownerValue, + contractAccountValue, + contractHashValue + }; + + auto const entry = builder.build(index); + + // Verify optional fields are not present + EXPECT_FALSE(entry.hasInstanceParameterValues()); + EXPECT_FALSE(entry.getInstanceParameterValues().has_value()); + EXPECT_FALSE(entry.hasURI()); + EXPECT_FALSE(entry.getURI().has_value()); +} +} diff --git a/src/tests/libxrpl/protocol_autogen/ledger_entries/EscrowTests.cpp b/src/tests/libxrpl/protocol_autogen/ledger_entries/EscrowTests.cpp index 2dbb450e28c..821c156e555 100644 --- a/src/tests/libxrpl/protocol_autogen/ledger_entries/EscrowTests.cpp +++ b/src/tests/libxrpl/protocol_autogen/ledger_entries/EscrowTests.cpp @@ -27,6 +27,8 @@ TEST(EscrowTests, BuilderSettersRoundTrip) auto const conditionValue = canonical_VL(); auto const cancelAfterValue = canonical_UINT32(); auto const finishAfterValue = canonical_UINT32(); + auto const finishFunctionValue = canonical_VL(); + auto const dataValue = canonical_VL(); auto const sourceTagValue = canonical_UINT32(); auto const destinationTagValue = canonical_UINT32(); auto const ownerNodeValue = canonical_UINT64(); @@ -49,6 +51,8 @@ TEST(EscrowTests, BuilderSettersRoundTrip) builder.setCondition(conditionValue); builder.setCancelAfter(cancelAfterValue); builder.setFinishAfter(finishAfterValue); + builder.setFinishFunction(finishFunctionValue); + builder.setData(dataValue); builder.setSourceTag(sourceTagValue); builder.setDestinationTag(destinationTagValue); builder.setDestinationNode(destinationNodeValue); @@ -132,6 +136,22 @@ TEST(EscrowTests, BuilderSettersRoundTrip) EXPECT_TRUE(entry.hasFinishAfter()); } + { + auto const& expected = finishFunctionValue; + auto const actualOpt = entry.getFinishFunction(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfFinishFunction"); + EXPECT_TRUE(entry.hasFinishFunction()); + } + + { + auto const& expected = dataValue; + auto const actualOpt = entry.getData(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfData"); + EXPECT_TRUE(entry.hasData()); + } + { auto const& expected = sourceTagValue; auto const actualOpt = entry.getSourceTag(); @@ -192,6 +212,8 @@ TEST(EscrowTests, BuilderFromSleRoundTrip) auto const conditionValue = canonical_VL(); auto const cancelAfterValue = canonical_UINT32(); auto const finishAfterValue = canonical_UINT32(); + auto const finishFunctionValue = canonical_VL(); + auto const dataValue = canonical_VL(); auto const sourceTagValue = canonical_UINT32(); auto const destinationTagValue = canonical_UINT32(); auto const ownerNodeValue = canonical_UINT64(); @@ -210,6 +232,8 @@ TEST(EscrowTests, BuilderFromSleRoundTrip) sle->at(sfCondition) = conditionValue; sle->at(sfCancelAfter) = cancelAfterValue; sle->at(sfFinishAfter) = finishAfterValue; + sle->at(sfFinishFunction) = finishFunctionValue; + sle->at(sfData) = dataValue; sle->at(sfSourceTag) = sourceTagValue; sle->at(sfDestinationTag) = destinationTagValue; sle->at(sfOwnerNode) = ownerNodeValue; @@ -340,6 +364,32 @@ TEST(EscrowTests, BuilderFromSleRoundTrip) expectEqualField(expected, *fromBuilderOpt, "sfFinishAfter"); } + { + auto const& expected = finishFunctionValue; + + auto const fromSleOpt = entryFromSle.getFinishFunction(); + auto const fromBuilderOpt = entryFromBuilder.getFinishFunction(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfFinishFunction"); + expectEqualField(expected, *fromBuilderOpt, "sfFinishFunction"); + } + + { + auto const& expected = dataValue; + + auto const fromSleOpt = entryFromSle.getData(); + auto const fromBuilderOpt = entryFromBuilder.getData(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfData"); + expectEqualField(expected, *fromBuilderOpt, "sfData"); + } + { auto const& expected = sourceTagValue; @@ -477,6 +527,10 @@ TEST(EscrowTests, OptionalFieldsReturnNullopt) EXPECT_FALSE(entry.getCancelAfter().has_value()); EXPECT_FALSE(entry.hasFinishAfter()); EXPECT_FALSE(entry.getFinishAfter().has_value()); + EXPECT_FALSE(entry.hasFinishFunction()); + EXPECT_FALSE(entry.getFinishFunction().has_value()); + EXPECT_FALSE(entry.hasData()); + EXPECT_FALSE(entry.getData().has_value()); EXPECT_FALSE(entry.hasSourceTag()); EXPECT_FALSE(entry.getSourceTag().has_value()); EXPECT_FALSE(entry.hasDestinationTag()); diff --git a/src/tests/libxrpl/protocol_autogen/ledger_entries/FeeSettingsTests.cpp b/src/tests/libxrpl/protocol_autogen/ledger_entries/FeeSettingsTests.cpp index 479d0c56c66..6d593bb1384 100644 --- a/src/tests/libxrpl/protocol_autogen/ledger_entries/FeeSettingsTests.cpp +++ b/src/tests/libxrpl/protocol_autogen/ledger_entries/FeeSettingsTests.cpp @@ -27,6 +27,9 @@ TEST(FeeSettingsTests, BuilderSettersRoundTrip) auto const baseFeeDropsValue = canonical_AMOUNT(); auto const reserveBaseDropsValue = canonical_AMOUNT(); auto const reserveIncrementDropsValue = canonical_AMOUNT(); + auto const extensionComputeLimitValue = canonical_UINT32(); + auto const extensionSizeLimitValue = canonical_UINT32(); + auto const gasPriceValue = canonical_UINT32(); auto const previousTxnIDValue = canonical_UINT256(); auto const previousTxnLgrSeqValue = canonical_UINT32(); @@ -40,6 +43,9 @@ TEST(FeeSettingsTests, BuilderSettersRoundTrip) builder.setBaseFeeDrops(baseFeeDropsValue); builder.setReserveBaseDrops(reserveBaseDropsValue); builder.setReserveIncrementDrops(reserveIncrementDropsValue); + builder.setExtensionComputeLimit(extensionComputeLimitValue); + builder.setExtensionSizeLimit(extensionSizeLimitValue); + builder.setGasPrice(gasPriceValue); builder.setPreviousTxnID(previousTxnIDValue); builder.setPreviousTxnLgrSeq(previousTxnLgrSeqValue); @@ -108,6 +114,30 @@ TEST(FeeSettingsTests, BuilderSettersRoundTrip) EXPECT_TRUE(entry.hasReserveIncrementDrops()); } + { + auto const& expected = extensionComputeLimitValue; + auto const actualOpt = entry.getExtensionComputeLimit(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfExtensionComputeLimit"); + EXPECT_TRUE(entry.hasExtensionComputeLimit()); + } + + { + auto const& expected = extensionSizeLimitValue; + auto const actualOpt = entry.getExtensionSizeLimit(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfExtensionSizeLimit"); + EXPECT_TRUE(entry.hasExtensionSizeLimit()); + } + + { + auto const& expected = gasPriceValue; + auto const actualOpt = entry.getGasPrice(); + ASSERT_TRUE(actualOpt.has_value()); + expectEqualField(expected, *actualOpt, "sfGasPrice"); + EXPECT_TRUE(entry.hasGasPrice()); + } + { auto const& expected = previousTxnIDValue; auto const actualOpt = entry.getPreviousTxnID(); @@ -144,6 +174,9 @@ TEST(FeeSettingsTests, BuilderFromSleRoundTrip) auto const baseFeeDropsValue = canonical_AMOUNT(); auto const reserveBaseDropsValue = canonical_AMOUNT(); auto const reserveIncrementDropsValue = canonical_AMOUNT(); + auto const extensionComputeLimitValue = canonical_UINT32(); + auto const extensionSizeLimitValue = canonical_UINT32(); + auto const gasPriceValue = canonical_UINT32(); auto const previousTxnIDValue = canonical_UINT256(); auto const previousTxnLgrSeqValue = canonical_UINT32(); @@ -156,6 +189,9 @@ TEST(FeeSettingsTests, BuilderFromSleRoundTrip) sle->at(sfBaseFeeDrops) = baseFeeDropsValue; sle->at(sfReserveBaseDrops) = reserveBaseDropsValue; sle->at(sfReserveIncrementDrops) = reserveIncrementDropsValue; + sle->at(sfExtensionComputeLimit) = extensionComputeLimitValue; + sle->at(sfExtensionSizeLimit) = extensionSizeLimitValue; + sle->at(sfGasPrice) = gasPriceValue; sle->at(sfPreviousTxnID) = previousTxnIDValue; sle->at(sfPreviousTxnLgrSeq) = previousTxnLgrSeqValue; @@ -259,6 +295,45 @@ TEST(FeeSettingsTests, BuilderFromSleRoundTrip) expectEqualField(expected, *fromBuilderOpt, "sfReserveIncrementDrops"); } + { + auto const& expected = extensionComputeLimitValue; + + auto const fromSleOpt = entryFromSle.getExtensionComputeLimit(); + auto const fromBuilderOpt = entryFromBuilder.getExtensionComputeLimit(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfExtensionComputeLimit"); + expectEqualField(expected, *fromBuilderOpt, "sfExtensionComputeLimit"); + } + + { + auto const& expected = extensionSizeLimitValue; + + auto const fromSleOpt = entryFromSle.getExtensionSizeLimit(); + auto const fromBuilderOpt = entryFromBuilder.getExtensionSizeLimit(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfExtensionSizeLimit"); + expectEqualField(expected, *fromBuilderOpt, "sfExtensionSizeLimit"); + } + + { + auto const& expected = gasPriceValue; + + auto const fromSleOpt = entryFromSle.getGasPrice(); + auto const fromBuilderOpt = entryFromBuilder.getGasPrice(); + + ASSERT_TRUE(fromSleOpt.has_value()); + ASSERT_TRUE(fromBuilderOpt.has_value()); + + expectEqualField(expected, *fromSleOpt, "sfGasPrice"); + expectEqualField(expected, *fromBuilderOpt, "sfGasPrice"); + } + { auto const& expected = previousTxnIDValue; @@ -351,6 +426,12 @@ TEST(FeeSettingsTests, OptionalFieldsReturnNullopt) EXPECT_FALSE(entry.getReserveBaseDrops().has_value()); EXPECT_FALSE(entry.hasReserveIncrementDrops()); EXPECT_FALSE(entry.getReserveIncrementDrops().has_value()); + EXPECT_FALSE(entry.hasExtensionComputeLimit()); + EXPECT_FALSE(entry.getExtensionComputeLimit().has_value()); + EXPECT_FALSE(entry.hasExtensionSizeLimit()); + EXPECT_FALSE(entry.getExtensionSizeLimit().has_value()); + EXPECT_FALSE(entry.hasGasPrice()); + EXPECT_FALSE(entry.getGasPrice().has_value()); EXPECT_FALSE(entry.hasPreviousTxnID()); EXPECT_FALSE(entry.getPreviousTxnID().has_value()); EXPECT_FALSE(entry.hasPreviousTxnLgrSeq()); diff --git a/src/tests/libxrpl/protocol_autogen/transactions/ContractCallTests.cpp b/src/tests/libxrpl/protocol_autogen/transactions/ContractCallTests.cpp new file mode 100644 index 00000000000..f467bc5ebaf --- /dev/null +++ b/src/tests/libxrpl/protocol_autogen/transactions/ContractCallTests.cpp @@ -0,0 +1,231 @@ +// Auto-generated unit tests for transaction ContractCall + + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +namespace xrpl::transactions { + +// 1 & 4) Set fields via builder setters, build, then read them back via +// wrapper getters. After build(), validate() should succeed. +TEST(TransactionsContractCallTests, BuilderSettersRoundTrip) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::secp256k1, generateSeed("testContractCall")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 1; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific field values + auto const contractAccountValue = canonical_ACCOUNT(); + auto const functionNameValue = canonical_VL(); + auto const parametersValue = canonical_ARRAY(); + auto const computationAllowanceValue = canonical_UINT32(); + + ContractCallBuilder builder{ + accountValue, + contractAccountValue, + functionNameValue, + computationAllowanceValue, + sequenceValue, + feeValue + }; + + // Set optional fields + builder.setParameters(parametersValue); + + auto tx = builder.build(publicKey, secretKey); + + std::string reason; + EXPECT_TRUE(tx.validate(reason)) << reason; + + // Verify signing was applied + EXPECT_FALSE(tx.getSigningPubKey().empty()); + EXPECT_TRUE(tx.hasTxnSignature()); + + // Verify common fields + EXPECT_EQ(tx.getAccount(), accountValue); + EXPECT_EQ(tx.getSequence(), sequenceValue); + EXPECT_EQ(tx.getFee(), feeValue); + + // Verify required fields + { + auto const& expected = contractAccountValue; + auto const actual = tx.getContractAccount(); + expectEqualField(expected, actual, "sfContractAccount"); + } + + { + auto const& expected = functionNameValue; + auto const actual = tx.getFunctionName(); + expectEqualField(expected, actual, "sfFunctionName"); + } + + { + auto const& expected = computationAllowanceValue; + auto const actual = tx.getComputationAllowance(); + expectEqualField(expected, actual, "sfComputationAllowance"); + } + + // Verify optional fields + { + auto const& expected = parametersValue; + auto const actualOpt = tx.getParameters(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfParameters should be present"; + expectEqualField(expected, *actualOpt, "sfParameters"); + EXPECT_TRUE(tx.hasParameters()); + } + +} + +// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper, +// and verify all fields match. +TEST(TransactionsContractCallTests, BuilderFromStTxRoundTrip) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::secp256k1, generateSeed("testContractCallFromTx")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 2; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific field values + auto const contractAccountValue = canonical_ACCOUNT(); + auto const functionNameValue = canonical_VL(); + auto const parametersValue = canonical_ARRAY(); + auto const computationAllowanceValue = canonical_UINT32(); + + // Build an initial transaction + ContractCallBuilder initialBuilder{ + accountValue, + contractAccountValue, + functionNameValue, + computationAllowanceValue, + sequenceValue, + feeValue + }; + + initialBuilder.setParameters(parametersValue); + + auto initialTx = initialBuilder.build(publicKey, secretKey); + + // Create builder from existing STTx + ContractCallBuilder builderFromTx{initialTx.getSTTx()}; + + auto rebuiltTx = builderFromTx.build(publicKey, secretKey); + + std::string reason; + EXPECT_TRUE(rebuiltTx.validate(reason)) << reason; + + // Verify common fields + EXPECT_EQ(rebuiltTx.getAccount(), accountValue); + EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue); + EXPECT_EQ(rebuiltTx.getFee(), feeValue); + + // Verify required fields + { + auto const& expected = contractAccountValue; + auto const actual = rebuiltTx.getContractAccount(); + expectEqualField(expected, actual, "sfContractAccount"); + } + + { + auto const& expected = functionNameValue; + auto const actual = rebuiltTx.getFunctionName(); + expectEqualField(expected, actual, "sfFunctionName"); + } + + { + auto const& expected = computationAllowanceValue; + auto const actual = rebuiltTx.getComputationAllowance(); + expectEqualField(expected, actual, "sfComputationAllowance"); + } + + // Verify optional fields + { + auto const& expected = parametersValue; + auto const actualOpt = rebuiltTx.getParameters(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfParameters should be present"; + expectEqualField(expected, *actualOpt, "sfParameters"); + } + +} + +// 3) Verify wrapper throws when constructed from wrong transaction type. +TEST(TransactionsContractCallTests, WrapperThrowsOnWrongTxType) +{ + // Build a valid transaction of a different type + auto const [pk, sk] = + generateKeyPair(KeyType::secp256k1, generateSeed("testWrongType")); + auto const account = calcAccountID(pk); + + AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()}; + auto wrongTx = wrongBuilder.build(pk, sk); + + EXPECT_THROW(ContractCall{wrongTx.getSTTx()}, std::runtime_error); +} + +// 4) Verify builder throws when constructed from wrong transaction type. +TEST(TransactionsContractCallTests, BuilderThrowsOnWrongTxType) +{ + // Build a valid transaction of a different type + auto const [pk, sk] = + generateKeyPair(KeyType::secp256k1, generateSeed("testWrongTypeBuilder")); + auto const account = calcAccountID(pk); + + AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()}; + auto wrongTx = wrongBuilder.build(pk, sk); + + EXPECT_THROW(ContractCallBuilder{wrongTx.getSTTx()}, std::runtime_error); +} + +// 5) Build with only required fields and verify optional fields return nullopt. +TEST(TransactionsContractCallTests, OptionalFieldsReturnNullopt) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::secp256k1, generateSeed("testContractCallNullopt")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 3; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific required field values + auto const contractAccountValue = canonical_ACCOUNT(); + auto const functionNameValue = canonical_VL(); + auto const computationAllowanceValue = canonical_UINT32(); + + ContractCallBuilder builder{ + accountValue, + contractAccountValue, + functionNameValue, + computationAllowanceValue, + sequenceValue, + feeValue + }; + + // Do NOT set optional fields + + auto tx = builder.build(publicKey, secretKey); + + // Verify optional fields are not present + EXPECT_FALSE(tx.hasParameters()); + EXPECT_FALSE(tx.getParameters().has_value()); +} + +} diff --git a/src/tests/libxrpl/protocol_autogen/transactions/ContractClawbackTests.cpp b/src/tests/libxrpl/protocol_autogen/transactions/ContractClawbackTests.cpp new file mode 100644 index 00000000000..0200657391f --- /dev/null +++ b/src/tests/libxrpl/protocol_autogen/transactions/ContractClawbackTests.cpp @@ -0,0 +1,195 @@ +// Auto-generated unit tests for transaction ContractClawback + + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +namespace xrpl::transactions { + +// 1 & 4) Set fields via builder setters, build, then read them back via +// wrapper getters. After build(), validate() should succeed. +TEST(TransactionsContractClawbackTests, BuilderSettersRoundTrip) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::secp256k1, generateSeed("testContractClawback")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 1; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific field values + auto const contractAccountValue = canonical_ACCOUNT(); + auto const amountValue = canonical_AMOUNT(); + + ContractClawbackBuilder builder{ + accountValue, + amountValue, + sequenceValue, + feeValue + }; + + // Set optional fields + builder.setContractAccount(contractAccountValue); + + auto tx = builder.build(publicKey, secretKey); + + std::string reason; + EXPECT_TRUE(tx.validate(reason)) << reason; + + // Verify signing was applied + EXPECT_FALSE(tx.getSigningPubKey().empty()); + EXPECT_TRUE(tx.hasTxnSignature()); + + // Verify common fields + EXPECT_EQ(tx.getAccount(), accountValue); + EXPECT_EQ(tx.getSequence(), sequenceValue); + EXPECT_EQ(tx.getFee(), feeValue); + + // Verify required fields + { + auto const& expected = amountValue; + auto const actual = tx.getAmount(); + expectEqualField(expected, actual, "sfAmount"); + } + + // Verify optional fields + { + auto const& expected = contractAccountValue; + auto const actualOpt = tx.getContractAccount(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfContractAccount should be present"; + expectEqualField(expected, *actualOpt, "sfContractAccount"); + EXPECT_TRUE(tx.hasContractAccount()); + } + +} + +// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper, +// and verify all fields match. +TEST(TransactionsContractClawbackTests, BuilderFromStTxRoundTrip) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::secp256k1, generateSeed("testContractClawbackFromTx")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 2; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific field values + auto const contractAccountValue = canonical_ACCOUNT(); + auto const amountValue = canonical_AMOUNT(); + + // Build an initial transaction + ContractClawbackBuilder initialBuilder{ + accountValue, + amountValue, + sequenceValue, + feeValue + }; + + initialBuilder.setContractAccount(contractAccountValue); + + auto initialTx = initialBuilder.build(publicKey, secretKey); + + // Create builder from existing STTx + ContractClawbackBuilder builderFromTx{initialTx.getSTTx()}; + + auto rebuiltTx = builderFromTx.build(publicKey, secretKey); + + std::string reason; + EXPECT_TRUE(rebuiltTx.validate(reason)) << reason; + + // Verify common fields + EXPECT_EQ(rebuiltTx.getAccount(), accountValue); + EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue); + EXPECT_EQ(rebuiltTx.getFee(), feeValue); + + // Verify required fields + { + auto const& expected = amountValue; + auto const actual = rebuiltTx.getAmount(); + expectEqualField(expected, actual, "sfAmount"); + } + + // Verify optional fields + { + auto const& expected = contractAccountValue; + auto const actualOpt = rebuiltTx.getContractAccount(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfContractAccount should be present"; + expectEqualField(expected, *actualOpt, "sfContractAccount"); + } + +} + +// 3) Verify wrapper throws when constructed from wrong transaction type. +TEST(TransactionsContractClawbackTests, WrapperThrowsOnWrongTxType) +{ + // Build a valid transaction of a different type + auto const [pk, sk] = + generateKeyPair(KeyType::secp256k1, generateSeed("testWrongType")); + auto const account = calcAccountID(pk); + + AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()}; + auto wrongTx = wrongBuilder.build(pk, sk); + + EXPECT_THROW(ContractClawback{wrongTx.getSTTx()}, std::runtime_error); +} + +// 4) Verify builder throws when constructed from wrong transaction type. +TEST(TransactionsContractClawbackTests, BuilderThrowsOnWrongTxType) +{ + // Build a valid transaction of a different type + auto const [pk, sk] = + generateKeyPair(KeyType::secp256k1, generateSeed("testWrongTypeBuilder")); + auto const account = calcAccountID(pk); + + AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()}; + auto wrongTx = wrongBuilder.build(pk, sk); + + EXPECT_THROW(ContractClawbackBuilder{wrongTx.getSTTx()}, std::runtime_error); +} + +// 5) Build with only required fields and verify optional fields return nullopt. +TEST(TransactionsContractClawbackTests, OptionalFieldsReturnNullopt) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::secp256k1, generateSeed("testContractClawbackNullopt")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 3; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific required field values + auto const amountValue = canonical_AMOUNT(); + + ContractClawbackBuilder builder{ + accountValue, + amountValue, + sequenceValue, + feeValue + }; + + // Do NOT set optional fields + + auto tx = builder.build(publicKey, secretKey); + + // Verify optional fields are not present + EXPECT_FALSE(tx.hasContractAccount()); + EXPECT_FALSE(tx.getContractAccount().has_value()); +} + +} diff --git a/src/tests/libxrpl/protocol_autogen/transactions/ContractCreateTests.cpp b/src/tests/libxrpl/protocol_autogen/transactions/ContractCreateTests.cpp new file mode 100644 index 00000000000..29dd15c6b7f --- /dev/null +++ b/src/tests/libxrpl/protocol_autogen/transactions/ContractCreateTests.cpp @@ -0,0 +1,282 @@ +// Auto-generated unit tests for transaction ContractCreate + + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +namespace xrpl::transactions { + +// 1 & 4) Set fields via builder setters, build, then read them back via +// wrapper getters. After build(), validate() should succeed. +TEST(TransactionsContractCreateTests, BuilderSettersRoundTrip) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::secp256k1, generateSeed("testContractCreate")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 1; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific field values + auto const contractCodeValue = canonical_VL(); + auto const contractHashValue = canonical_UINT256(); + auto const functionsValue = canonical_ARRAY(); + auto const instanceParametersValue = canonical_ARRAY(); + auto const instanceParameterValuesValue = canonical_ARRAY(); + auto const uRIValue = canonical_VL(); + + ContractCreateBuilder builder{ + accountValue, + sequenceValue, + feeValue + }; + + // Set optional fields + builder.setContractCode(contractCodeValue); + builder.setContractHash(contractHashValue); + builder.setFunctions(functionsValue); + builder.setInstanceParameters(instanceParametersValue); + builder.setInstanceParameterValues(instanceParameterValuesValue); + builder.setURI(uRIValue); + + auto tx = builder.build(publicKey, secretKey); + + std::string reason; + EXPECT_TRUE(tx.validate(reason)) << reason; + + // Verify signing was applied + EXPECT_FALSE(tx.getSigningPubKey().empty()); + EXPECT_TRUE(tx.hasTxnSignature()); + + // Verify common fields + EXPECT_EQ(tx.getAccount(), accountValue); + EXPECT_EQ(tx.getSequence(), sequenceValue); + EXPECT_EQ(tx.getFee(), feeValue); + + // Verify required fields + // Verify optional fields + { + auto const& expected = contractCodeValue; + auto const actualOpt = tx.getContractCode(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfContractCode should be present"; + expectEqualField(expected, *actualOpt, "sfContractCode"); + EXPECT_TRUE(tx.hasContractCode()); + } + + { + auto const& expected = contractHashValue; + auto const actualOpt = tx.getContractHash(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfContractHash should be present"; + expectEqualField(expected, *actualOpt, "sfContractHash"); + EXPECT_TRUE(tx.hasContractHash()); + } + + { + auto const& expected = functionsValue; + auto const actualOpt = tx.getFunctions(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfFunctions should be present"; + expectEqualField(expected, *actualOpt, "sfFunctions"); + EXPECT_TRUE(tx.hasFunctions()); + } + + { + auto const& expected = instanceParametersValue; + auto const actualOpt = tx.getInstanceParameters(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfInstanceParameters should be present"; + expectEqualField(expected, *actualOpt, "sfInstanceParameters"); + EXPECT_TRUE(tx.hasInstanceParameters()); + } + + { + auto const& expected = instanceParameterValuesValue; + auto const actualOpt = tx.getInstanceParameterValues(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfInstanceParameterValues should be present"; + expectEqualField(expected, *actualOpt, "sfInstanceParameterValues"); + EXPECT_TRUE(tx.hasInstanceParameterValues()); + } + + { + auto const& expected = uRIValue; + auto const actualOpt = tx.getURI(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfURI should be present"; + expectEqualField(expected, *actualOpt, "sfURI"); + EXPECT_TRUE(tx.hasURI()); + } + +} + +// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper, +// and verify all fields match. +TEST(TransactionsContractCreateTests, BuilderFromStTxRoundTrip) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::secp256k1, generateSeed("testContractCreateFromTx")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 2; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific field values + auto const contractCodeValue = canonical_VL(); + auto const contractHashValue = canonical_UINT256(); + auto const functionsValue = canonical_ARRAY(); + auto const instanceParametersValue = canonical_ARRAY(); + auto const instanceParameterValuesValue = canonical_ARRAY(); + auto const uRIValue = canonical_VL(); + + // Build an initial transaction + ContractCreateBuilder initialBuilder{ + accountValue, + sequenceValue, + feeValue + }; + + initialBuilder.setContractCode(contractCodeValue); + initialBuilder.setContractHash(contractHashValue); + initialBuilder.setFunctions(functionsValue); + initialBuilder.setInstanceParameters(instanceParametersValue); + initialBuilder.setInstanceParameterValues(instanceParameterValuesValue); + initialBuilder.setURI(uRIValue); + + auto initialTx = initialBuilder.build(publicKey, secretKey); + + // Create builder from existing STTx + ContractCreateBuilder builderFromTx{initialTx.getSTTx()}; + + auto rebuiltTx = builderFromTx.build(publicKey, secretKey); + + std::string reason; + EXPECT_TRUE(rebuiltTx.validate(reason)) << reason; + + // Verify common fields + EXPECT_EQ(rebuiltTx.getAccount(), accountValue); + EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue); + EXPECT_EQ(rebuiltTx.getFee(), feeValue); + + // Verify required fields + // Verify optional fields + { + auto const& expected = contractCodeValue; + auto const actualOpt = rebuiltTx.getContractCode(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfContractCode should be present"; + expectEqualField(expected, *actualOpt, "sfContractCode"); + } + + { + auto const& expected = contractHashValue; + auto const actualOpt = rebuiltTx.getContractHash(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfContractHash should be present"; + expectEqualField(expected, *actualOpt, "sfContractHash"); + } + + { + auto const& expected = functionsValue; + auto const actualOpt = rebuiltTx.getFunctions(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfFunctions should be present"; + expectEqualField(expected, *actualOpt, "sfFunctions"); + } + + { + auto const& expected = instanceParametersValue; + auto const actualOpt = rebuiltTx.getInstanceParameters(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfInstanceParameters should be present"; + expectEqualField(expected, *actualOpt, "sfInstanceParameters"); + } + + { + auto const& expected = instanceParameterValuesValue; + auto const actualOpt = rebuiltTx.getInstanceParameterValues(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfInstanceParameterValues should be present"; + expectEqualField(expected, *actualOpt, "sfInstanceParameterValues"); + } + + { + auto const& expected = uRIValue; + auto const actualOpt = rebuiltTx.getURI(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfURI should be present"; + expectEqualField(expected, *actualOpt, "sfURI"); + } + +} + +// 3) Verify wrapper throws when constructed from wrong transaction type. +TEST(TransactionsContractCreateTests, WrapperThrowsOnWrongTxType) +{ + // Build a valid transaction of a different type + auto const [pk, sk] = + generateKeyPair(KeyType::secp256k1, generateSeed("testWrongType")); + auto const account = calcAccountID(pk); + + AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()}; + auto wrongTx = wrongBuilder.build(pk, sk); + + EXPECT_THROW(ContractCreate{wrongTx.getSTTx()}, std::runtime_error); +} + +// 4) Verify builder throws when constructed from wrong transaction type. +TEST(TransactionsContractCreateTests, BuilderThrowsOnWrongTxType) +{ + // Build a valid transaction of a different type + auto const [pk, sk] = + generateKeyPair(KeyType::secp256k1, generateSeed("testWrongTypeBuilder")); + auto const account = calcAccountID(pk); + + AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()}; + auto wrongTx = wrongBuilder.build(pk, sk); + + EXPECT_THROW(ContractCreateBuilder{wrongTx.getSTTx()}, std::runtime_error); +} + +// 5) Build with only required fields and verify optional fields return nullopt. +TEST(TransactionsContractCreateTests, OptionalFieldsReturnNullopt) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::secp256k1, generateSeed("testContractCreateNullopt")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 3; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific required field values + + ContractCreateBuilder builder{ + accountValue, + sequenceValue, + feeValue + }; + + // Do NOT set optional fields + + auto tx = builder.build(publicKey, secretKey); + + // Verify optional fields are not present + EXPECT_FALSE(tx.hasContractCode()); + EXPECT_FALSE(tx.getContractCode().has_value()); + EXPECT_FALSE(tx.hasContractHash()); + EXPECT_FALSE(tx.getContractHash().has_value()); + EXPECT_FALSE(tx.hasFunctions()); + EXPECT_FALSE(tx.getFunctions().has_value()); + EXPECT_FALSE(tx.hasInstanceParameters()); + EXPECT_FALSE(tx.getInstanceParameters().has_value()); + EXPECT_FALSE(tx.hasInstanceParameterValues()); + EXPECT_FALSE(tx.getInstanceParameterValues().has_value()); + EXPECT_FALSE(tx.hasURI()); + EXPECT_FALSE(tx.getURI().has_value()); +} + +} diff --git a/src/tests/libxrpl/protocol_autogen/transactions/ContractDeleteTests.cpp b/src/tests/libxrpl/protocol_autogen/transactions/ContractDeleteTests.cpp new file mode 100644 index 00000000000..6140cf0ec82 --- /dev/null +++ b/src/tests/libxrpl/protocol_autogen/transactions/ContractDeleteTests.cpp @@ -0,0 +1,146 @@ +// Auto-generated unit tests for transaction ContractDelete + + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +namespace xrpl::transactions { + +// 1 & 4) Set fields via builder setters, build, then read them back via +// wrapper getters. After build(), validate() should succeed. +TEST(TransactionsContractDeleteTests, BuilderSettersRoundTrip) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::secp256k1, generateSeed("testContractDelete")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 1; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific field values + auto const contractAccountValue = canonical_ACCOUNT(); + + ContractDeleteBuilder builder{ + accountValue, + contractAccountValue, + sequenceValue, + feeValue + }; + + // Set optional fields + + auto tx = builder.build(publicKey, secretKey); + + std::string reason; + EXPECT_TRUE(tx.validate(reason)) << reason; + + // Verify signing was applied + EXPECT_FALSE(tx.getSigningPubKey().empty()); + EXPECT_TRUE(tx.hasTxnSignature()); + + // Verify common fields + EXPECT_EQ(tx.getAccount(), accountValue); + EXPECT_EQ(tx.getSequence(), sequenceValue); + EXPECT_EQ(tx.getFee(), feeValue); + + // Verify required fields + { + auto const& expected = contractAccountValue; + auto const actual = tx.getContractAccount(); + expectEqualField(expected, actual, "sfContractAccount"); + } + + // Verify optional fields +} + +// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper, +// and verify all fields match. +TEST(TransactionsContractDeleteTests, BuilderFromStTxRoundTrip) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::secp256k1, generateSeed("testContractDeleteFromTx")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 2; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific field values + auto const contractAccountValue = canonical_ACCOUNT(); + + // Build an initial transaction + ContractDeleteBuilder initialBuilder{ + accountValue, + contractAccountValue, + sequenceValue, + feeValue + }; + + + auto initialTx = initialBuilder.build(publicKey, secretKey); + + // Create builder from existing STTx + ContractDeleteBuilder builderFromTx{initialTx.getSTTx()}; + + auto rebuiltTx = builderFromTx.build(publicKey, secretKey); + + std::string reason; + EXPECT_TRUE(rebuiltTx.validate(reason)) << reason; + + // Verify common fields + EXPECT_EQ(rebuiltTx.getAccount(), accountValue); + EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue); + EXPECT_EQ(rebuiltTx.getFee(), feeValue); + + // Verify required fields + { + auto const& expected = contractAccountValue; + auto const actual = rebuiltTx.getContractAccount(); + expectEqualField(expected, actual, "sfContractAccount"); + } + + // Verify optional fields +} + +// 3) Verify wrapper throws when constructed from wrong transaction type. +TEST(TransactionsContractDeleteTests, WrapperThrowsOnWrongTxType) +{ + // Build a valid transaction of a different type + auto const [pk, sk] = + generateKeyPair(KeyType::secp256k1, generateSeed("testWrongType")); + auto const account = calcAccountID(pk); + + AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()}; + auto wrongTx = wrongBuilder.build(pk, sk); + + EXPECT_THROW(ContractDelete{wrongTx.getSTTx()}, std::runtime_error); +} + +// 4) Verify builder throws when constructed from wrong transaction type. +TEST(TransactionsContractDeleteTests, BuilderThrowsOnWrongTxType) +{ + // Build a valid transaction of a different type + auto const [pk, sk] = + generateKeyPair(KeyType::secp256k1, generateSeed("testWrongTypeBuilder")); + auto const account = calcAccountID(pk); + + AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()}; + auto wrongTx = wrongBuilder.build(pk, sk); + + EXPECT_THROW(ContractDeleteBuilder{wrongTx.getSTTx()}, std::runtime_error); +} + + +} diff --git a/src/tests/libxrpl/protocol_autogen/transactions/ContractModifyTests.cpp b/src/tests/libxrpl/protocol_autogen/transactions/ContractModifyTests.cpp new file mode 100644 index 00000000000..f527363e7e1 --- /dev/null +++ b/src/tests/libxrpl/protocol_autogen/transactions/ContractModifyTests.cpp @@ -0,0 +1,324 @@ +// Auto-generated unit tests for transaction ContractModify + + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +namespace xrpl::transactions { + +// 1 & 4) Set fields via builder setters, build, then read them back via +// wrapper getters. After build(), validate() should succeed. +TEST(TransactionsContractModifyTests, BuilderSettersRoundTrip) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::secp256k1, generateSeed("testContractModify")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 1; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific field values + auto const contractAccountValue = canonical_ACCOUNT(); + auto const ownerValue = canonical_ACCOUNT(); + auto const contractCodeValue = canonical_VL(); + auto const contractHashValue = canonical_UINT256(); + auto const functionsValue = canonical_ARRAY(); + auto const instanceParametersValue = canonical_ARRAY(); + auto const instanceParameterValuesValue = canonical_ARRAY(); + auto const uRIValue = canonical_VL(); + + ContractModifyBuilder builder{ + accountValue, + sequenceValue, + feeValue + }; + + // Set optional fields + builder.setContractAccount(contractAccountValue); + builder.setOwner(ownerValue); + builder.setContractCode(contractCodeValue); + builder.setContractHash(contractHashValue); + builder.setFunctions(functionsValue); + builder.setInstanceParameters(instanceParametersValue); + builder.setInstanceParameterValues(instanceParameterValuesValue); + builder.setURI(uRIValue); + + auto tx = builder.build(publicKey, secretKey); + + std::string reason; + EXPECT_TRUE(tx.validate(reason)) << reason; + + // Verify signing was applied + EXPECT_FALSE(tx.getSigningPubKey().empty()); + EXPECT_TRUE(tx.hasTxnSignature()); + + // Verify common fields + EXPECT_EQ(tx.getAccount(), accountValue); + EXPECT_EQ(tx.getSequence(), sequenceValue); + EXPECT_EQ(tx.getFee(), feeValue); + + // Verify required fields + // Verify optional fields + { + auto const& expected = contractAccountValue; + auto const actualOpt = tx.getContractAccount(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfContractAccount should be present"; + expectEqualField(expected, *actualOpt, "sfContractAccount"); + EXPECT_TRUE(tx.hasContractAccount()); + } + + { + auto const& expected = ownerValue; + auto const actualOpt = tx.getOwner(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfOwner should be present"; + expectEqualField(expected, *actualOpt, "sfOwner"); + EXPECT_TRUE(tx.hasOwner()); + } + + { + auto const& expected = contractCodeValue; + auto const actualOpt = tx.getContractCode(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfContractCode should be present"; + expectEqualField(expected, *actualOpt, "sfContractCode"); + EXPECT_TRUE(tx.hasContractCode()); + } + + { + auto const& expected = contractHashValue; + auto const actualOpt = tx.getContractHash(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfContractHash should be present"; + expectEqualField(expected, *actualOpt, "sfContractHash"); + EXPECT_TRUE(tx.hasContractHash()); + } + + { + auto const& expected = functionsValue; + auto const actualOpt = tx.getFunctions(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfFunctions should be present"; + expectEqualField(expected, *actualOpt, "sfFunctions"); + EXPECT_TRUE(tx.hasFunctions()); + } + + { + auto const& expected = instanceParametersValue; + auto const actualOpt = tx.getInstanceParameters(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfInstanceParameters should be present"; + expectEqualField(expected, *actualOpt, "sfInstanceParameters"); + EXPECT_TRUE(tx.hasInstanceParameters()); + } + + { + auto const& expected = instanceParameterValuesValue; + auto const actualOpt = tx.getInstanceParameterValues(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfInstanceParameterValues should be present"; + expectEqualField(expected, *actualOpt, "sfInstanceParameterValues"); + EXPECT_TRUE(tx.hasInstanceParameterValues()); + } + + { + auto const& expected = uRIValue; + auto const actualOpt = tx.getURI(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfURI should be present"; + expectEqualField(expected, *actualOpt, "sfURI"); + EXPECT_TRUE(tx.hasURI()); + } + +} + +// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper, +// and verify all fields match. +TEST(TransactionsContractModifyTests, BuilderFromStTxRoundTrip) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::secp256k1, generateSeed("testContractModifyFromTx")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 2; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific field values + auto const contractAccountValue = canonical_ACCOUNT(); + auto const ownerValue = canonical_ACCOUNT(); + auto const contractCodeValue = canonical_VL(); + auto const contractHashValue = canonical_UINT256(); + auto const functionsValue = canonical_ARRAY(); + auto const instanceParametersValue = canonical_ARRAY(); + auto const instanceParameterValuesValue = canonical_ARRAY(); + auto const uRIValue = canonical_VL(); + + // Build an initial transaction + ContractModifyBuilder initialBuilder{ + accountValue, + sequenceValue, + feeValue + }; + + initialBuilder.setContractAccount(contractAccountValue); + initialBuilder.setOwner(ownerValue); + initialBuilder.setContractCode(contractCodeValue); + initialBuilder.setContractHash(contractHashValue); + initialBuilder.setFunctions(functionsValue); + initialBuilder.setInstanceParameters(instanceParametersValue); + initialBuilder.setInstanceParameterValues(instanceParameterValuesValue); + initialBuilder.setURI(uRIValue); + + auto initialTx = initialBuilder.build(publicKey, secretKey); + + // Create builder from existing STTx + ContractModifyBuilder builderFromTx{initialTx.getSTTx()}; + + auto rebuiltTx = builderFromTx.build(publicKey, secretKey); + + std::string reason; + EXPECT_TRUE(rebuiltTx.validate(reason)) << reason; + + // Verify common fields + EXPECT_EQ(rebuiltTx.getAccount(), accountValue); + EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue); + EXPECT_EQ(rebuiltTx.getFee(), feeValue); + + // Verify required fields + // Verify optional fields + { + auto const& expected = contractAccountValue; + auto const actualOpt = rebuiltTx.getContractAccount(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfContractAccount should be present"; + expectEqualField(expected, *actualOpt, "sfContractAccount"); + } + + { + auto const& expected = ownerValue; + auto const actualOpt = rebuiltTx.getOwner(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfOwner should be present"; + expectEqualField(expected, *actualOpt, "sfOwner"); + } + + { + auto const& expected = contractCodeValue; + auto const actualOpt = rebuiltTx.getContractCode(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfContractCode should be present"; + expectEqualField(expected, *actualOpt, "sfContractCode"); + } + + { + auto const& expected = contractHashValue; + auto const actualOpt = rebuiltTx.getContractHash(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfContractHash should be present"; + expectEqualField(expected, *actualOpt, "sfContractHash"); + } + + { + auto const& expected = functionsValue; + auto const actualOpt = rebuiltTx.getFunctions(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfFunctions should be present"; + expectEqualField(expected, *actualOpt, "sfFunctions"); + } + + { + auto const& expected = instanceParametersValue; + auto const actualOpt = rebuiltTx.getInstanceParameters(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfInstanceParameters should be present"; + expectEqualField(expected, *actualOpt, "sfInstanceParameters"); + } + + { + auto const& expected = instanceParameterValuesValue; + auto const actualOpt = rebuiltTx.getInstanceParameterValues(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfInstanceParameterValues should be present"; + expectEqualField(expected, *actualOpt, "sfInstanceParameterValues"); + } + + { + auto const& expected = uRIValue; + auto const actualOpt = rebuiltTx.getURI(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfURI should be present"; + expectEqualField(expected, *actualOpt, "sfURI"); + } + +} + +// 3) Verify wrapper throws when constructed from wrong transaction type. +TEST(TransactionsContractModifyTests, WrapperThrowsOnWrongTxType) +{ + // Build a valid transaction of a different type + auto const [pk, sk] = + generateKeyPair(KeyType::secp256k1, generateSeed("testWrongType")); + auto const account = calcAccountID(pk); + + AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()}; + auto wrongTx = wrongBuilder.build(pk, sk); + + EXPECT_THROW(ContractModify{wrongTx.getSTTx()}, std::runtime_error); +} + +// 4) Verify builder throws when constructed from wrong transaction type. +TEST(TransactionsContractModifyTests, BuilderThrowsOnWrongTxType) +{ + // Build a valid transaction of a different type + auto const [pk, sk] = + generateKeyPair(KeyType::secp256k1, generateSeed("testWrongTypeBuilder")); + auto const account = calcAccountID(pk); + + AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()}; + auto wrongTx = wrongBuilder.build(pk, sk); + + EXPECT_THROW(ContractModifyBuilder{wrongTx.getSTTx()}, std::runtime_error); +} + +// 5) Build with only required fields and verify optional fields return nullopt. +TEST(TransactionsContractModifyTests, OptionalFieldsReturnNullopt) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::secp256k1, generateSeed("testContractModifyNullopt")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 3; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific required field values + + ContractModifyBuilder builder{ + accountValue, + sequenceValue, + feeValue + }; + + // Do NOT set optional fields + + auto tx = builder.build(publicKey, secretKey); + + // Verify optional fields are not present + EXPECT_FALSE(tx.hasContractAccount()); + EXPECT_FALSE(tx.getContractAccount().has_value()); + EXPECT_FALSE(tx.hasOwner()); + EXPECT_FALSE(tx.getOwner().has_value()); + EXPECT_FALSE(tx.hasContractCode()); + EXPECT_FALSE(tx.getContractCode().has_value()); + EXPECT_FALSE(tx.hasContractHash()); + EXPECT_FALSE(tx.getContractHash().has_value()); + EXPECT_FALSE(tx.hasFunctions()); + EXPECT_FALSE(tx.getFunctions().has_value()); + EXPECT_FALSE(tx.hasInstanceParameters()); + EXPECT_FALSE(tx.getInstanceParameters().has_value()); + EXPECT_FALSE(tx.hasInstanceParameterValues()); + EXPECT_FALSE(tx.getInstanceParameterValues().has_value()); + EXPECT_FALSE(tx.hasURI()); + EXPECT_FALSE(tx.getURI().has_value()); +} + +} diff --git a/src/tests/libxrpl/protocol_autogen/transactions/ContractUserDeleteTests.cpp b/src/tests/libxrpl/protocol_autogen/transactions/ContractUserDeleteTests.cpp new file mode 100644 index 00000000000..896f0157a2a --- /dev/null +++ b/src/tests/libxrpl/protocol_autogen/transactions/ContractUserDeleteTests.cpp @@ -0,0 +1,231 @@ +// Auto-generated unit tests for transaction ContractUserDelete + + +#include + +#include + +#include +#include +#include +#include +#include + +#include + +namespace xrpl::transactions { + +// 1 & 4) Set fields via builder setters, build, then read them back via +// wrapper getters. After build(), validate() should succeed. +TEST(TransactionsContractUserDeleteTests, BuilderSettersRoundTrip) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::secp256k1, generateSeed("testContractUserDelete")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 1; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific field values + auto const contractAccountValue = canonical_ACCOUNT(); + auto const functionNameValue = canonical_VL(); + auto const parametersValue = canonical_ARRAY(); + auto const computationAllowanceValue = canonical_UINT32(); + + ContractUserDeleteBuilder builder{ + accountValue, + contractAccountValue, + functionNameValue, + computationAllowanceValue, + sequenceValue, + feeValue + }; + + // Set optional fields + builder.setParameters(parametersValue); + + auto tx = builder.build(publicKey, secretKey); + + std::string reason; + EXPECT_TRUE(tx.validate(reason)) << reason; + + // Verify signing was applied + EXPECT_FALSE(tx.getSigningPubKey().empty()); + EXPECT_TRUE(tx.hasTxnSignature()); + + // Verify common fields + EXPECT_EQ(tx.getAccount(), accountValue); + EXPECT_EQ(tx.getSequence(), sequenceValue); + EXPECT_EQ(tx.getFee(), feeValue); + + // Verify required fields + { + auto const& expected = contractAccountValue; + auto const actual = tx.getContractAccount(); + expectEqualField(expected, actual, "sfContractAccount"); + } + + { + auto const& expected = functionNameValue; + auto const actual = tx.getFunctionName(); + expectEqualField(expected, actual, "sfFunctionName"); + } + + { + auto const& expected = computationAllowanceValue; + auto const actual = tx.getComputationAllowance(); + expectEqualField(expected, actual, "sfComputationAllowance"); + } + + // Verify optional fields + { + auto const& expected = parametersValue; + auto const actualOpt = tx.getParameters(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfParameters should be present"; + expectEqualField(expected, *actualOpt, "sfParameters"); + EXPECT_TRUE(tx.hasParameters()); + } + +} + +// 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper, +// and verify all fields match. +TEST(TransactionsContractUserDeleteTests, BuilderFromStTxRoundTrip) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::secp256k1, generateSeed("testContractUserDeleteFromTx")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 2; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific field values + auto const contractAccountValue = canonical_ACCOUNT(); + auto const functionNameValue = canonical_VL(); + auto const parametersValue = canonical_ARRAY(); + auto const computationAllowanceValue = canonical_UINT32(); + + // Build an initial transaction + ContractUserDeleteBuilder initialBuilder{ + accountValue, + contractAccountValue, + functionNameValue, + computationAllowanceValue, + sequenceValue, + feeValue + }; + + initialBuilder.setParameters(parametersValue); + + auto initialTx = initialBuilder.build(publicKey, secretKey); + + // Create builder from existing STTx + ContractUserDeleteBuilder builderFromTx{initialTx.getSTTx()}; + + auto rebuiltTx = builderFromTx.build(publicKey, secretKey); + + std::string reason; + EXPECT_TRUE(rebuiltTx.validate(reason)) << reason; + + // Verify common fields + EXPECT_EQ(rebuiltTx.getAccount(), accountValue); + EXPECT_EQ(rebuiltTx.getSequence(), sequenceValue); + EXPECT_EQ(rebuiltTx.getFee(), feeValue); + + // Verify required fields + { + auto const& expected = contractAccountValue; + auto const actual = rebuiltTx.getContractAccount(); + expectEqualField(expected, actual, "sfContractAccount"); + } + + { + auto const& expected = functionNameValue; + auto const actual = rebuiltTx.getFunctionName(); + expectEqualField(expected, actual, "sfFunctionName"); + } + + { + auto const& expected = computationAllowanceValue; + auto const actual = rebuiltTx.getComputationAllowance(); + expectEqualField(expected, actual, "sfComputationAllowance"); + } + + // Verify optional fields + { + auto const& expected = parametersValue; + auto const actualOpt = rebuiltTx.getParameters(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfParameters should be present"; + expectEqualField(expected, *actualOpt, "sfParameters"); + } + +} + +// 3) Verify wrapper throws when constructed from wrong transaction type. +TEST(TransactionsContractUserDeleteTests, WrapperThrowsOnWrongTxType) +{ + // Build a valid transaction of a different type + auto const [pk, sk] = + generateKeyPair(KeyType::secp256k1, generateSeed("testWrongType")); + auto const account = calcAccountID(pk); + + AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()}; + auto wrongTx = wrongBuilder.build(pk, sk); + + EXPECT_THROW(ContractUserDelete{wrongTx.getSTTx()}, std::runtime_error); +} + +// 4) Verify builder throws when constructed from wrong transaction type. +TEST(TransactionsContractUserDeleteTests, BuilderThrowsOnWrongTxType) +{ + // Build a valid transaction of a different type + auto const [pk, sk] = + generateKeyPair(KeyType::secp256k1, generateSeed("testWrongTypeBuilder")); + auto const account = calcAccountID(pk); + + AccountSetBuilder wrongBuilder{account, 1, canonical_AMOUNT()}; + auto wrongTx = wrongBuilder.build(pk, sk); + + EXPECT_THROW(ContractUserDeleteBuilder{wrongTx.getSTTx()}, std::runtime_error); +} + +// 5) Build with only required fields and verify optional fields return nullopt. +TEST(TransactionsContractUserDeleteTests, OptionalFieldsReturnNullopt) +{ + // Generate a deterministic keypair for signing + auto const [publicKey, secretKey] = + generateKeyPair(KeyType::secp256k1, generateSeed("testContractUserDeleteNullopt")); + + // Common transaction fields + auto const accountValue = calcAccountID(publicKey); + std::uint32_t const sequenceValue = 3; + auto const feeValue = canonical_AMOUNT(); + + // Transaction-specific required field values + auto const contractAccountValue = canonical_ACCOUNT(); + auto const functionNameValue = canonical_VL(); + auto const computationAllowanceValue = canonical_UINT32(); + + ContractUserDeleteBuilder builder{ + accountValue, + contractAccountValue, + functionNameValue, + computationAllowanceValue, + sequenceValue, + feeValue + }; + + // Do NOT set optional fields + + auto tx = builder.build(publicKey, secretKey); + + // Verify optional fields are not present + EXPECT_FALSE(tx.hasParameters()); + EXPECT_FALSE(tx.getParameters().has_value()); +} + +} diff --git a/src/tests/libxrpl/protocol_autogen/transactions/EscrowCreateTests.cpp b/src/tests/libxrpl/protocol_autogen/transactions/EscrowCreateTests.cpp index 1d62fadb209..54c5bf8f434 100644 --- a/src/tests/libxrpl/protocol_autogen/transactions/EscrowCreateTests.cpp +++ b/src/tests/libxrpl/protocol_autogen/transactions/EscrowCreateTests.cpp @@ -30,11 +30,13 @@ TEST(TransactionsEscrowCreateTests, BuilderSettersRoundTrip) // Transaction-specific field values auto const destinationValue = canonical_ACCOUNT(); + auto const destinationTagValue = canonical_UINT32(); auto const amountValue = canonical_AMOUNT(); auto const conditionValue = canonical_VL(); auto const cancelAfterValue = canonical_UINT32(); auto const finishAfterValue = canonical_UINT32(); - auto const destinationTagValue = canonical_UINT32(); + auto const finishFunctionValue = canonical_VL(); + auto const dataValue = canonical_VL(); EscrowCreateBuilder builder{ accountValue, @@ -45,10 +47,12 @@ TEST(TransactionsEscrowCreateTests, BuilderSettersRoundTrip) }; // Set optional fields + builder.setDestinationTag(destinationTagValue); builder.setCondition(conditionValue); builder.setCancelAfter(cancelAfterValue); builder.setFinishAfter(finishAfterValue); - builder.setDestinationTag(destinationTagValue); + builder.setFinishFunction(finishFunctionValue); + builder.setData(dataValue); auto tx = builder.build(publicKey, secretKey); @@ -78,6 +82,14 @@ TEST(TransactionsEscrowCreateTests, BuilderSettersRoundTrip) } // Verify optional fields + { + auto const& expected = destinationTagValue; + auto const actualOpt = tx.getDestinationTag(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfDestinationTag should be present"; + expectEqualField(expected, *actualOpt, "sfDestinationTag"); + EXPECT_TRUE(tx.hasDestinationTag()); + } + { auto const& expected = conditionValue; auto const actualOpt = tx.getCondition(); @@ -103,11 +115,19 @@ TEST(TransactionsEscrowCreateTests, BuilderSettersRoundTrip) } { - auto const& expected = destinationTagValue; - auto const actualOpt = tx.getDestinationTag(); - ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfDestinationTag should be present"; - expectEqualField(expected, *actualOpt, "sfDestinationTag"); - EXPECT_TRUE(tx.hasDestinationTag()); + auto const& expected = finishFunctionValue; + auto const actualOpt = tx.getFinishFunction(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfFinishFunction should be present"; + expectEqualField(expected, *actualOpt, "sfFinishFunction"); + EXPECT_TRUE(tx.hasFinishFunction()); + } + + { + auto const& expected = dataValue; + auto const actualOpt = tx.getData(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfData should be present"; + expectEqualField(expected, *actualOpt, "sfData"); + EXPECT_TRUE(tx.hasData()); } } @@ -127,11 +147,13 @@ TEST(TransactionsEscrowCreateTests, BuilderFromStTxRoundTrip) // Transaction-specific field values auto const destinationValue = canonical_ACCOUNT(); + auto const destinationTagValue = canonical_UINT32(); auto const amountValue = canonical_AMOUNT(); auto const conditionValue = canonical_VL(); auto const cancelAfterValue = canonical_UINT32(); auto const finishAfterValue = canonical_UINT32(); - auto const destinationTagValue = canonical_UINT32(); + auto const finishFunctionValue = canonical_VL(); + auto const dataValue = canonical_VL(); // Build an initial transaction EscrowCreateBuilder initialBuilder{ @@ -142,10 +164,12 @@ TEST(TransactionsEscrowCreateTests, BuilderFromStTxRoundTrip) feeValue }; + initialBuilder.setDestinationTag(destinationTagValue); initialBuilder.setCondition(conditionValue); initialBuilder.setCancelAfter(cancelAfterValue); initialBuilder.setFinishAfter(finishAfterValue); - initialBuilder.setDestinationTag(destinationTagValue); + initialBuilder.setFinishFunction(finishFunctionValue); + initialBuilder.setData(dataValue); auto initialTx = initialBuilder.build(publicKey, secretKey); @@ -176,6 +200,13 @@ TEST(TransactionsEscrowCreateTests, BuilderFromStTxRoundTrip) } // Verify optional fields + { + auto const& expected = destinationTagValue; + auto const actualOpt = rebuiltTx.getDestinationTag(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfDestinationTag should be present"; + expectEqualField(expected, *actualOpt, "sfDestinationTag"); + } + { auto const& expected = conditionValue; auto const actualOpt = rebuiltTx.getCondition(); @@ -198,10 +229,17 @@ TEST(TransactionsEscrowCreateTests, BuilderFromStTxRoundTrip) } { - auto const& expected = destinationTagValue; - auto const actualOpt = rebuiltTx.getDestinationTag(); - ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfDestinationTag should be present"; - expectEqualField(expected, *actualOpt, "sfDestinationTag"); + auto const& expected = finishFunctionValue; + auto const actualOpt = rebuiltTx.getFinishFunction(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfFinishFunction should be present"; + expectEqualField(expected, *actualOpt, "sfFinishFunction"); + } + + { + auto const& expected = dataValue; + auto const actualOpt = rebuiltTx.getData(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfData should be present"; + expectEqualField(expected, *actualOpt, "sfData"); } } @@ -263,14 +301,18 @@ TEST(TransactionsEscrowCreateTests, OptionalFieldsReturnNullopt) auto tx = builder.build(publicKey, secretKey); // Verify optional fields are not present + EXPECT_FALSE(tx.hasDestinationTag()); + EXPECT_FALSE(tx.getDestinationTag().has_value()); EXPECT_FALSE(tx.hasCondition()); EXPECT_FALSE(tx.getCondition().has_value()); EXPECT_FALSE(tx.hasCancelAfter()); EXPECT_FALSE(tx.getCancelAfter().has_value()); EXPECT_FALSE(tx.hasFinishAfter()); EXPECT_FALSE(tx.getFinishAfter().has_value()); - EXPECT_FALSE(tx.hasDestinationTag()); - EXPECT_FALSE(tx.getDestinationTag().has_value()); + EXPECT_FALSE(tx.hasFinishFunction()); + EXPECT_FALSE(tx.getFinishFunction().has_value()); + EXPECT_FALSE(tx.hasData()); + EXPECT_FALSE(tx.getData().has_value()); } } diff --git a/src/tests/libxrpl/protocol_autogen/transactions/EscrowFinishTests.cpp b/src/tests/libxrpl/protocol_autogen/transactions/EscrowFinishTests.cpp index f76bfffb598..524e106c6a7 100644 --- a/src/tests/libxrpl/protocol_autogen/transactions/EscrowFinishTests.cpp +++ b/src/tests/libxrpl/protocol_autogen/transactions/EscrowFinishTests.cpp @@ -34,6 +34,7 @@ TEST(TransactionsEscrowFinishTests, BuilderSettersRoundTrip) auto const fulfillmentValue = canonical_VL(); auto const conditionValue = canonical_VL(); auto const credentialIDsValue = canonical_VECTOR256(); + auto const computationAllowanceValue = canonical_UINT32(); EscrowFinishBuilder builder{ accountValue, @@ -47,6 +48,7 @@ TEST(TransactionsEscrowFinishTests, BuilderSettersRoundTrip) builder.setFulfillment(fulfillmentValue); builder.setCondition(conditionValue); builder.setCredentialIDs(credentialIDsValue); + builder.setComputationAllowance(computationAllowanceValue); auto tx = builder.build(publicKey, secretKey); @@ -100,6 +102,14 @@ TEST(TransactionsEscrowFinishTests, BuilderSettersRoundTrip) EXPECT_TRUE(tx.hasCredentialIDs()); } + { + auto const& expected = computationAllowanceValue; + auto const actualOpt = tx.getComputationAllowance(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfComputationAllowance should be present"; + expectEqualField(expected, *actualOpt, "sfComputationAllowance"); + EXPECT_TRUE(tx.hasComputationAllowance()); + } + } // 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper, @@ -121,6 +131,7 @@ TEST(TransactionsEscrowFinishTests, BuilderFromStTxRoundTrip) auto const fulfillmentValue = canonical_VL(); auto const conditionValue = canonical_VL(); auto const credentialIDsValue = canonical_VECTOR256(); + auto const computationAllowanceValue = canonical_UINT32(); // Build an initial transaction EscrowFinishBuilder initialBuilder{ @@ -134,6 +145,7 @@ TEST(TransactionsEscrowFinishTests, BuilderFromStTxRoundTrip) initialBuilder.setFulfillment(fulfillmentValue); initialBuilder.setCondition(conditionValue); initialBuilder.setCredentialIDs(credentialIDsValue); + initialBuilder.setComputationAllowance(computationAllowanceValue); auto initialTx = initialBuilder.build(publicKey, secretKey); @@ -185,6 +197,13 @@ TEST(TransactionsEscrowFinishTests, BuilderFromStTxRoundTrip) expectEqualField(expected, *actualOpt, "sfCredentialIDs"); } + { + auto const& expected = computationAllowanceValue; + auto const actualOpt = rebuiltTx.getComputationAllowance(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfComputationAllowance should be present"; + expectEqualField(expected, *actualOpt, "sfComputationAllowance"); + } + } // 3) Verify wrapper throws when constructed from wrong transaction type. @@ -250,6 +269,8 @@ TEST(TransactionsEscrowFinishTests, OptionalFieldsReturnNullopt) EXPECT_FALSE(tx.getCondition().has_value()); EXPECT_FALSE(tx.hasCredentialIDs()); EXPECT_FALSE(tx.getCredentialIDs().has_value()); + EXPECT_FALSE(tx.hasComputationAllowance()); + EXPECT_FALSE(tx.getComputationAllowance().has_value()); } } diff --git a/src/tests/libxrpl/protocol_autogen/transactions/SetFeeTests.cpp b/src/tests/libxrpl/protocol_autogen/transactions/SetFeeTests.cpp index 9605daee9ff..0084b232ddc 100644 --- a/src/tests/libxrpl/protocol_autogen/transactions/SetFeeTests.cpp +++ b/src/tests/libxrpl/protocol_autogen/transactions/SetFeeTests.cpp @@ -37,6 +37,9 @@ TEST(TransactionsSetFeeTests, BuilderSettersRoundTrip) auto const baseFeeDropsValue = canonical_AMOUNT(); auto const reserveBaseDropsValue = canonical_AMOUNT(); auto const reserveIncrementDropsValue = canonical_AMOUNT(); + auto const extensionComputeLimitValue = canonical_UINT32(); + auto const extensionSizeLimitValue = canonical_UINT32(); + auto const gasPriceValue = canonical_UINT32(); SetFeeBuilder builder{ accountValue, @@ -53,6 +56,9 @@ TEST(TransactionsSetFeeTests, BuilderSettersRoundTrip) builder.setBaseFeeDrops(baseFeeDropsValue); builder.setReserveBaseDrops(reserveBaseDropsValue); builder.setReserveIncrementDrops(reserveIncrementDropsValue); + builder.setExtensionComputeLimit(extensionComputeLimitValue); + builder.setExtensionSizeLimit(extensionSizeLimitValue); + builder.setGasPrice(gasPriceValue); auto tx = builder.build(publicKey, secretKey); @@ -134,6 +140,30 @@ TEST(TransactionsSetFeeTests, BuilderSettersRoundTrip) EXPECT_TRUE(tx.hasReserveIncrementDrops()); } + { + auto const& expected = extensionComputeLimitValue; + auto const actualOpt = tx.getExtensionComputeLimit(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfExtensionComputeLimit should be present"; + expectEqualField(expected, *actualOpt, "sfExtensionComputeLimit"); + EXPECT_TRUE(tx.hasExtensionComputeLimit()); + } + + { + auto const& expected = extensionSizeLimitValue; + auto const actualOpt = tx.getExtensionSizeLimit(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfExtensionSizeLimit should be present"; + expectEqualField(expected, *actualOpt, "sfExtensionSizeLimit"); + EXPECT_TRUE(tx.hasExtensionSizeLimit()); + } + + { + auto const& expected = gasPriceValue; + auto const actualOpt = tx.getGasPrice(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfGasPrice should be present"; + expectEqualField(expected, *actualOpt, "sfGasPrice"); + EXPECT_TRUE(tx.hasGasPrice()); + } + } // 2 & 4) Start from an STTx, construct a builder from it, build a new wrapper, @@ -158,6 +188,9 @@ TEST(TransactionsSetFeeTests, BuilderFromStTxRoundTrip) auto const baseFeeDropsValue = canonical_AMOUNT(); auto const reserveBaseDropsValue = canonical_AMOUNT(); auto const reserveIncrementDropsValue = canonical_AMOUNT(); + auto const extensionComputeLimitValue = canonical_UINT32(); + auto const extensionSizeLimitValue = canonical_UINT32(); + auto const gasPriceValue = canonical_UINT32(); // Build an initial transaction SetFeeBuilder initialBuilder{ @@ -174,6 +207,9 @@ TEST(TransactionsSetFeeTests, BuilderFromStTxRoundTrip) initialBuilder.setBaseFeeDrops(baseFeeDropsValue); initialBuilder.setReserveBaseDrops(reserveBaseDropsValue); initialBuilder.setReserveIncrementDrops(reserveIncrementDropsValue); + initialBuilder.setExtensionComputeLimit(extensionComputeLimitValue); + initialBuilder.setExtensionSizeLimit(extensionSizeLimitValue); + initialBuilder.setGasPrice(gasPriceValue); auto initialTx = initialBuilder.build(publicKey, secretKey); @@ -248,6 +284,27 @@ TEST(TransactionsSetFeeTests, BuilderFromStTxRoundTrip) expectEqualField(expected, *actualOpt, "sfReserveIncrementDrops"); } + { + auto const& expected = extensionComputeLimitValue; + auto const actualOpt = rebuiltTx.getExtensionComputeLimit(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfExtensionComputeLimit should be present"; + expectEqualField(expected, *actualOpt, "sfExtensionComputeLimit"); + } + + { + auto const& expected = extensionSizeLimitValue; + auto const actualOpt = rebuiltTx.getExtensionSizeLimit(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfExtensionSizeLimit should be present"; + expectEqualField(expected, *actualOpt, "sfExtensionSizeLimit"); + } + + { + auto const& expected = gasPriceValue; + auto const actualOpt = rebuiltTx.getGasPrice(); + ASSERT_TRUE(actualOpt.has_value()) << "Optional field sfGasPrice should be present"; + expectEqualField(expected, *actualOpt, "sfGasPrice"); + } + } // 3) Verify wrapper throws when constructed from wrong transaction type. @@ -319,6 +376,12 @@ TEST(TransactionsSetFeeTests, OptionalFieldsReturnNullopt) EXPECT_FALSE(tx.getReserveBaseDrops().has_value()); EXPECT_FALSE(tx.hasReserveIncrementDrops()); EXPECT_FALSE(tx.getReserveIncrementDrops().has_value()); + EXPECT_FALSE(tx.hasExtensionComputeLimit()); + EXPECT_FALSE(tx.getExtensionComputeLimit().has_value()); + EXPECT_FALSE(tx.hasExtensionSizeLimit()); + EXPECT_FALSE(tx.getExtensionSizeLimit().has_value()); + EXPECT_FALSE(tx.hasGasPrice()); + EXPECT_FALSE(tx.getGasPrice().has_value()); } } diff --git a/src/xrpld/app/main/Application.cpp b/src/xrpld/app/main/Application.cpp index a18462f0f7f..58c33e68111 100644 --- a/src/xrpld/app/main/Application.cpp +++ b/src/xrpld/app/main/Application.cpp @@ -817,6 +817,24 @@ class ApplicationImp : public Application, public BasicApp return *walletDB_; } + virtual Fees + getFees() const override + { + XRPL_ASSERT(config_, "xrpl::ApplicationImp::getFees : non-null config"); + + auto const& f1(config_->FEES); + + Fees f2; + f2.base = f1.reference_fee; + f2.reserve = f1.account_reserve; + f2.increment = f1.owner_reserve; + f2.extensionComputeLimit = f1.extension_compute_limit; + f2.extensionSizeLimit = f1.extension_size_limit; + f2.gasPrice = f1.gas_price; + + return f2; + } + bool serverOkay(std::string& reason) override; diff --git a/src/xrpld/app/misc/DeleteUtils.cpp b/src/xrpld/app/misc/DeleteUtils.cpp new file mode 100644 index 00000000000..503a5b72f1c --- /dev/null +++ b/src/xrpld/app/misc/DeleteUtils.cpp @@ -0,0 +1,383 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace xrpl { + +// Local function definitions that provides signature compatibility. +TER +offerDelete( + ServiceRegistry& registry, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return offerDelete(view, sleDel, j); +} + +TER +removeSignersFromLedger( + ServiceRegistry& registry, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return SignerListSet::removeFromLedger(registry, view, account, j); +} + +TER +removeTicketFromLedger( + ServiceRegistry&, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const&, + beast::Journal j) +{ + return Transactor::ticketDelete(view, account, delIndex, j); +} + +TER +removeDepositPreauthFromLedger( + ServiceRegistry&, + ApplyView& view, + AccountID const&, + uint256 const& delIndex, + std::shared_ptr const&, + beast::Journal j) +{ + return DepositPreauth::removeFromLedger(view, delIndex, j); +} + +TER +removeNFTokenOfferFromLedger( + ServiceRegistry& registry, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal) +{ + if (!nft::deleteTokenOffer(view, sleDel)) + return tefBAD_LEDGER; + + return tesSUCCESS; +} + +TER +removeDIDFromLedger( + ServiceRegistry& registry, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return DIDDelete::deleteSLE(view, sleDel, account, j); +} + +TER +removeOracleFromLedger( + ServiceRegistry&, + ApplyView& view, + AccountID const& account, + uint256 const&, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return OracleDelete::deleteOracle(view, sleDel, account, j); +} + +TER +removeCredentialFromLedger( + ServiceRegistry&, + ApplyView& view, + AccountID const&, + uint256 const&, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return credentials::deleteSLE(view, sleDel, j); +} + +TER +removeDelegateFromLedger( + ServiceRegistry& registry, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return DelegateSet::deleteDelegate(view, sleDel, account, j); +} + +TER +removeContractFromLedger( + ServiceRegistry& registry, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j) +{ + return ContractDelete::deleteContract(view, sleDel, account, j); +} + +// Return nullptr if the LedgerEntryType represents an obligation that can't +// be deleted. Otherwise return the pointer to the function that can delete +// the non-obligation +DeleterFuncPtr +nonObligationDeleter(LedgerEntryType t) +{ + switch (t) + { + case ltOFFER: + return offerDelete; + case ltSIGNER_LIST: + return removeSignersFromLedger; + case ltTICKET: + return removeTicketFromLedger; + case ltDEPOSIT_PREAUTH: + return removeDepositPreauthFromLedger; + case ltNFTOKEN_OFFER: + return removeNFTokenOfferFromLedger; + case ltDID: + return removeDIDFromLedger; + case ltORACLE: + return removeOracleFromLedger; + case ltCREDENTIAL: + return removeCredentialFromLedger; + case ltDELEGATE: + return removeDelegateFromLedger; + case ltCONTRACT: + return removeContractFromLedger; + default: + return nullptr; + } +} + +TER +deletePreclaim( + PreclaimContext const& ctx, + std::uint32_t seqDelta, + AccountID const account, + AccountID const dest, + bool isPseudoAccount) +{ + auto destSle = ctx.view.read(keylet::account(dest)); + + if (!destSle) + return tecNO_DST; + + if ((*destSle)[sfFlags] & lsfRequireDestTag && !ctx.tx[~sfDestinationTag]) + return tecDST_TAG_NEEDED; + + // If credentials are provided - check them anyway + if (auto const err = credentials::valid(ctx.tx, ctx.view, account, ctx.j); !isTesSuccess(err)) + return err; + + // if credentials then postpone auth check to doApply, to check for expired + // credentials + if (!ctx.tx.isFieldPresent(sfCredentialIDs)) + { + // Check whether the destination account requires deposit authorization. + if (destSle->getFlags() & lsfDepositAuth) + { + if (!ctx.view.exists(keylet::depositPreauth(dest, account)) && !isPseudoAccount) + return tecNO_PERMISSION; + } + } + + auto srcSle = ctx.view.read(keylet::account(account)); + XRPL_ASSERT(srcSle, "xrpl::DeleteAccount::preclaim : non-null account"); + if (!srcSle) + return terNO_ACCOUNT; + + { + // If an issuer has any issued NFTs resident in the ledger then it + // cannot be deleted. + if ((*srcSle)[~sfMintedNFTokens] != (*srcSle)[~sfBurnedNFTokens]) + return tecHAS_OBLIGATIONS; + + // If the account owns any NFTs it cannot be deleted. + Keylet const first = keylet::nftpage_min(account); + Keylet const last = keylet::nftpage_max(account); + + auto const cp = ctx.view.read( + Keylet(ltNFTOKEN_PAGE, ctx.view.succ(first.key, last.key.next()).value_or(last.key))); + if (cp) + return tecHAS_OBLIGATIONS; + } + + // We don't allow an account to be deleted if its sequence number + // is within 256 of the current ledger. This prevents replay of old + // transactions if this account is resurrected after it is deleted. + // + // We look at the account's Sequence rather than the transaction's + // Sequence in preparation for Tickets. + if ((*srcSle)[sfSequence] + seqDelta > ctx.view.seq()) + return tecTOO_SOON; + + // When fixNFTokenRemint is enabled, we don't allow an account to be + // deleted if is within 256 of the + // current ledger. This is to prevent having duplicate NFTokenIDs after + // account re-creation. + // + // Without this restriction, duplicate NFTokenIDs can be reproduced when + // authorized minting is involved. Because when the minter mints a NFToken, + // the issuer's sequence does not change. So when the issuer re-creates + // their account and mints a NFToken, it is possible that the + // NFTokenSequence of this NFToken is the same as the one that the + // authorized minter minted in a previous ledger. + if ((*srcSle)[~sfFirstNFTokenSequence].value_or(0) + (*srcSle)[~sfMintedNFTokens].value_or(0) + + seqDelta > + ctx.view.seq()) + return tecTOO_SOON; + + // Verify that the account does not own any objects that would prevent + // the account from being deleted. + Keylet const ownerDirKeylet{keylet::ownerDir(account)}; + if (dirIsEmpty(ctx.view, ownerDirKeylet)) + return tesSUCCESS; + + std::shared_ptr sleDirNode{}; + unsigned int uDirEntry{0}; + uint256 dirEntry{beast::zero}; + + // Account has no directory at all. This _should_ have been caught + // by the dirIsEmpty() check earlier, but it's okay to catch it here. + if (!cdirFirst(ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry)) + return tesSUCCESS; + + std::int32_t deletableDirEntryCount{0}; + do + { + // Make sure any directory node types that we find are the kind + // we can delete. + auto sleItem = ctx.view.read(keylet::child(dirEntry)); + if (!sleItem) + { + // Directory node has an invalid index. Bail out. + JLOG(ctx.j.fatal()) << "DeleteAccount: directory node in ledger " << ctx.view.seq() + << " has index to object that is missing: " << to_string(dirEntry); + return tefBAD_LEDGER; + } + + LedgerEntryType const nodeType{safe_cast((*sleItem)[sfLedgerEntryType])}; + + if (!nonObligationDeleter(nodeType)) + return tecHAS_OBLIGATIONS; + + // We found a deletable directory entry. Count it. If we find too + // many deletable directory entries then bail out. + if (++deletableDirEntryCount > maxDeletableDirEntries) + return tefTOO_BIG; + + } while (cdirNext(ctx.view, ownerDirKeylet.key, sleDirNode, uDirEntry, dirEntry)); + + return tesSUCCESS; +} + +TER +deleteDoApply( + ApplyContext& applyCtx, + STAmount const& accountBalance, + AccountID const& account, + AccountID const& dest) +{ + auto& view = applyCtx.view(); + STTx const tx = applyCtx.tx; + beast::Journal j = applyCtx.journal; + + auto srcSle = view.peek(keylet::account(account)); + XRPL_ASSERT(srcSle, "xrpl::DeleteAccount::doApply : non-null source account"); + + if (!srcSle) + return tefBAD_LEDGER; + + auto destSle = view.peek(keylet::account(dest)); + XRPL_ASSERT(destSle, "xrpl::DeleteAccount::doApply : non-null destination account"); + + if (!destSle) + return tefBAD_LEDGER; + + if (tx.isFieldPresent(sfCredentialIDs)) + { + if (auto err = verifyDepositPreauth(tx, view, account, dest, destSle, j); + !isTesSuccess(err)) + return err; + } + + Keylet const ownerDirKeylet{keylet::ownerDir(account)}; + auto const ter = cleanupOnAccountDelete( + view, + ownerDirKeylet, + [&](LedgerEntryType nodeType, + uint256 const& dirEntry, + std::shared_ptr& sleItem) -> std::pair { + if (auto deleter = nonObligationDeleter(nodeType)) + { + TER const result{deleter(applyCtx.registry, view, account, dirEntry, sleItem, j)}; + + return {result, SkipEntry::No}; + } + + UNREACHABLE( + "xrpl::DeleteAccount::doApply : undeletable item not found " + "in preclaim"); + JLOG(j.error()) << "DeleteAccount undeletable item not " + "found in preclaim."; + return {tecHAS_OBLIGATIONS, SkipEntry::No}; + }, + j); + if (ter != tesSUCCESS) + return ter; + + // Transfer any XRP remaining after the fee is paid to the destination: + (*destSle)[sfBalance] = (*destSle)[sfBalance] + accountBalance; + (*srcSle)[sfBalance] = (*srcSle)[sfBalance] - accountBalance; + applyCtx.deliver(accountBalance); + + // DA: Pseudo accounts can have 0 balance, so we skip this assert. + // FIX FIX FIX: DA FIX + // XRPL_ASSERT( + // (*srcSle)[sfBalance] == XRPAmount(0), + // "xrpl::DeleteAccount::doApply : source balance is zero"); + + // If there's still an owner directory associated with the source account + // delete it. + if (view.exists(ownerDirKeylet) && !view.emptyDirDelete(ownerDirKeylet)) + { + JLOG(j.error()) << "DeleteAccount cannot delete root dir node of " << toBase58(account); + return tecHAS_OBLIGATIONS; + } + + // Re-arm the password change fee if we can and need to. + if (accountBalance > XRPAmount(0) && (*destSle).isFlag(lsfPasswordSpent)) + (*destSle).clearFlag(lsfPasswordSpent); + + view.update(destSle); + view.erase(srcSle); + + return tesSUCCESS; +} + +} // namespace xrpl diff --git a/src/xrpld/app/misc/DeleteUtils.h b/src/xrpld/app/misc/DeleteUtils.h new file mode 100644 index 00000000000..6bcca1fb5f8 --- /dev/null +++ b/src/xrpld/app/misc/DeleteUtils.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +// Define a function pointer type that can be used to delete ledger node types. +using DeleterFuncPtr = TER (*)( + ServiceRegistry& registry, + ApplyView& view, + AccountID const& account, + uint256 const& delIndex, + std::shared_ptr const& sleDel, + beast::Journal j); + +DeleterFuncPtr +nonObligationDeleter(LedgerEntryType t); + +TER +deletePreclaim( + PreclaimContext const& ctx, + std::uint32_t seqDelta, + AccountID const account, + AccountID const dest, + bool isPseudoAccount = false); + +TER +deleteDoApply( + ApplyContext& applyCtx, + STAmount const& accountBalance, + AccountID const& account, + AccountID const& dest); + +} // namespace xrpl diff --git a/src/xrpld/app/misc/FeeVoteImpl.cpp b/src/xrpld/app/misc/FeeVoteImpl.cpp index c4e6c3cdc2a..0e9d146e113 100644 --- a/src/xrpld/app/misc/FeeVoteImpl.cpp +++ b/src/xrpld/app/misc/FeeVoteImpl.cpp @@ -32,10 +32,10 @@ namespace xrpl { namespace detail { +template class VotableValue { private: - using value_type = XRPAmount; value_type const current_; // The current setting value_type const target_; // The setting we want std::map voteMap_; @@ -69,8 +69,9 @@ class VotableValue getVotes() const; }; -auto -VotableValue::getVotes() const -> std::pair +template +std::pair +VotableValue::getVotes() const { value_type ourVote = current_; int weight = 0; @@ -124,6 +125,14 @@ FeeVoteImpl::doValidation(Fees const& lastFees, Rules const& rules, STValidation // Values should always be in a valid range (because the voting process // will ignore out-of-range values) but if we detect such a case, we do // not send a value. + auto vote = [&v, this](auto const current, auto target, char const* name, auto const& sfield) { + if (current != target) + { + JLOG(journal_.info()) << "Voting for " << name << " of " << target; + + v[sfield] = target; + } + }; if (rules.enabled(featureXRPFees)) { auto vote = @@ -144,12 +153,12 @@ FeeVoteImpl::doValidation(Fees const& lastFees, Rules const& rules, STValidation { auto to32 = [](XRPAmount target) { return target.dropsAs(); }; auto to64 = [](XRPAmount target) { return target.dropsAs(); }; - auto vote = [&v, this]( - auto const current, - XRPAmount target, - auto const& convertCallback, - char const* name, - auto const& sfield) { + auto voteAndConvert = [&v, this]( + auto const current, + XRPAmount target, + auto const& convertCallback, + char const* name, + auto const& sfield) { if (current != target) { JLOG(journal_.info()) << "Voting for " << name << " of " << target; @@ -168,6 +177,20 @@ FeeVoteImpl::doValidation(Fees const& lastFees, Rules const& rules, STValidation "reserve increment", sfReserveIncrement); } + if (rules.enabled(featureSmartEscrow)) + { + vote( + lastFees.extensionComputeLimit, + target_.extension_compute_limit, + "extension compute limit", + sfExtensionComputeLimit); + vote( + lastFees.extensionSizeLimit, + target_.extension_size_limit, + "extension size limit", + sfExtensionSizeLimit); + vote(lastFees.gasPrice, target_.gas_price, "gas price", sfGasPrice); + } } void @@ -187,11 +210,19 @@ FeeVoteImpl::doVoting( detail::VotableValue incReserveVote(lastClosedLedger->fees().increment, target_.ownerReserve); + detail::VotableValue extensionComputeVote( + lastClosedLedger->fees().extensionComputeLimit, target_.extension_compute_limit); + + detail::VotableValue extensionSizeVote( + lastClosedLedger->fees().extensionSizeLimit, target_.extension_size_limit); + + detail::VotableValue gasPriceVote(lastClosedLedger->fees().gasPrice, target_.gas_price); + auto const& rules = lastClosedLedger->rules(); if (rules.enabled(featureXRPFees)) { auto doVote = [](std::shared_ptr const& val, - detail::VotableValue& value, + detail::VotableValue& value, SF_AMOUNT const& xrpField) { if (auto const field = ~val->at(~xrpField); field && field->native()) { @@ -223,7 +254,7 @@ FeeVoteImpl::doVoting( else { auto doVote = [](std::shared_ptr const& val, - detail::VotableValue& value, + detail::VotableValue& value, auto const& valueField) { if (auto const field = val->at(~valueField)) { @@ -257,6 +288,30 @@ FeeVoteImpl::doVoting( doVote(val, incReserveVote, sfReserveIncrement); } } + if (rules.enabled(featureSmartEscrow)) + { + auto doVote = [](std::shared_ptr const& val, + detail::VotableValue& value, + SF_UINT32 const& extensionField) { + if (auto const field = ~val->at(~extensionField); field) + { + value.addVote(field.value()); + } + else + { + value.noVote(); + } + }; + + for (auto const& val : set) + { + if (!val->isTrusted()) + continue; + doVote(val, extensionComputeVote, sfExtensionComputeLimit); + doVote(val, extensionSizeVote, sfExtensionSizeLimit); + doVote(val, gasPriceVote, sfGasPrice); + } + } // choose our positions // TODO: Use structured binding once LLVM 16 is the minimum supported @@ -265,11 +320,15 @@ FeeVoteImpl::doVoting( auto const baseFee = baseFeeVote.getVotes(); auto const baseReserve = baseReserveVote.getVotes(); auto const incReserve = incReserveVote.getVotes(); + auto const extensionCompute = extensionComputeVote.getVotes(); + auto const extensionSize = extensionSizeVote.getVotes(); + auto const gasPrice = gasPriceVote.getVotes(); auto const seq = lastClosedLedger->header().seq + 1; // add transactions to our position - if (baseFee.second || baseReserve.second || incReserve.second) + if (baseFee.second || baseReserve.second || incReserve.second || extensionCompute.second || + extensionSize.second || gasPrice.second) { JLOG(journal_.warn()) << "We are voting for a fee change: " << baseFee.first << "/" << baseReserve.first << "/" << incReserve.first; @@ -294,6 +353,12 @@ FeeVoteImpl::doVoting( incReserve.first.dropsAs(incReserveVote.current()); obj[sfReferenceFeeUnits] = kFeeUnitsDeprecated; } + if (rules.enabled(featureSmartEscrow)) + { + obj[sfExtensionComputeLimit] = extensionCompute.first; + obj[sfExtensionSizeLimit] = extensionSize.first; + obj[sfGasPrice] = gasPrice.first; + } }); uint256 const txID = feeTx.getTransactionID(); diff --git a/src/xrpld/app/misc/NetworkOPs.cpp b/src/xrpld/app/misc/NetworkOPs.cpp index 12c79b821cc..933b64a142e 100644 --- a/src/xrpld/app/misc/NetworkOPs.cpp +++ b/src/xrpld/app/misc/NetworkOPs.cpp @@ -621,6 +621,12 @@ class NetworkOPsImp final : public NetworkOPs subConsensus(InfoSub::ref ispListener) override; bool unsubConsensus(std::uint64_t uListener) override; + bool + subContractEvent(InfoSub::ref ispListener) override; + bool + unsubContractEvent(std::uint64_t uListener) override; + void + pubContractEvent(std::string const& name, STJson const& event) override; InfoSub::pointer findRpcSub(std::string const& strUrl) override; @@ -821,6 +827,7 @@ class NetworkOPsImp final : public NetworkOPs SPeerStatus, // Peer status changes. SConsensusPhase, // Consensus phase SBookChanges, // Per-ledger order book changes + SContractEvents, // Contract events SLastEntry // Any new entry must be ADDED ABOVE this one }; @@ -2353,6 +2360,34 @@ NetworkOPsImp::pubConsensus(ConsensusPhase phase) } } +void +NetworkOPsImp::pubContractEvent(std::string const& name, STJson const& event) +{ + std::lock_guard const sl(mSubLock); + + auto& streamMap = mStreamMaps[sContractEvents]; + if (!streamMap.empty()) + { + Json::Value jvObj(Json::objectValue); + jvObj[jss::type] = "contractEvent"; + jvObj[jss::name] = name; + jvObj[jss::data] = event.getJson(JsonOptions::none); + + for (auto i = streamMap.begin(); i != streamMap.end();) + { + if (auto p = i->second.lock()) + { + p->send(jvObj, true); + ++i; + } + else + { + i = streamMap.erase(i); + } + } + } +} + void NetworkOPsImp::pubValidation(std::shared_ptr const& val) { @@ -2429,6 +2464,16 @@ NetworkOPsImp::pubValidation(std::shared_ptr const& val) reserveIncXRP && reserveIncXRP->native()) jvObj[jss::reserve_inc] = reserveIncXRP->xrp().jsonClipped(); + if (auto const extensionComputeLimit = ~val->at(~sfExtensionComputeLimit); + extensionComputeLimit) + jvObj[jss::extension_compute] = *extensionComputeLimit; + + if (auto const extensionSizeLimit = ~val->at(~sfExtensionSizeLimit); extensionSizeLimit) + jvObj[jss::extension_size] = *extensionSizeLimit; + + if (auto const gasPrice = ~val->at(~sfGasPrice); gasPrice) + jvObj[jss::gas_price] = *gasPrice; + // NOTE Use MultiApiJson to publish two slightly different JSON objects // for consumers supporting different API versions MultiApiJson multiObj{jvObj}; @@ -2877,11 +2922,18 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) l[jss::seq] = json::UInt(lpClosed->header().seq); l[jss::hash] = to_string(lpClosed->header().hash); + bool const smartEscrowEnabled = lpClosed->rules().enabled(featureSmartEscrow); if (!human) { l[jss::base_fee] = baseFee.jsonClipped(); l[jss::reserve_base] = lpClosed->fees().reserve.jsonClipped(); l[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped(); + if (smartEscrowEnabled) + { + l[jss::extension_compute] = lpClosed->fees().extensionComputeLimit; + l[jss::extension_size] = lpClosed->fees().extensionSizeLimit; + l[jss::gas_price] = lpClosed->fees().gasPrice; + } l[jss::close_time] = json::Value::UInt(lpClosed->header().closeTime.time_since_epoch().count()); } @@ -2890,6 +2942,12 @@ NetworkOPsImp::getServerInfo(bool human, bool admin, bool counters) l[jss::base_fee_xrp] = baseFee.decimalXRP(); l[jss::reserve_base_xrp] = lpClosed->fees().reserve.decimalXRP(); l[jss::reserve_inc_xrp] = lpClosed->fees().increment.decimalXRP(); + if (smartEscrowEnabled) + { + l[jss::extension_compute] = lpClosed->fees().extensionComputeLimit; + l[jss::extension_size] = lpClosed->fees().extensionSizeLimit; + l[jss::gas_price] = lpClosed->fees().gasPrice; + } if (auto const closeOffset = registry_.get().getTimeKeeper().closeOffset(); std::abs(closeOffset.count()) >= 60) @@ -3085,6 +3143,12 @@ NetworkOPsImp::pubLedger(std::shared_ptr const& lpAccepted) jvObj[jss::fee_base] = lpAccepted->fees().base.jsonClipped(); jvObj[jss::reserve_base] = lpAccepted->fees().reserve.jsonClipped(); jvObj[jss::reserve_inc] = lpAccepted->fees().increment.jsonClipped(); + if (lpAccepted->rules().enabled(featureSmartEscrow)) + { + jvObj[jss::extension_compute] = lpAccepted->fees().extensionComputeLimit; + jvObj[jss::extension_size] = lpAccepted->fees().extensionSizeLimit; + jvObj[jss::gas_price] = lpAccepted->fees().gasPrice; + } jvObj[jss::txn_count] = json::UInt(alpAccepted->size()); @@ -4065,6 +4129,12 @@ NetworkOPsImp::subLedger(InfoSub::ref isrListener, json::Value& jvResult) jvResult[jss::reserve_base] = lpClosed->fees().reserve.jsonClipped(); jvResult[jss::reserve_inc] = lpClosed->fees().increment.jsonClipped(); jvResult[jss::network_id] = registry_.get().getNetworkIDService().getNetworkID(); + if (lpClosed->rules().enabled(featureSmartEscrow)) + { + jvResult[jss::extension_compute] = lpClosed->fees().extensionComputeLimit; + jvResult[jss::extension_size] = lpClosed->fees().extensionSizeLimit; + jvResult[jss::gas_price] = lpClosed->fees().gasPrice; + } } if ((mode_ >= OperatingMode::SYNCING) && !isNeedNetworkLedger()) @@ -4235,6 +4305,22 @@ NetworkOPsImp::unsubConsensus(std::uint64_t uSeq) return streamMaps_[SConsensusPhase].erase(uSeq) != 0u; } +// <-- bool: true=added, false=already there +bool +NetworkOPsImp::subContractEvent(InfoSub::ref isrListener) +{ + std::lock_guard const sl(mSubLock); + return mStreamMaps[sContractEvents].emplace(isrListener->getSeq(), isrListener).second; +} + +// <-- bool: true=erased, false=was not there +bool +NetworkOPsImp::unsubContractEvent(std::uint64_t uSeq) +{ + std::lock_guard const sl(mSubLock); + return mStreamMaps[sContractEvents].erase(uSeq); +} + InfoSub::pointer NetworkOPsImp::findRpcSub(std::string const& strUrl) { diff --git a/src/xrpld/app/wasm/detail/ContractHostFuncImpl.cpp b/src/xrpld/app/wasm/detail/ContractHostFuncImpl.cpp new file mode 100644 index 00000000000..cdca7cc5eaf --- /dev/null +++ b/src/xrpld/app/wasm/detail/ContractHostFuncImpl.cpp @@ -0,0 +1,1061 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +Expected +getFieldBytesFromSTData(xrpl::STData const& funcParam, std::uint32_t stTypeId) +{ + switch (stTypeId) + { + case STI_UINT8: { + if (funcParam.getInnerSType() != STI_UINT8) + return Unexpected(HostFunctionError::INVALID_PARAMS); + uint8_t const data = funcParam.getFieldU8(); + return Bytes{data}; + } + case STI_UINT16: { + if (funcParam.getInnerSType() != STI_UINT16) + return Unexpected(HostFunctionError::INVALID_PARAMS); + uint16_t const data = funcParam.getFieldU16(); + return Bytes{ + static_cast(data & 0xFF), + static_cast((data >> 8) & 0xFF)}; + } + case STI_UINT32: { + if (funcParam.getInnerSType() != STI_UINT32) + return Unexpected(HostFunctionError::INVALID_PARAMS); + uint32_t const data = funcParam.getFieldU32(); + return Bytes{ + static_cast(data & 0xFF), + static_cast((data >> 8) & 0xFF), + static_cast((data >> 16) & 0xFF), + static_cast((data >> 24) & 0xFF)}; + } + case STI_UINT64: { + if (funcParam.getInnerSType() != STI_UINT64) + return Unexpected(HostFunctionError::INVALID_PARAMS); + uint64_t const data = funcParam.getFieldU64(); + return Bytes{ + static_cast(data & 0xFF), + static_cast((data >> 8) & 0xFF), + static_cast((data >> 16) & 0xFF), + static_cast((data >> 24) & 0xFF), + static_cast((data >> 32) & 0xFF), + static_cast((data >> 40) & 0xFF), + static_cast((data >> 48) & 0xFF), + static_cast((data >> 56) & 0xFF)}; + } + case STI_UINT128: { + if (funcParam.getInnerSType() != STI_UINT128) + return Unexpected(HostFunctionError::INVALID_PARAMS); + uint128 data = funcParam.getFieldH128(); + return Bytes{ + reinterpret_cast(&data), + reinterpret_cast(&data) + sizeof(uint128)}; + } + case STI_UINT160: { + if (funcParam.getInnerSType() != STI_UINT160) + return Unexpected(HostFunctionError::INVALID_PARAMS); + uint160 data = funcParam.getFieldH160(); + return Bytes{data.begin(), data.end()}; + } + case STI_UINT192: { + if (funcParam.getInnerSType() != STI_UINT192) + return Unexpected(HostFunctionError::INVALID_PARAMS); + uint192 data = funcParam.getFieldH192(); + return Bytes{data.begin(), data.end()}; + } + case STI_UINT256: { + if (funcParam.getInnerSType() != STI_UINT256) + return Unexpected(HostFunctionError::INVALID_PARAMS); + uint256 data = funcParam.getFieldH256(); + return Bytes{data.begin(), data.end()}; + } + case STI_VL: { + if (funcParam.getInnerSType() != STI_VL) + return Unexpected(HostFunctionError::INVALID_PARAMS); + auto data = funcParam.getFieldVL(); + return Bytes{data.begin(), data.end()}; + } + case STI_ACCOUNT: { + if (funcParam.getInnerSType() != STI_ACCOUNT) + return Unexpected(HostFunctionError::INVALID_PARAMS); + AccountID data = funcParam.getAccountID(); + return Bytes{data.data(), data.data() + data.size()}; + } + case STI_AMOUNT: { + if (funcParam.getInnerSType() != STI_AMOUNT) + return Unexpected(HostFunctionError::INVALID_PARAMS); + STAmount const data = funcParam.getFieldAmount(); + Serializer s; + data.add(s); + auto const& serialized = s.getData(); + return Bytes{serialized.begin(), serialized.end()}; + } + case STI_NUMBER: { + if (funcParam.getInnerSType() != STI_NUMBER) + return Unexpected(HostFunctionError::INVALID_PARAMS); + STNumber const data = funcParam.getFieldNumber(); + Serializer s; + data.add(s); + auto const& serialized = s.getData(); + return Bytes{serialized.begin(), serialized.end()}; + } + case STI_ISSUE: { + if (funcParam.getInnerSType() != STI_ISSUE) + return Unexpected(HostFunctionError::INVALID_PARAMS); + STIssue const data = funcParam.getFieldIssue(); + Serializer s; + data.add(s); + auto const& serialized = s.getData(); + return Bytes{serialized.begin(), serialized.end()}; + } + case STI_CURRENCY: { + if (funcParam.getInnerSType() != STI_CURRENCY) + return Unexpected(HostFunctionError::INVALID_PARAMS); + STCurrency const data = funcParam.getFieldCurrency(); + Serializer s; + data.add(s); + auto const& serialized = s.getData(); + return Bytes{serialized.begin(), serialized.end()}; + } + case STI_PATHSET: + case STI_VECTOR256: + case STI_XCHAIN_BRIDGE: + case STI_DATA: + case STI_DATATYPE: + case STI_JSON: + default: + return Unexpected(HostFunctionError::INVALID_PARAMS); + } + return Unexpected(HostFunctionError::INVALID_PARAMS); +} + +Expected +ContractHostFunctionsImpl::instanceParam(std::uint32_t index, std::uint32_t stTypeId) +{ + auto j = getJournal(); + auto const& instanceParams = contractCtx.instanceParameters; + + if (instanceParams.size() <= index) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: " << "instanceParam: Index out of bounds"; + return Unexpected(HostFunctionError::INDEX_OUT_OF_BOUNDS); + } + + xrpl::STData const& instParam = instanceParams[index].value; + return getFieldBytesFromSTData(instParam, stTypeId); +} + +Expected +ContractHostFunctionsImpl::functionParam(std::uint32_t index, std::uint32_t stTypeId) +{ + auto j = getJournal(); + auto const& funcParams = contractCtx.functionParameters; + + if (funcParams.size() <= index) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: " << "functionParam: Index out of bounds"; + return Unexpected(HostFunctionError::INDEX_OUT_OF_BOUNDS); + } + + xrpl::STData const& funcParam = funcParams[index].value; + return getFieldBytesFromSTData(funcParam, stTypeId); +} + +inline std::optional const>> +getDataCache(ContractContext& contractCtx, xrpl::AccountID const& account) +{ + auto& dataMap = contractCtx.result.dataMap; + if (dataMap.find(account) == dataMap.end()) + return std::nullopt; + + auto const& ret = dataMap[account]; + return std::cref(ret); +} + +inline std::pair +getDataOrCache(ContractContext& contractCtx, AccountID const& account) +{ + auto cacheEntryLookup = getDataCache(contractCtx, account); + if (!cacheEntryLookup) + { + AccountID const& contractAccount = contractCtx.result.contractAccount; + auto const dataKeylet = keylet::contractData(account, contractAccount); + auto& view = contractCtx.applyCtx.view(); + auto const dataSle = view.read(dataKeylet); + if (dataSle) + { + // Return the STJson from the SLE + STJson const data = dataSle->getFieldJson(sfContractJson); + return {data.isObject(), data}; + } + + // Return New STJson if not found + STJson const data; + return {true, data}; + } + + // Return the cached STJson + auto const& cacheEntry = cacheEntryLookup->get(); + return {cacheEntry.second.isObject(), cacheEntry.second}; +} + +inline HostFunctionError +setDataCache( + ContractContext& contractCtx, + AccountID const& account, + STJson const& data, + beast::Journal const& j, + bool modified = true) +{ + auto& dataMap = contractCtx.result.dataMap; + auto& view = contractCtx.applyCtx.view(); + auto const contractId = contractCtx.result.contractKeylet.key; + + auto const sleAccount = view.read(keylet::account(account)); + if (!sleAccount) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " << "setDataCache: Account not found"; + return HostFunctionError::INVALID_ACCOUNT; + } + + uint32_t const maxDataModifications = 1000u; + + if (modified && dataMap.modifiedCount >= maxDataModifications) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataCache: Exceeded max data modifications"; + return HostFunctionError::INTERNAL; + } + + if (dataMap.find(account) == dataMap.end()) + { + auto const& fees = contractCtx.applyCtx.view().fees(); + STAmount const bal = sleAccount->getFieldAmount(sfBalance); + + int64_t availableForReserves = + bal.xrp().drops() - fees.accountReserve(sleAccount->getFieldU32(sfOwnerCount)).drops(); + int64_t increment = fees.increment.drops(); + if (increment <= 0) + increment = 1; + + availableForReserves /= increment; + + if (availableForReserves < 1 && modified) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: " << "setDataCache: Insufficient reserve"; + return HostFunctionError::INTERNAL; + } + + dataMap.modifiedCount++; + dataMap[account] = {modified, data}; + + // for (auto const& [acct, entry] : dataMap) + // { + // JLOG(j.trace()) + // << "Account: " << to_string(acct) + // << ", Modified: " << entry.first << ", Data: " + // << entry.second.getJson(JsonOptions::none).toStyledString(); + // } + + return HostFunctionError::SUCCESS; + } + + // auto& availableForReserves = std::get<0>(dataMap[account]); + // bool const canReserveNew = availableForReserves > 0; + if (modified) + { + // if (!canReserveNew) + // return HostFunctionError::INSUFFICIENT_RESERVE; + + // availableForReserves--; + dataMap.modifiedCount++; + } + + dataMap[account] = {modified, data}; + // for (auto const& [acct, entry] : dataMap) + // { + // JLOG(j.trace()) + // << "Account: " << to_string(acct) << ", Modified: " << + // entry.first + // << ", Data: " + // << entry.second.getJson(JsonOptions::none).toStyledString(); + // } + return HostFunctionError::SUCCESS; +} + +Expected +ContractHostFunctionsImpl::getDataObjectField(AccountID const& account, std::string_view const& key) +{ + auto j = getJournal(); + auto& view = contractCtx.applyCtx.view(); + AccountID const& contractAccount = contractCtx.result.contractAccount; + try + { + auto const sleAccount = view.read(keylet::account(account)); + if (!sleAccount) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: " << "getDataObjectField: Account not found"; + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + } + + // first check if the requested state was previously cached this session + auto cacheEntryLookup = getDataCache(contractCtx, account); + if (cacheEntryLookup) + { + auto const& cacheEntry = cacheEntryLookup->get(); + STJson const data = cacheEntry.second; + auto const keyValue = data.getObjectField(std::string(key)); + if (!keyValue) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: " << "getDataObjectField: Invalid field"; + return Unexpected(HostFunctionError::INVALID_FIELD); + } + + Serializer s; + keyValue.value()->add(s); + return Bytes{s.peekData().data(), s.peekData().data() + s.peekData().size()}; + } + + auto const dataKeylet = keylet::contractData(account, contractAccount); + auto const dataSle = view.read(dataKeylet); + if (!dataSle) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: " << "getDataObjectField: Data SLE not found"; + return Unexpected(HostFunctionError::INTERNAL); + } + + STJson const data = dataSle->getFieldJson(sfContractJson); + // it exists add it to cache and return it + if (setDataCache(contractCtx, account, data, j, false) != HostFunctionError::SUCCESS) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataObjectField: Failed to set data cache"; + return Unexpected(HostFunctionError::INTERNAL); + } + + auto const keyValue = data.getObjectField(std::string(key)); + if (!keyValue) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: " << "getDataObjectField: Invalid field"; + return Unexpected(HostFunctionError::INVALID_FIELD); + } + + Serializer s; + keyValue.value()->add(s); + return Bytes{s.peekData().data(), s.peekData().data() + s.peekData().size()}; + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataObjectField: Exception: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::getDataNestedObjectField( + AccountID const& account, + std::string_view const& key, + std::string_view const& nestedKey) +{ + auto j = getJournal(); + auto& view = contractCtx.applyCtx.view(); + AccountID const& contractAccount = contractCtx.result.contractAccount; + try + { + auto const sleAccount = view.read(keylet::account(account)); + if (!sleAccount) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedObjectField: Account not found"; + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + } + + // first check if the requested state was previously cached this session + auto cacheEntryLookup = getDataCache(contractCtx, account); + if (cacheEntryLookup) + { + auto const& cacheEntry = cacheEntryLookup->get(); + STJson const data = cacheEntry.second; + auto const keyValue = + data.getNestedObjectField(std::string(key), std::string(nestedKey)); + if (!keyValue) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: " << "getDataNestedObjectField: Invalid field"; + return Unexpected(HostFunctionError::INVALID_FIELD); + } + + Serializer s; + keyValue.value()->add(s); + return Bytes{s.peekData().data(), s.peekData().data() + s.peekData().size()}; + } + + auto const dataKeylet = keylet::contractData(account, contractAccount); + auto const dataSle = view.read(dataKeylet); + if (!dataSle) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedObjectField: Data SLE not found"; + return Unexpected(HostFunctionError::INTERNAL); + } + + STJson const data = dataSle->getFieldJson(sfContractJson); + // it exists add it to cache and return it + if (setDataCache(contractCtx, account, data, j, false) != HostFunctionError::SUCCESS) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedObjectField: Failed to set data cache"; + return Unexpected(HostFunctionError::INTERNAL); + } + + auto const keyValue = data.getNestedObjectField(std::string(key), std::string(nestedKey)); + if (!keyValue) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: " << "getDataNestedObjectField: Invalid field"; + return Unexpected(HostFunctionError::INVALID_FIELD); + } + + Serializer s; + keyValue.value()->add(s); + return Bytes{s.peekData().data(), s.peekData().data() + s.peekData().size()}; + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedObjectField: Exception: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::setDataObjectField( + AccountID const& account, + std::string_view const& key, + STJson::Value const& value) +{ + auto j = getJournal(); + try + { + auto [isObject, data] = getDataOrCache(contractCtx, account); + if (!isObject) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataObjectField: Invalid state: not an object"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + data.setObjectField(std::string(key), value); + if (HostFunctionError const ret = setDataCache(contractCtx, account, data, j, true); + ret != HostFunctionError::SUCCESS) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataObjectField: Failed to set object field"; + return Unexpected(ret); + } + + return static_cast(HostFunctionError::SUCCESS); + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataObjectField: Exception: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::setDataNestedObjectField( + AccountID const& account, + std::string_view const& key, + std::string_view const& nestedKey, + STJson::Value const& value) +{ + auto j = getJournal(); + try + { + auto [isObject, data] = getDataOrCache(contractCtx, account); + if (!isObject) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataNestedObjectField: Invalid state: not an object"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + data.setNestedObjectField(std::string(key), std::string(nestedKey), value); + if (HostFunctionError const ret = setDataCache(contractCtx, account, data, j, true); + ret != HostFunctionError::SUCCESS) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataNestedObjectField: Failed to set nested " + "object field"; + return Unexpected(ret); + } + + return static_cast(HostFunctionError::SUCCESS); + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataNestedObjectField: Exception: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::getDataArrayElementField( + AccountID const& account, + size_t index, + std::string_view const& key) +{ + auto j = getJournal(); + auto& view = contractCtx.applyCtx.view(); + AccountID const& contractAccount = contractCtx.result.contractAccount; + try + { + auto const sleAccount = view.read(keylet::account(account)); + if (!sleAccount) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataArrayElementField: Account not found"; + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + } + + // first check if the requested state was previously cached this session + auto cacheEntryLookup = getDataCache(contractCtx, account); + if (cacheEntryLookup) + { + auto const& cacheEntry = cacheEntryLookup->get(); + STJson const data = cacheEntry.second; + + if (!data.isArray()) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataArrayElementField: Invalid state: not an array"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + auto const fieldValue = data.getArrayElementField(index, std::string(key)); + if (!fieldValue) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataArrayElementField: Failed to get array " + "element field"; + return Unexpected(HostFunctionError::INVALID_FIELD); + } + + Serializer s; + fieldValue.value()->add(s); + return Bytes{s.peekData().data(), s.peekData().data() + s.peekData().size()}; + } + + auto const dataKeylet = keylet::contractData(account, contractAccount); + auto const dataSle = view.read(dataKeylet); + if (!dataSle) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataArrayElementField: Failed to read contract data"; + return Unexpected(HostFunctionError::INTERNAL); + } + + STJson const data = dataSle->getFieldJson(sfContractJson); + + if (!data.isArray()) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataArrayElementField: Invalid state: not an array"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + // it exists add it to cache and return it + if (setDataCache(contractCtx, account, data, j, false) != HostFunctionError::SUCCESS) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataArrayElementField: Failed to set array " + "element field"; + return Unexpected(HostFunctionError::INTERNAL); + } + + auto const fieldValue = data.getArrayElementField(index, std::string(key)); + if (!fieldValue) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataArrayElementField: Failed to get array " + "element field"; + return Unexpected(HostFunctionError::INVALID_FIELD); + } + + Serializer s; + fieldValue.value()->add(s); + return Bytes{s.peekData().data(), s.peekData().data() + s.peekData().size()}; + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataArrayElementField: Exception: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::getDataNestedArrayElementField( + AccountID const& account, + std::string_view const& key, + size_t index, + std::string_view const& nestedKey) +{ + auto j = getJournal(); + auto& view = contractCtx.applyCtx.view(); + AccountID const& contractAccount = contractCtx.result.contractAccount; + try + { + auto const sleAccount = view.read(keylet::account(account)); + if (!sleAccount) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedArrayElementField: Account not found"; + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + } + + // if (account != contractCtx.result.otxnAccount) + // { + // JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + // << "getDataNestedArrayElementField: Unauthorized + // access to account data"; + // return Unexpected(HostFunctionError::INVALID_ACCOUNT); + // } + + // first check if the requested state was previously cached this session + auto cacheEntryLookup = getDataCache(contractCtx, account); + if (cacheEntryLookup) + { + auto const& cacheEntry = cacheEntryLookup->get(); + STJson const data = cacheEntry.second; + + if (!data.isObject()) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedArrayElementField: Invalid state: " + "not an object"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + auto const fieldValue = + data.getNestedArrayElementField(std::string(key), index, std::string(nestedKey)); + if (!fieldValue) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedArrayElementField: Failed to get " + "nested array element field"; + return Unexpected(HostFunctionError::INVALID_FIELD); + } + + Serializer s; + fieldValue.value()->add(s); + return Bytes{s.peekData().data(), s.peekData().data() + s.peekData().size()}; + } + + auto const dataKeylet = keylet::contractData(account, contractAccount); + auto const dataSle = view.read(dataKeylet); + if (!dataSle) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedArrayElementField: Failed to read " + "contract data"; + return Unexpected(HostFunctionError::INTERNAL); + } + + STJson const data = dataSle->getFieldJson(sfContractJson); + + if (!data.isObject()) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedArrayElementField: Invalid state: " + "not an object"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + // it exists add it to cache and return it + if (setDataCache(contractCtx, account, data, j, false) != HostFunctionError::SUCCESS) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataNestedArrayElementField: Failed to set " + "nested array element field"; + return Unexpected(HostFunctionError::INTERNAL); + } + + auto const fieldValue = + data.getNestedArrayElementField(std::string(key), index, std::string(nestedKey)); + if (!fieldValue) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedArrayElementField: Failed to get " + "nested array element field"; + return Unexpected(HostFunctionError::INVALID_FIELD); + } + + Serializer s; + fieldValue.value()->add(s); + return Bytes{s.peekData().data(), s.peekData().data() + s.peekData().size()}; + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "getDataNestedArrayElementField: Exception: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::setDataArrayElementField( + AccountID const& account, + size_t index, + std::string_view const& key, + STJson::Value const& value) +{ + auto j = getJournal(); + auto [isObject, data] = getDataOrCache(contractCtx, account); + + try + { + // For array operations, we expect isObject to be false (indicating it's + // an array) But getDataOrCache returns isObject=true for new data, so + // we need to check the actual type + if (isObject && data.getMap().size() > 0) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataArrayElementField: Invalid state: not an array"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + // If it's a new empty object, convert it to an array + if (isObject && data.getMap().empty()) + { + data = STJson(STJson::Array{}); + } + + if (!data.isArray()) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataArrayElementField: Invalid state: not an array"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + data.setArrayElementField(index, std::string(key), value); + if (HostFunctionError const ret = setDataCache(contractCtx, account, data, j, true); + ret != HostFunctionError::SUCCESS) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataArrayElementField: Failed to set array " + "element field"; + return Unexpected(ret); + } + + return static_cast(HostFunctionError::SUCCESS); + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataArrayElementField: Exception: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::setDataNestedArrayElementField( + AccountID const& account, + std::string_view const& key, + size_t index, + std::string_view const& nestedKey, + STJson::Value const& value) +{ + auto j = getJournal(); + try + { + auto [isObject, data] = getDataOrCache(contractCtx, account); + if (!isObject) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataNestedArrayElementField: Invalid state: " + "not an object"; + return Unexpected(HostFunctionError::INVALID_STATE); + } + + data.setNestedArrayElementField(std::string(key), index, std::string(nestedKey), value); + if (HostFunctionError const ret = setDataCache(contractCtx, account, data, j, true); + ret != HostFunctionError::SUCCESS) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataNestedArrayElementField: Failed to set " + "nested array element field"; + return Unexpected(ret); + } + + return static_cast(HostFunctionError::SUCCESS); + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "setDataNestedArrayElementField: Exception: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::buildTxn(std::uint16_t const& txType) +{ + auto j = getJournal(); + auto& app = contractCtx.applyCtx.registry; + + if (!Emitable::getInstance().isEmitable(txType)) + { + JLOG(j.trace()) << "Transaction type: " << txType << " is not emitable."; + return Unexpected(HostFunctionError::SUBMIT_TXN_FAILURE); + } + + try + { + auto jv = Json::Value(Json::objectValue); + auto item = TxFormats::getInstance().findByType(safe_cast(txType)); + jv[sfTransactionType] = item->getName(); + jv[sfFee] = "0"; + jv[sfFlags] = 1073741824; + jv[sfSequence] = contractCtx.result.nextSequence; + jv[sfAccount] = to_string(contractCtx.result.contractAccount); + jv[sfSigningPubKey] = ""; + if (auto const networkID = app.get().getNetworkIDService().getNetworkID(); networkID != 0) + jv[sfNetworkID] = networkID; + + STParsedJSONObject parsed("txn", jv); + contractCtx.built_txns.push_back(*parsed.object); + contractCtx.result.nextSequence += 1; + return contractCtx.built_txns.size() - 1; + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: Exception in buildTxn: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::addTxnField( + std::uint32_t const& index, + SField const& field, + Slice const& data) +{ + auto j = getJournal(); + try + { + if (index >= contractCtx.built_txns.size()) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "addTxnField: index out of bounds: " << index; + return Unexpected(HostFunctionError::INDEX_OUT_OF_BOUNDS); + } + + // Get the transaction STObject + auto& obj = contractCtx.built_txns[index]; + + // Ensure the transaction has a TransactionType field + if (!obj.isFieldPresent(sfTransactionType)) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "addTxnField: TransactionType field not present " + "in transaction."; + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + } + + // Extract the numeric tx type from the STObject and convert to TxType + auto txTypeVal = obj.getFieldU16(sfTransactionType); + auto txFormat = TxFormats::getInstance().findByType(safe_cast(txTypeVal)); + if (!txFormat) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " + << "addTxnField: Invalid TransactionType: " << txTypeVal; + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + } + + // Check if the provided field is allowed for this transaction type + bool found = false; + for (auto const& e : txFormat->getSOTemplate()) + { + if (e.sField().getName() == field.getName()) + { + found = true; + break; + } + } + if (!found) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " << "addTxnField: Field " + << field.getName() << " not allowed in transaction type " + << txFormat->getName(); + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + } + + obj.addFieldFromSlice(field, data); + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: " << "addTxnField: TXN: " + << obj.getJson(JsonOptions::none).toStyledString(); + return static_cast(HostFunctionError::SUCCESS); + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId + << "]: Exception in addTxnField: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::emitBuiltTxn(std::uint32_t const& index) +{ + auto j = getJournal(); + auto& app = contractCtx.applyCtx.registry; + auto& parentTx = contractCtx.applyCtx.tx; + auto const parentBatchId = parentTx.getTransactionID(); + try + { + if (index >= contractCtx.built_txns.size()) + { + JLOG(j.trace()) << "WasmTrace[" << parentBatchId + << "]: " << "emitBuiltTxn: index out of bounds: " << index; + return Unexpected(HostFunctionError::INDEX_OUT_OF_BOUNDS); + } + + // Ensure tfInnerBatchTxn is always set, even if the contract + // overwrote sfFlags via addTxnField. + contractCtx.built_txns[index].setFlag(tfInnerBatchTxn); + auto stxPtr = std::make_shared(std::move(contractCtx.built_txns[index])); + + std::string reason; + auto tpTrans = std::make_shared(stxPtr, reason, app.get().getApp()); + if (tpTrans->getStatus() != NEW) + { + JLOG(j.trace()) << "WasmTrace[" << parentBatchId << "]: " + << "emitBuiltTxn: Failed to decode transaction: " << reason; + return Unexpected(HostFunctionError::SUBMIT_TXN_FAILURE); + } + + // Use a persistent emit view that is seeded with the + // transactor's pending state changes (balances, consumed + // sequence, etc.) so that each emitted transaction validates + // against the full current state. A full apply() is used + // (matching the Batch inner-transaction pattern) so that + // sequence numbers, balances, owner counts, and all other + // ledger state are properly updated between successive emits. + auto& emitView = contractCtx.getEmitView(); + auto const stx = tpTrans->getSTransaction(); + + OpenView perTxView(batch_view, emitView); + auto const ret = apply(app, perTxView, parentBatchId, *stx, tapBATCH, j); + + JLOG(j.trace()) << "WasmTrace[" << parentBatchId << "]: " << stx->getTransactionID() << " " + << transToken(ret.ter); + + if (ret.applied && (isTesSuccess(ret.ter) || isTecClaim(ret.ter))) + { + perTxView.apply(emitView); + contractCtx.result.emittedTxns.push(stx); + } + return TERtoInt(ret.ter); + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << parentBatchId + << "]: Exception in emitBuiltTxn: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::emitTxn(std::shared_ptr const& stxPtr) +{ + auto& app = contractCtx.applyCtx.registry; + auto& parentTx = contractCtx.applyCtx.tx; + auto j = getJournal(); + + try + { + // Ensure tfInnerBatchTxn is always set on emitted transactions. + // Since STTx is const, create a mutable copy if the flag is missing. + std::shared_ptr txPtr = stxPtr; + if (!stxPtr->isFlag(tfInnerBatchTxn)) + { + STObject obj(static_cast(*stxPtr)); + obj.setFlag(tfInnerBatchTxn); + txPtr = std::make_shared(std::move(obj)); + } + + std::string reason; + auto tpTrans = std::make_shared(txPtr, reason, app.get().getApp()); + if (tpTrans->getStatus() != NEW) + return Unexpected(HostFunctionError::SUBMIT_TXN_FAILURE); + + // Use a persistent emit view seeded with the transactor's + // pending state, and do a full apply() for each emission + // (see emitBuiltTxn for detailed rationale). + auto& emitView = contractCtx.getEmitView(); + auto const parentBatchId = parentTx.getTransactionID(); + auto const stx = tpTrans->getSTransaction(); + + OpenView perTxView(batch_view, emitView); + auto const ret = apply(app, perTxView, parentBatchId, *stx, tapBATCH, j); + + JLOG(j.trace()) << "WasmTrace[" << parentBatchId << "]: " << stx->getTransactionID() << " " + << transToken(ret.ter); + + if (ret.applied && (isTesSuccess(ret.ter) || isTecClaim(ret.ter))) + { + perTxView.apply(emitView); + contractCtx.result.emittedTxns.push(stx); + } + return TERtoInt(ret.ter); + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << parentTx.getTransactionID() + << "]: Exception in emitTxn: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +Expected +ContractHostFunctionsImpl::emitEvent(std::string_view const& eventName, STJson const& eventData) +{ + auto j = getJournal(); + + try + { + // TODO: Validation + auto& eventMap = contractCtx.result.eventMap; + eventMap[std::string(eventName)] = eventData; + return static_cast(HostFunctionError::SUCCESS); + } + catch (std::exception const& e) + { + JLOG(j.trace()) << "WasmTrace[" << contractId << "]: Exception in emitEvent: " << e.what(); + return Unexpected(HostFunctionError::INTERNAL); + } +} + +} // namespace xrpl diff --git a/src/xrpld/core/Config.h b/src/xrpld/core/Config.h index a18b68a5084..9ec612b3322 100644 --- a/src/xrpld/core/Config.h +++ b/src/xrpld/core/Config.h @@ -56,14 +56,34 @@ struct FeeSetup /** The per-owned item reserve requirement in drops. */ XRPAmount ownerReserve{2 * kDropsPerXrp}; + /** The compute limit for Feature Extensions. */ + std::uint32_t extension_compute_limit{1'000'000}; + + /** The WASM size limit for Feature Extensions. */ + std::uint32_t extension_size_limit{100'000}; + + /** The price of 1 WASM gas, in micro-drops. */ + std::uint32_t gas_price{1'000'000}; + /* (Remember to update the example cfg files when changing any of these * values.) */ - /** Convert to a Fees object for use with Ledger construction. */ + /** Convert to a Fees object for use with Ledger construction. + Extension fees (extensionComputeLimit, extensionSizeLimit, gasPrice) + are intentionally NOT set here. They start at 0 and are activated + through fee voting on the first flag ledger, or via the genesis + ledger SLE when featureSmartEscrow is in the initial amendments. */ [[nodiscard]] Fees toFees() const { - return Fees{referenceFee, accountReserve, ownerReserve}; + Fees f; + f.base = referenceFee; + f.reserve = accountReserve; + f.increment = ownerReserve; + f.extensionComputeLimit = 0; + f.extensionSizeLimit = 0; + f.gasPrice = 0; + return f; } }; diff --git a/src/xrpld/core/detail/Config.cpp b/src/xrpld/core/detail/Config.cpp index 6eedc43edd9..d6a779b1687 100644 --- a/src/xrpld/core/detail/Config.cpp +++ b/src/xrpld/core/detail/Config.cpp @@ -1192,6 +1192,12 @@ setupFeeVote(Section const& section) setup.accountReserve = temp; if (set(temp, "owner_reserve", section)) setup.ownerReserve = temp; + if (set(temp, "extension_compute_limit", section)) + setup.extension_compute_limit = temp; + if (set(temp, "extension_size_limit", section)) + setup.extension_size_limit = temp; + if (set(temp, "gas_price", section)) + setup.gas_price = temp; } return setup; } diff --git a/src/xrpld/rpc/detail/Handler.cpp b/src/xrpld/rpc/detail/Handler.cpp index 23eb8fdeecd..67caf5de199 100644 --- a/src/xrpld/rpc/detail/Handler.cpp +++ b/src/xrpld/rpc/detail/Handler.cpp @@ -146,6 +146,10 @@ Handler const kHandlerArray[]{ .valueMethod = byRef(&doConsensusInfo), .role = Role::ADMIN, .condition = Condition::NoCondition}, + {.name = "contract_info", + .valueMethod = byRef(&doContractInfo), + .role = Role::USER, + .condition = Condition::NoCondition}, {.name = "deposit_authorized", .valueMethod = byRef(&doDepositAuthorized), .role = Role::USER, diff --git a/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp b/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp index 4d0d10a66b5..32740d3c5da 100644 --- a/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp +++ b/src/xrpld/rpc/detail/RPCLedgerHelpers.cpp @@ -492,4 +492,40 @@ getOrAcquireLedger(RPC::JsonContext const& context) return Unexpected(RPC::makeError(RpcNotReady, "findCreate failed to return an inbound ledger")); } +/** + * @brief Injects JSON describing a ledger entry. + * + * @param jv The JSON value to populate. + * @param sle The ledger entry to describe. + * + * @details + * Populates the provided JSON value with the description of the specified + * ledger entry. If the entry is an account root and contains an email hash, + * adds a 'urlgravatar' field with the corresponding Gravatar URL. + * If the entry is not an account root, sets the 'Invalid' field to true. + */ +void +injectSLE(Json::Value& jv, SLE const& sle) +{ + jv = sle.getJson(JsonOptions::none); + if (sle.getType() == ltACCOUNT_ROOT) + { + if (sle.isFieldPresent(sfEmailHash)) + { + auto const& hash = sle.getFieldH128(sfEmailHash); + Blob const b(hash.begin(), hash.end()); + std::string md5 = strHex(makeSlice(b)); + boost::to_lower(md5); + // VFALCO TODO Give a name and move this constant + // to a more visible location. Also + // shouldn't this be https? + jv[jss::urlgravatar] = str(boost::format("http://www.gravatar.com/avatar/%s") % md5); + } + } + else + { + jv[jss::Invalid] = true; + } +} + } // namespace xrpl::RPC diff --git a/src/xrpld/rpc/detail/RPCLedgerHelpers.h b/src/xrpld/rpc/detail/RPCLedgerHelpers.h index faec4ae069c..571b41214a9 100644 --- a/src/xrpld/rpc/detail/RPCLedgerHelpers.h +++ b/src/xrpld/rpc/detail/RPCLedgerHelpers.h @@ -170,6 +170,9 @@ ledgerFromSpecifier( Expected, json::Value> getOrAcquireLedger(RPC::JsonContext const& context); +void +injectSLE(Json::Value& jv, SLE const& sle); + } // namespace RPC } // namespace xrpl diff --git a/src/xrpld/rpc/handlers/ContractInfo.cpp b/src/xrpld/rpc/handlers/ContractInfo.cpp new file mode 100644 index 00000000000..9cb3f4efb04 --- /dev/null +++ b/src/xrpld/rpc/handlers/ContractInfo.cpp @@ -0,0 +1,162 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace xrpl { + +// { +// contract_account: , +// function : // optional +// user_account : +// ledger_index : +// } + +Json::Value +doContractInfo(RPC::JsonContext& context) +{ + auto& params = context.params; + + std::string contractAccount; + if (params.isMember(jss::contract_account)) + { + if (!params[jss::contract_account].isString()) + return RPC::invalid_field_error(jss::contract_account); + contractAccount = params[jss::contract_account].asString(); + } + else + return RPC::missing_field_error(jss::contract_account); + + std::string functionName; + if (params.isMember(jss::function)) + { + if (!params[jss::function].isString()) + return RPC::invalid_field_error(jss::function); + functionName = params[jss::function].asString(); + } + + std::string account; + if (params.isMember(jss::account)) + { + if (!params[jss::account].isString()) + return RPC::invalid_field_error(jss::account); + account = params[jss::account].asString(); + } + + std::shared_ptr ledger; + auto result = RPC::lookupLedger(ledger, context); + + if (!ledger) + return result; + + // contract account + auto caid = parseBase58(contractAccount); + if (!caid) + { + RPC::inject_error(rpcACT_MALFORMED, result); + return result; + } + auto const caID{caid.value()}; + auto const caSle = ledger->read(keylet::account(caID)); + if (!caSle) + { + result[jss::contract_account] = toBase58(caID); + RPC::inject_error(rpcACT_NOT_FOUND, result); + } + + uint256 const contractID = caSle->getFieldH256(sfContractID); + auto const contractSle = ledger->read(keylet::contract(contractID)); + if (!contractSle) + { + result[jss::contract_account] = toBase58(caID); + RPC::inject_error(rpcOBJECT_NOT_FOUND, result); + } + + // contract source + if (!contractSle->at(sfContractHash)) + { + result[jss::contract_account] = toBase58(caID); + RPC::inject_error(rpcUNKNOWN, result); + } + + auto const sourceSle = ledger->read(keylet::contractSource(contractSle->at(sfContractHash))); + if (!sourceSle) + { + result[jss::contract_account] = toBase58(caID); + RPC::inject_error(rpcOBJECT_NOT_FOUND, result); + } + + result[jss::contract_account] = toBase58(caID); + result[jss::code] = strHex(sourceSle->at(sfContractCode)); + result[jss::hash] = to_string(sourceSle->at(sfContractHash)); + + // lambda to format the functions response: + // name: + // params: [: , : , : ] + auto formatFunctions = [](Json::Value& jv, std::shared_ptr const& slePtr) { + if (slePtr && slePtr->isFieldPresent(sfFunctions)) + { + auto const& functions = slePtr->getFieldArray(sfFunctions); + for (auto const& function : functions) + { + Json::Value jvFunction(Json::objectValue); + jvFunction[jss::name] = strHex(function.getFieldVL(sfFunctionName)); + Json::Value jvParams(Json::arrayValue); + for (auto const& param : function.getFieldArray(sfParameters)) + { + Json::Value jvParam(Json::objectValue); + jvParam[jss::flags] = param.getFieldU32(sfParameterFlag); + jvParam[jss::type] = + param.getFieldDataType(sfParameterType).getInnerTypeString(); + jvParams.append(jvParam); + } + jvFunction[jss::params] = std::move(jvParams); + jv.append(std::move(jvFunction)); + } + } + }; + if (sourceSle->isFieldPresent(sfFunctions)) + formatFunctions(result[jss::functions], sourceSle); + if (contractSle->isFieldPresent(sfURI)) + result[jss::source_code_uri] = strHex(contractSle->at(sfURI)); + + Json::Value jvAccepted(Json::objectValue); + RPC::injectSLE(jvAccepted, *caSle); + result[jss::account_data] = jvAccepted; + + auto const dataSle = ledger->read(keylet::contractData(caID, caID)); + if (dataSle) + result[jss::contract_data] = + dataSle->getFieldJson(sfContractJson).getJson(JsonOptions::none); + + if (!account.empty()) + { + auto id = parseBase58(account); + if (!id) + { + RPC::inject_error(rpcACT_MALFORMED, result); + return result; + } + auto const accountID = id.value(); + if (ledger->exists(keylet::account(accountID))) + { + if (auto dataSle = ledger->read(keylet::contractData(accountID, caID))) + result[jss::user_data] = + dataSle->getFieldJson(sfContractJson).getJson(JsonOptions::none); + } + } + + return result; +} + +} // namespace xrpl diff --git a/src/xrpld/rpc/handlers/Handlers.h b/src/xrpld/rpc/handlers/Handlers.h index 2d39e42e022..fca3312a5c3 100644 --- a/src/xrpld/rpc/handlers/Handlers.h +++ b/src/xrpld/rpc/handlers/Handlers.h @@ -39,6 +39,8 @@ doConnect(RPC::JsonContext&); json::Value doConsensusInfo(RPC::JsonContext&); json::Value +doContractInfo(RPC::JsonContext&); +json::Value doDepositAuthorized(RPC::JsonContext&); json::Value doFeature(RPC::JsonContext&); diff --git a/src/xrpld/rpc/handlers/account/AccountNFTs.cpp b/src/xrpld/rpc/handlers/account/AccountNFTs.cpp index 0eb91206f39..7b9ce8a75bf 100644 --- a/src/xrpld/rpc/handlers/account/AccountNFTs.cpp +++ b/src/xrpld/rpc/handlers/account/AccountNFTs.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include diff --git a/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp b/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp index 9a9119d2baf..fb76bcbace8 100644 --- a/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp +++ b/src/xrpld/rpc/handlers/ledger/LedgerEntry.cpp @@ -818,6 +818,72 @@ parseXChainOwnedCreateAccountClaimID( return keylet.key; } +static Expected +parseContractSource( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) +{ + if (!params.isObject()) + { + return parseObjectID(params, fieldName); + } + + auto const id = LedgerEntryHelpers::requiredAccountID(params, jss::owner, "malformedOwner"); + if (!id) + return Unexpected(id.error()); + + auto const seq = LedgerEntryHelpers::requiredUInt32(params, jss::seq, "malformedRequest"); + if (!seq) + return Unexpected(seq.error()); + + return keylet::vault(*id, *seq).key; +} + +static Expected +parseContract( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) +{ + if (!params.isObject()) + { + return parseObjectID(params, fieldName); + } + + auto const id = LedgerEntryHelpers::requiredAccountID(params, jss::owner, "malformedOwner"); + if (!id) + return Unexpected(id.error()); + + auto const seq = LedgerEntryHelpers::requiredUInt32(params, jss::seq, "malformedRequest"); + if (!seq) + return Unexpected(seq.error()); + + return keylet::vault(*id, *seq).key; +} + +static Expected +parseContractData( + Json::Value const& params, + Json::StaticString const fieldName, + [[maybe_unused]] unsigned const apiVersion) +{ + if (!params.isObject()) + { + return parseObjectID(params, fieldName); + } + + auto const id = LedgerEntryHelpers::requiredAccountID(params, jss::owner, "malformedOwner"); + if (!id) + return Unexpected(id.error()); + + auto const seq = LedgerEntryHelpers::requiredUInt32(params, jss::seq, "malformedRequest"); + if (!seq) + return Unexpected(seq.error()); + + return keylet::vault(*id, *seq).key; +} + struct LedgerEntry { json::StaticString fieldName; diff --git a/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp b/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp index aa23a7af260..5c5de0423ab 100644 --- a/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp +++ b/src/xrpld/rpc/handlers/server_info/ServerDefinitions.cpp @@ -87,6 +87,7 @@ ServerDefinitions::translate(std::string const& inp) {"PATHSET", "PathSet"}, {"VL", "Blob"}, {"XCHAIN_BRIDGE", "XChainBridge"}, + {"DATATYPE", "DataType"}, }; if (auto const& it = kReplacements.find(inp); it != kReplacements.end()) diff --git a/src/xrpld/rpc/handlers/subscribe/Subscribe.cpp b/src/xrpld/rpc/handlers/subscribe/Subscribe.cpp index 93840bb6d66..215dba25066 100644 --- a/src/xrpld/rpc/handlers/subscribe/Subscribe.cpp +++ b/src/xrpld/rpc/handlers/subscribe/Subscribe.cpp @@ -159,6 +159,10 @@ doSubscribe(RPC::JsonContext& context) { context.netOps.subConsensus(ispSub); } + else if (streamName == "contract_events") + { + context.netOps.subContractEvent(ispSub); + } else { return rpcError(RpcStreamMalformed); diff --git a/src/xrpld/rpc/handlers/subscribe/Unsubscribe.cpp b/src/xrpld/rpc/handlers/subscribe/Unsubscribe.cpp index 36dae615b34..7e2ebd4c4d1 100644 --- a/src/xrpld/rpc/handlers/subscribe/Unsubscribe.cpp +++ b/src/xrpld/rpc/handlers/subscribe/Unsubscribe.cpp @@ -91,6 +91,10 @@ doUnsubscribe(RPC::JsonContext& context) { context.netOps.unsubConsensus(ispSub->getSeq()); } + else if (streamName == "contract_events") + { + context.netOps.unsubContractEvent(ispSub->getSeq()); + } else { return rpcError(RpcStreamMalformed);