diff --git a/src/test/app/HostFuncImpl_test.cpp b/src/test/app/HostFuncImpl_test.cpp index f75ad1aa72..60578c849e 100644 --- a/src/test/app/HostFuncImpl_test.cpp +++ b/src/test/app/HostFuncImpl_test.cpp @@ -1356,8 +1356,7 @@ struct HostFuncImpl_test : public beast::unit_test::suite BEAST_EXPECT(result.has_value() && result.value() == 0); // Should fail for too large data - std::vector bigData( - 1024 * 1024 + 1, 0x42); // > maxWasmDataLength + std::vector bigData(maxWasmDataLength + 1, 0x42); auto const tooBig = hfs.updateData(Slice(bigData.data(), bigData.size())); if (BEAST_EXPECT(!tooBig.has_value())) @@ -2102,6 +2101,7 @@ struct HostFuncImpl_test : public beast::unit_test::suite 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"; @@ -2744,8 +2744,14 @@ struct HostFuncImpl_test : public beast::unit_test::suite } { - auto const result = - hfs.floatPower(makeSlice(floatMaxIOU), 40000, 0); + 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() == @@ -2809,6 +2815,46 @@ struct HostFuncImpl_test : public beast::unit_test::suite 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 diff --git a/src/test/app/TestHostFunctions.h b/src/test/app/TestHostFunctions.h index ffa0bfb895..6973a107fe 100644 --- a/src/test/app/TestHostFunctions.h +++ b/src/test/app/TestHostFunctions.h @@ -605,13 +605,17 @@ public: struct PerfHostFunctions : public TestHostFunctions { Keylet leKey; + std::shared_ptr currentLedgerObj = nullptr; + bool isLedgerObjCached = false; + static int constexpr MAX_CACHE = 256; std::array, MAX_CACHE> cache; + // std::optional data_; // deferred data update, not used in + // performance std::shared_ptr tx_; void const* rt_ = nullptr; -public: PerfHostFunctions( test::jtx::Env& env, Keylet const& k, @@ -620,9 +624,113 @@ public: { } + Expected + getLedgerSqn() override + { + auto seq = env_.current()->seq(); + if (seq > std::numeric_limits::max()) + return Unexpected(HostFunctionError::INTERNAL); // LCOV_EXCL_LINE + return static_cast(seq); + } + + Expected + getParentLedgerTime() override + { + auto time = + env_.current()->parentCloseTime().time_since_epoch().count(); + if (time > std::numeric_limits::max()) + return Unexpected(HostFunctionError::INTERNAL); + return static_cast(time); + } + + Expected + getParentLedgerHash() override + { + return env_.current()->info().parentHash; + } + + Expected + getLedgerAccountHash() override + { + return env_.current()->info().accountHash; + } + + Expected + getLedgerTransactionHash() override + { + return env_.current()->info().txHash; + } + + Expected + getBaseFee() override + { + auto fee = env_.current()->fees().base.drops(); + if (fee > std::numeric_limits::max()) + return Unexpected(HostFunctionError::INTERNAL); + return static_cast(fee); + } + + Expected + isAmendmentEnabled(uint256 const& amendmentId) override + { + return env_.current()->rules().enabled(amendmentId); + } + + Expected + isAmendmentEnabled(std::string_view const& amendmentName) override + { + auto const& table = env_.app().getAmendmentTable(); + auto const amendment = table.find(std::string(amendmentName)); + return env_.current()->rules().enabled(amendment); + } + + Expected, HostFunctionError> + getCurrentLedgerObj() + { + if (!isLedgerObjCached) + { + isLedgerObjCached = true; + currentLedgerObj = env_.le(leKey); + } + if (currentLedgerObj) + return currentLedgerObj; + return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND); + } + + Expected, HostFunctionError> + peekCurrentLedgerObj(int32_t cacheIdx) + { + --cacheIdx; + if (cacheIdx < 0 || cacheIdx >= MAX_CACHE) + return Unexpected(HostFunctionError::SLOT_OUT_RANGE); + + if (!cache[cacheIdx]) + { // return Unexpected(HostFunctionError::INVALID_SLOT); + auto const r = getCurrentLedgerObj(); + if (!r) + return Unexpected(r.error()); + cache[cacheIdx] = *r; + } + + return cache[cacheIdx]; + } + + Expected + normalizeCacheIndex(int32_t cacheIdx) + { + --cacheIdx; + if (cacheIdx < 0 || cacheIdx >= MAX_CACHE) + return Unexpected(HostFunctionError::SLOT_OUT_RANGE); + if (!cache[cacheIdx]) + return Unexpected(HostFunctionError::EMPTY_SLOT); + return cacheIdx; + } + virtual Expected cacheLedgerObj(uint256 const&, int32_t cacheIdx) override { + // auto const& keylet = keylet::unchecked(objId); + static int32_t intIdx = 0; if (cacheIdx < 0 || cacheIdx > MAX_CACHE) @@ -648,158 +756,157 @@ public: return cacheIdx + 1; } - static Bytes - getAnyFieldData(STBase const& obj) + static Expected + getAnyFieldData(STBase const* obj) { // auto const& fname = obj.getFName(); - auto const stype = obj.getSType(); + if (!obj) + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + + auto const stype = obj->getSType(); switch (stype) { + // LCOV_EXCL_START case STI_UNKNOWN: case STI_NOTPRESENT: - return {}; + return Unexpected(HostFunctionError::FIELD_NOT_FOUND); + break; + // LCOV_EXCL_STOP + case STI_OBJECT: + case STI_ARRAY: + return Unexpected(HostFunctionError::NOT_LEAF_FIELD); break; case STI_ACCOUNT: { - auto const& super(static_cast(obj)); - auto const& data = super.value(); - return {data.begin(), data.end()}; + auto const* account(static_cast(obj)); + auto const& data = account->value(); + return Bytes{data.begin(), data.end()}; } break; - case STI_AMOUNT: { - auto const& super(static_cast(obj)); - int64_t const data = super.xrp().drops(); - auto const* b = reinterpret_cast(&data); - auto const* e = reinterpret_cast(&data + 1); - return {b, e}; + case STI_AMOUNT: + // will be processed by serializer + break; + case STI_ISSUE: { + auto const* issue(static_cast(obj)); + Asset const& asset(issue->value()); + // XRP and IOU will be processed by serializer + if (asset.holds()) + { + // MPT + auto const& mptIssue = asset.get(); + auto const& mptID = mptIssue.getMptID(); + return Bytes{mptID.cbegin(), mptID.cend()}; + } } break; case STI_VL: { - auto const& super(static_cast(obj)); - auto const& data = super.value(); - return {data.begin(), data.end()}; + auto const* vl(static_cast(obj)); + auto const& data = vl->value(); + return Bytes{data.begin(), data.end()}; } break; - case STI_UINT256: { - auto const& super(static_cast const&>(obj)); - auto const& data = super.value(); - return {data.begin(), data.end()}; - } - break; - case STI_UINT32: { - auto const& super( - static_cast const&>(obj)); - std::uint32_t const data = super.value(); + case STI_UINT16: { + auto const& num( + static_cast const*>(obj)); + std::uint16_t const data = num->value(); auto const* b = reinterpret_cast(&data); auto const* e = reinterpret_cast(&data + 1); - return {b, e}; + return Bytes{b, e}; + } + case STI_UINT32: { + auto const* num( + static_cast const*>(obj)); + std::uint32_t const data = num->value(); + auto const* b = reinterpret_cast(&data); + auto const* e = reinterpret_cast(&data + 1); + return Bytes{b, e}; } break; default: - break; + break; // default to serializer } Serializer msg; - obj.add(msg); + obj->add(msg); + auto const data = msg.getData(); - return msg.getData(); + return data; } Expected getTxField(SField const& fname) override { - auto const* field = tx_->peekAtPField(fname); - if (!field) - return Unexpected(HostFunctionError::FIELD_NOT_FOUND); - if ((STI_OBJECT == field->getSType()) || - (STI_ARRAY == field->getSType())) - return Unexpected(HostFunctionError::NOT_LEAF_FIELD); - - return getAnyFieldData(*field); + return getAnyFieldData(tx_->peekAtPField(fname)); } Expected getCurrentLedgerObjField(SField const& fname) override { - auto const sle = env_.le(leKey); + auto const sle = getCurrentLedgerObj(); if (!sle) - return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND); - - auto const* field = sle->peekAtPField(fname); - if (!field) - return Unexpected(HostFunctionError::FIELD_NOT_FOUND); - if ((STI_OBJECT == field->getSType()) || - (STI_ARRAY == field->getSType())) - return Unexpected(HostFunctionError::NOT_LEAF_FIELD); - - return getAnyFieldData(*field); + return Unexpected(sle.error()); + return getAnyFieldData((*sle)->peekAtPField(fname)); } Expected getLedgerObjField(int32_t cacheIdx, SField const& fname) override { - --cacheIdx; - if (cacheIdx < 0 || cacheIdx >= MAX_CACHE) - return Unexpected(HostFunctionError::SLOT_OUT_RANGE); + auto const sle = peekCurrentLedgerObj(cacheIdx); + if (!sle) + return Unexpected(sle.error()); + return getAnyFieldData((*sle)->peekAtPField(fname)); + } - if (!cache[cacheIdx]) - { - // return Unexpected(HostFunctionError::INVALID_SLOT); - cache[cacheIdx] = env_.le(leKey); - } - - auto const* field = cache[cacheIdx]->peekAtPField(fname); - if (!field) - return Unexpected(HostFunctionError::FIELD_NOT_FOUND); - if ((STI_OBJECT == field->getSType()) || - (STI_ARRAY == field->getSType())) - return Unexpected(HostFunctionError::NOT_LEAF_FIELD); - - return getAnyFieldData(*field); + static inline bool + noField(STBase const* field) + { + return !field || (STI_NOTPRESENT == field->getSType()) || + (STI_UNKNOWN == field->getSType()); } static Expected - locateField(STObject const& obj, Slice const& loc) + locateField(STObject const& obj, Slice const& locator) { - if (loc.empty() || (loc.size() & 3)) // must be multiple of 4 + if (locator.empty() || (locator.size() & 3)) // must be multiple of 4 return Unexpected(HostFunctionError::LOCATOR_MALFORMED); - int32_t const* l = reinterpret_cast(loc.data()); - int32_t const sz = loc.size() / 4; + int32_t const* locPtr = + reinterpret_cast(locator.data()); + int32_t const locSize = locator.size() / 4; STBase const* field = nullptr; - auto const& m = SField::getKnownCodeToField(); + auto const& knownSFields = SField::getKnownCodeToField(); { - int32_t const c = l[0]; - auto const it = m.find(c); - if (it == m.end()) - return Unexpected(HostFunctionError::LOCATOR_MALFORMED); - auto const& fname(*it->second); + int32_t const sfieldCode = 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 (!field || (STI_NOTPRESENT == field->getSType())) + if (noField(field)) return Unexpected(HostFunctionError::FIELD_NOT_FOUND); } - for (int i = 1; i < sz; ++i) + for (int i = 1; i < locSize; ++i) { - int32_t const c = l[i]; + int32_t const sfieldCode = locPtr[i]; if (STI_ARRAY == field->getSType()) { auto const* arr = static_cast(field); - if (c >= arr->size()) - return Unexpected(HostFunctionError::LOCATOR_MALFORMED); - field = &(arr->operator[](c)); + if (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); - auto const it = m.find(c); - if (it == m.end()) - return Unexpected(HostFunctionError::LOCATOR_MALFORMED); - auto const& fname(*it->second); + 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 // simple field must be the last one @@ -807,7 +914,7 @@ public: return Unexpected(HostFunctionError::LOCATOR_MALFORMED); } - if (!field || (STI_NOTPRESENT == field->getSType())) + if (noField(field)) return Unexpected(HostFunctionError::FIELD_NOT_FOUND); } @@ -822,57 +929,35 @@ public: auto const r = locateField(*tx_, locator); if (!r) return Unexpected(r.error()); - - auto const* field = r.value(); - if ((STI_OBJECT == field->getSType()) || - (STI_ARRAY == field->getSType())) - return Unexpected(HostFunctionError::NOT_LEAF_FIELD); - - return getAnyFieldData(*field); + return getAnyFieldData(*r); } Expected getCurrentLedgerObjNestedField(Slice const& locator) override { - auto const sle = env_.le(leKey); + auto const sle = getCurrentLedgerObj(); if (!sle) - return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND); + return Unexpected(sle.error()); - auto const r = locateField(*sle, locator); + auto const r = locateField(**sle, locator); if (!r) return Unexpected(r.error()); - auto const* field = r.value(); - if ((STI_OBJECT == field->getSType()) || - (STI_ARRAY == field->getSType())) - return Unexpected(HostFunctionError::NOT_LEAF_FIELD); - - return getAnyFieldData(*field); + return getAnyFieldData(*r); } Expected getLedgerObjNestedField(int32_t cacheIdx, Slice const& locator) override { - --cacheIdx; - if (cacheIdx < 0 || cacheIdx >= MAX_CACHE) - return Unexpected(HostFunctionError::SLOT_OUT_RANGE); + auto const sle = peekCurrentLedgerObj(cacheIdx); + if (!sle) + return Unexpected(sle.error()); - if (!cache[cacheIdx]) - { - // return Unexpected(HostFunctionError::INVALID_SLOT); - cache[cacheIdx] = env_.le(leKey); - } - - auto const r = locateField(*cache[cacheIdx], locator); + auto const r = locateField(**sle, locator); if (!r) return Unexpected(r.error()); - auto const* field = r.value(); - if ((STI_OBJECT == field->getSType()) || - (STI_ARRAY == field->getSType())) - return Unexpected(HostFunctionError::NOT_LEAF_FIELD); - - return getAnyFieldData(*field); + return getAnyFieldData(*r); } Expected @@ -882,7 +967,7 @@ public: return Unexpected(HostFunctionError::NO_ARRAY); auto const* field = tx_->peekAtPField(fname); - if (!field) + if (noField(field)) return Unexpected(HostFunctionError::FIELD_NOT_FOUND); if (field->getSType() != STI_ARRAY) @@ -898,12 +983,12 @@ public: if (fname.fieldType != STI_ARRAY) return Unexpected(HostFunctionError::NO_ARRAY); - auto const sle = env_.le(leKey); + auto const sle = getCurrentLedgerObj(); if (!sle) - return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND); + return Unexpected(sle.error()); - auto const* field = sle->peekAtPField(fname); - if (!field) + auto const* field = (*sle)->peekAtPField(fname); + if (noField(field)) return Unexpected(HostFunctionError::FIELD_NOT_FOUND); if (field->getSType() != STI_ARRAY) @@ -919,17 +1004,12 @@ public: if (fname.fieldType != STI_ARRAY) return Unexpected(HostFunctionError::NO_ARRAY); - if (cacheIdx < 0 || cacheIdx >= MAX_CACHE) - return Unexpected(HostFunctionError::SLOT_OUT_RANGE); + auto const sle = peekCurrentLedgerObj(cacheIdx); + if (!sle) + return Unexpected(sle.error()); - if (!cache[cacheIdx]) - { - // return Unexpected(HostFunctionError::INVALID_SLOT); - cache[cacheIdx] = env_.le(leKey); - } - - auto const* field = cache[cacheIdx]->peekAtPField(fname); - if (!field) + auto const* field = (*sle)->peekAtPField(fname); + if (noField(field)) return Unexpected(HostFunctionError::FIELD_NOT_FOUND); if (field->getSType() != STI_ARRAY) @@ -957,10 +1037,11 @@ public: Expected getCurrentLedgerObjNestedArrayLen(Slice const& locator) override { - auto const sle = env_.le(leKey); + auto const sle = getCurrentLedgerObj(); if (!sle) - return Unexpected(HostFunctionError::LEDGER_OBJ_NOT_FOUND); - auto const r = locateField(*sle, locator); + return Unexpected(sle.error()); + + auto const r = locateField(**sle, locator); if (!r) return Unexpected(r.error()); auto const* field = r.value(); @@ -975,17 +1056,11 @@ public: Expected getLedgerObjNestedArrayLen(int32_t cacheIdx, Slice const& locator) override { - --cacheIdx; - if (cacheIdx < 0 || cacheIdx >= MAX_CACHE) - return Unexpected(HostFunctionError::SLOT_OUT_RANGE); + auto const sle = peekCurrentLedgerObj(cacheIdx); + if (!sle) + return Unexpected(sle.error()); - if (!cache[cacheIdx]) - { - // return Unexpected(HostFunctionError::INVALID_SLOT); - cache[cacheIdx] = env_.le(leKey); - } - - auto const r = locateField(*cache[cacheIdx], locator); + auto const r = locateField(**sle, locator); if (!r) return Unexpected(r.error()); @@ -1001,6 +1076,9 @@ public: Expected updateData(Slice const& data) override { + if (data.size() > maxWasmDataLength) + return Unexpected(HostFunctionError::DATA_FIELD_TOO_LARGE); + ripple::detail::ApplyViewBase v( env_.app().openLedger().current().get(), tapNONE); @@ -1014,6 +1092,19 @@ public: return data.size(); } + Expected + checkSignature( + Slice const& message, + Slice const& signature, + Slice const& pubkey) override + { + if (!publicKeyType(pubkey)) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + PublicKey const pk(pubkey); + return verify(pk, message, signature, /*canonical*/ true); + } + Expected computeSha512HalfHash(Slice const& data) override { @@ -1021,6 +1112,215 @@ public: return hash; } + Expected + accountKeylet(AccountID const& account) 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) override + { + 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 + checkKeylet(AccountID const& account, std::uint32_t seq) 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) override + { + 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 + didKeylet(AccountID const& account) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::did(account); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + delegateKeylet(AccountID const& account, AccountID const& authorize) + override + { + 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 + depositPreauthKeylet(AccountID const& account, AccountID const& authorize) + override + { + 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 + escrowKeylet(AccountID const& account, std::uint32_t seq) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::escrow(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + lineKeylet( + AccountID const& account1, + AccountID const& account2, + Currency const& currency) override + { + 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 + mptIssuanceKeylet(AccountID const& issuer, std::uint32_t seq) override + { + if (!issuer) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + + auto const keylet = keylet::mptIssuance(seq, issuer); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + mptokenKeylet(MPTID const& mptid, AccountID const& holder) override + { + 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 + nftOfferKeylet(AccountID const& account, std::uint32_t seq) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::nftoffer(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + offerKeylet(AccountID const& account, std::uint32_t seq) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::offer(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + oracleKeylet(AccountID const& account, std::uint32_t documentId) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::oracle(account, documentId); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + paychanKeylet( + AccountID const& account, + AccountID const& destination, + std::uint32_t seq) override + { + 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 + permissionedDomainKeylet(AccountID const& account, std::uint32_t seq) + override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::permissionedDomain(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + signersKeylet(AccountID const& account) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::signers(account); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + ticketKeylet(AccountID const& account, std::uint32_t seq) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::ticket(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + + Expected + vaultKeylet(AccountID const& account, std::uint32_t seq) override + { + if (!account) + return Unexpected(HostFunctionError::INVALID_ACCOUNT); + auto const keylet = keylet::vault(account, seq); + return Bytes{keylet.key.begin(), keylet.key.end()}; + } + Expected getNFT(AccountID const& account, uint256 const& nftId) override { @@ -1044,6 +1344,40 @@ public: Slice const s = ouri->value(); return Bytes(s.begin(), s.end()); } + + Expected + getNFTIssuer(uint256 const& nftId) override + { + auto const issuer = nft::getIssuer(nftId); + if (!issuer) + return Unexpected(HostFunctionError::INVALID_PARAMS); + + return Bytes{issuer.begin(), issuer.end()}; + } + + Expected + getNFTTaxon(uint256 const& nftId) override + { + return nft::toUInt32(nft::getTaxon(nftId)); + } + + Expected + getNFTFlags(uint256 const& nftId) override + { + return nft::getFlags(nftId); + } + + Expected + getNFTTransferFee(uint256 const& nftId) override + { + return nft::getTransferFee(nftId); + } + + Expected + getNFTSerial(uint256 const& nftId) override + { + return nft::getSerial(nftId); + } }; } // namespace test diff --git a/src/test/app/Wasm_test.cpp b/src/test/app/Wasm_test.cpp index bd4f7d4380..4348e14130 100644 --- a/src/test/app/Wasm_test.cpp +++ b/src/test/app/Wasm_test.cpp @@ -519,7 +519,7 @@ struct Wasm_test : public beast::unit_test::suite if (BEAST_EXPECT(re.has_value())) { BEAST_EXPECTS(re->result == 1, std::to_string(re->result)); - BEAST_EXPECTS(re->cost == 91'412, std::to_string(re->cost)); + BEAST_EXPECTS(re->cost == 96'942, std::to_string(re->cost)); } env.close(); } @@ -534,7 +534,7 @@ struct Wasm_test : public beast::unit_test::suite if (BEAST_EXPECT(re.has_value())) { BEAST_EXPECTS(re->result == 1, std::to_string(re->result)); - BEAST_EXPECTS(re->cost == 6'533, std::to_string(re->cost)); + BEAST_EXPECTS(re->cost == 2'053, std::to_string(re->cost)); } env.close(); } @@ -550,13 +550,12 @@ struct Wasm_test : public beast::unit_test::suite // std::string const funcName("test"); auto const& wasmHex = hfPerfTest; - // auto const& wasmHex = opcCallPerfTest; std::string const wasmStr = boost::algorithm::unhex(wasmHex); std::vector const wasm(wasmStr.begin(), wasmStr.end()); - std::string const credType = "abcde"; - std::string const credType2 = "fghijk"; - std::string const credType3 = "0123456"; + // std::string const credType = "abcde"; + // std::string const credType2 = "fghijk"; + // std::string const credType3 = "0123456"; // char const uri[] = "uri"; Account const alan{"alan"}; @@ -597,7 +596,7 @@ struct Wasm_test : public beast::unit_test::suite // {issuer, credType2}, // {issuer, credType3}})); - // cREATE nft + // create nft [[maybe_unused]] uint256 const nft0{ token::getNextID(env, alan, 0u)}; env(token::mint(alan, 0u)); @@ -649,7 +648,7 @@ struct Wasm_test : public beast::unit_test::suite Bytes const wasm(wasmStr.begin(), wasmStr.end()); TestHostFunctions hfs(env, 0); - auto const allowance = 148'406; + auto const allowance = 153'296; auto re = runEscrowWasm( wasm, ESCROW_FUNCTION_NAME, {}, &hfs, allowance, env.journal); diff --git a/src/test/app/wasm_fixtures/fixtures.cpp b/src/test/app/wasm_fixtures/fixtures.cpp index de2ad6291b..8fb6340bed 100644 --- a/src/test/app/wasm_fixtures/fixtures.cpp +++ b/src/test/app/wasm_fixtures/fixtures.cpp @@ -9571,41 +9571,38 @@ extern std::string const deepRecursionHex = "2d657874"; extern std::string const hfPerfTest = - "0061736d0100000001180460037f7f7e017f60057f7f7f7f7f017f600000" - "6000017f021d0203656e760974726163655f6e756d000003656e76057472" - "616365000103030202030503010002068601167f0041a0090b7f0041a011" - "0b7f0041e0080b7f0041a0080b7f0041c0080b7f004180080b7f00418408" - "0b7f004188080b7f00418c080b7f004190080b7f004194080b7f00419808" - "0b7f004180090b7f004180080b7f0041e0110b7f0041e0110b7f0041e091" - "040b7f004180080b7f0041e091040b7f00418080080b7f0041000b7f0041" - "010b07c50219066d656d6f72790200115f5f7761736d5f63616c6c5f6374" - "6f727300020666696e6973680003036275660300076c6f635f6275660301" - "036d73670302086572725f686561640303096572725f646174613103040a" - "53465f4163636f756e7403050e53465f44657374696e6174696f6e030608" - "53465f4d656d6f7303070753465f4d656d6f03080b53465f4d656d6f4461" - "746103090753465f44617461030a1753465f417574686f72697a65437265" - "64656e7469616c73030b066163635f6964030c0c5f5f64736f5f68616e64" - "6c65030d0a5f5f646174615f656e64030e0b5f5f737461636b5f6c6f7703" - "0f0c5f5f737461636b5f6869676803100d5f5f676c6f62616c5f62617365" - "03110b5f5f686561705f6261736503120a5f5f686561705f656e6403130d" - "5f5f6d656d6f72795f6261736503140c5f5f7461626c655f626173650315" - "0aa2010202000b9c0102017f017e41807821000340200041a0116a420037" - "0300200041086a22000d000b41d811420037030041d011420037030041c8" - "11420037030041c011420037030041b811420037030041b0114200370300" - "41a811428d801c37030041a011429a803c3703000240034041e008411020" - "0110004118460440200142017c220142c0843d520d010c020b0b41a00841" - "1441c0084113410010011a0b41010b0b840104004180080b340100080003" - "00080009000f000a000e000d0007001b0007001a000f0000000000676574" - "5f6c65646765725f73716e206572726f720041c0080b13696e76616c6964" - "2072657475726e2073697a650041e0080b104e6577206163636f756e7420" - "69643a20004180090b14fc4f9afac9f1a8db807fda7dc9247bb557569d58" - "007f0970726f647563657273010c70726f6365737365642d62790105636c" - "616e675f31392e312e352d776173692d73646b202868747470733a2f2f67" - "69746875622e636f6d2f6c6c766d2f6c6c766d2d70726f6a656374206162" - "346235613264623538323935386166316565333038613739306366646234" - "32626432343732302900490f7461726765745f6665617475726573042b0f" - "6d757461626c652d676c6f62616c732b087369676e2d6578742b0f726566" - "6572656e63652d74797065732b0a6d756c746976616c7565"; + "0061736d0100000001190460057f7f7f7f7f017f60047f7f7f7f017f6000006000017f0236" + "0303656e7609666c6f61745f6c6f67000003656e76057472616365000003656e7612747261" + "63655f6f70617175655f666c6f617400010303020203050301000206aa011c7f0041b0090b" + "7f004193090b7f0041b0080b7f0041c0080b7f0041e0080b7f004180090b7f004180080b7f" + "004184080b7f004188080b7f00418c080b7f004190080b7f004194080b7f004198080b7f00" + "419c080b7f0041a0080b7f0041a4080b7f0041a8080b7f0041ac080b7f00419b090b7f0041" + "80080b7f0041b0110b7f0041b0110b7f0041b091040b7f004180080b7f0041b091040b7f00" + "418080080b7f0041000b7f0041010b0785031f066d656d6f72790200115f5f7761736d5f63" + "616c6c5f63746f727300030666696e697368000403627566030001610301086572725f6865" + "61640302096572725f6461746131030305696e707574030406726573756c74030508495445" + "525f4d4158030609484153485f53495a450307084143435f53495a4503080d43555252454e" + "43595f53495a4503090b4b45594c45545f53495a45030a0a53465f4163636f756e74030b0e" + "53465f44657374696e6174696f6e030c0853465f4d656d6f73030d0753465f4d656d6f030e" + "0b53465f4d656d6f44617461030f0753465f4461746103101753465f417574686f72697a65" + "43726564656e7469616c730311016203120c5f5f64736f5f68616e646c6503130a5f5f6461" + "74615f656e6403140b5f5f737461636b5f6c6f7703150c5f5f737461636b5f686967680316" + "0d5f5f676c6f62616c5f6261736503170b5f5f686561705f6261736503180a5f5f68656170" + "5f656e6403190d5f5f6d656d6f72795f62617365031a0c5f5f7461626c655f62617365031b" + "0a7c0202000b7701017f41807821000340200041b0116a4200370300200041086a22000d00" + "0b41c0843d210002400340419309410841b009418008410010004108460440200041016b22" + "000d010c020b0b41b008410f41c0084113410010011a0b41e0084111419309410810021a41" + "8009411241b009410810021a41010b0b9f0104004180080b53809698002000000014000000" + "1400000020000000010008000300080009000f000a000e000d0007001b0007001a000f0066" + "6c6f61745f6c6f67206572726f7200696e76616c69642072657475726e2073697a650041e0" + "080b11666c6f61745f6c6f6720696e7075743a20004180090b12666c6f61745f6c6f672072" + "6573756c743a20004193090b10d48b29430a256d21d920c49ba5e353f8007f0970726f6475" + "63657273010c70726f6365737365642d62790105636c616e675f31392e312e352d77617369" + "2d73646b202868747470733a2f2f6769746875622e636f6d2f6c6c766d2f6c6c766d2d7072" + "6f6a6563742061623462356132646235383239353861663165653330386137393063666462" + "3432626432343732302900490f7461726765745f6665617475726573042b0f6d757461626c" + "652d676c6f62616c732b087369676e2d6578742b0f7265666572656e63652d74797065732b" + "0a6d756c746976616c7565"; extern std::string const allKeyletsWasmHex = "0061736d0100000001530a60057f7f7f7f7f017f60047f7f7f7f017f60067f7f7f7f7f7f01" diff --git a/src/xrpld/app/wasm/HostFuncImpl.h b/src/xrpld/app/wasm/HostFuncImpl.h index 5539a5ef40..7c02e3160e 100644 --- a/src/xrpld/app/wasm/HostFuncImpl.h +++ b/src/xrpld/app/wasm/HostFuncImpl.h @@ -50,7 +50,15 @@ class WasmHostFunctionsImpl : public HostFunctions } Expected - normalizeCacheIndex(int32_t cacheIdx); + normalizeCacheIndex(int32_t cacheIdx) + { + --cacheIdx; + if (cacheIdx < 0 || cacheIdx >= MAX_CACHE) + return Unexpected(HostFunctionError::SLOT_OUT_RANGE); + if (!cache[cacheIdx]) + return Unexpected(HostFunctionError::EMPTY_SLOT); + return cacheIdx; + } public: WasmHostFunctionsImpl(ApplyContext& ctx, Keylet const& leKey) diff --git a/src/xrpld/app/wasm/detail/HostFuncImpl.cpp b/src/xrpld/app/wasm/detail/HostFuncImpl.cpp index 4b585acdcd..af8eff36ee 100644 --- a/src/xrpld/app/wasm/detail/HostFuncImpl.cpp +++ b/src/xrpld/app/wasm/detail/HostFuncImpl.cpp @@ -90,17 +90,6 @@ WasmHostFunctionsImpl::isAmendmentEnabled(std::string_view const& amendmentName) return ctx.view().rules().enabled(amendment); } -Expected -WasmHostFunctionsImpl::normalizeCacheIndex(int32_t cacheIdx) -{ - --cacheIdx; - if (cacheIdx < 0 || cacheIdx >= MAX_CACHE) - return Unexpected(HostFunctionError::SLOT_OUT_RANGE); - if (!cache[cacheIdx]) - return Unexpected(HostFunctionError::EMPTY_SLOT); - return cacheIdx; -} - Expected WasmHostFunctionsImpl::cacheLedgerObj(uint256 const& objId, int32_t cacheIdx) { @@ -1296,7 +1285,7 @@ floatPowerImpl(Slice const& x, int32_t n, int32_t mode) { try { - if (n < 0) + if ((n < 0) || (n > IOUAmount::maxExponent)) return Unexpected(HostFunctionError::FLOAT_INPUT_MALFORMED); SetRound rm(mode); diff --git a/src/xrpld/app/wasm/detail/HostFuncWrapper.cpp b/src/xrpld/app/wasm/detail/HostFuncWrapper.cpp index 5293f1db78..304fc9eeda 100644 --- a/src/xrpld/app/wasm/detail/HostFuncWrapper.cpp +++ b/src/xrpld/app/wasm/detail/HostFuncWrapper.cpp @@ -107,7 +107,11 @@ getDataSField(IW const* _runtime, wasm_val_vec_t const* params, int32_t& i) template Expected -getDataSlice(IW const* runtime, wasm_val_vec_t const* params, int32_t& i) +getDataSlice( + IW const* runtime, + wasm_val_vec_t const* params, + int32_t& i, + bool isUpdate = false) { auto const ptr = params->data[i].of.i32; auto const size = params->data[i + 1].of.i32; @@ -117,7 +121,7 @@ getDataSlice(IW const* runtime, wasm_val_vec_t const* params, int32_t& i) if (!size) return Slice(); - if (size > maxWasmDataLength) + if (size > (isUpdate ? maxWasmDataLength : maxWasmParamLength)) return Unexpected(HostFunctionError::DATA_FIELD_TOO_LARGE); auto memory = runtime ? runtime->getMem() : wmem(); @@ -751,7 +755,7 @@ updateData_wrap( auto const* runtime = reinterpret_cast(hf->getRT()); int index = 0; - auto const bytes = getDataSlice(runtime, params, index); + auto const bytes = getDataSlice(runtime, params, index, true); if (!bytes) { return hfResult(results, bytes.error()); @@ -1491,7 +1495,7 @@ trace_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) auto const* runtime = reinterpret_cast(hf->getRT()); int index = 0; - if (params->data[1].of.i32 + params->data[3].of.i32 > maxWasmDataLength) + if (params->data[1].of.i32 + params->data[3].of.i32 > maxWasmParamLength) { return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); } @@ -1524,7 +1528,7 @@ traceNum_wrap(void* env, wasm_val_vec_t const* params, wasm_val_vec_t* results) auto* hf = reinterpret_cast(env); auto const* runtime = reinterpret_cast(hf->getRT()); int index = 0; - if (params->data[1].of.i32 > maxWasmDataLength) + if (params->data[1].of.i32 > maxWasmParamLength) { return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); } @@ -1554,7 +1558,7 @@ traceAccount_wrap( auto* hf = reinterpret_cast(env); auto const* runtime = reinterpret_cast(hf->getRT()); - if (params->data[1].of.i32 > maxWasmDataLength) + if (params->data[1].of.i32 > maxWasmParamLength) return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); int i = 0; @@ -1579,7 +1583,7 @@ traceFloat_wrap( auto* hf = reinterpret_cast(env); auto const* runtime = reinterpret_cast(hf->getRT()); - if (params->data[1].of.i32 > maxWasmDataLength) + if (params->data[1].of.i32 > maxWasmParamLength) return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); int i = 0; @@ -1604,7 +1608,7 @@ traceAmount_wrap( auto* hf = reinterpret_cast(env); auto const* runtime = reinterpret_cast(hf->getRT()); - if (params->data[1].of.i32 > maxWasmDataLength) + if (params->data[1].of.i32 > maxWasmParamLength) return hfResult(results, HostFunctionError::DATA_FIELD_TOO_LARGE); int i = 0; diff --git a/src/xrpld/app/wasm/detail/WasmVM.cpp b/src/xrpld/app/wasm/detail/WasmVM.cpp index d9dd67860d..022d66f982 100644 --- a/src/xrpld/app/wasm/detail/WasmVM.cpp +++ b/src/xrpld/app/wasm/detail/WasmVM.cpp @@ -44,9 +44,9 @@ setCommonHostFunctions(HostFunctions* hfs, std::vector& i) WASM_IMPORT_FUNC2(i, getLedgerAccountHash, "get_ledger_account_hash", hfs, 60); WASM_IMPORT_FUNC2(i, getLedgerTransactionHash, "get_ledger_tx_hash", hfs, 60); WASM_IMPORT_FUNC2(i, getBaseFee, "get_base_fee", hfs, 60); - WASM_IMPORT_FUNC2(i, isAmendmentEnabled, "amendment_enabled", hfs, 60); + WASM_IMPORT_FUNC2(i, isAmendmentEnabled, "amendment_enabled", hfs, 100); - WASM_IMPORT_FUNC2(i, cacheLedgerObj, "cache_ledger_obj", hfs, 5000); + 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); @@ -60,20 +60,20 @@ setCommonHostFunctions(HostFunctions* hfs, std::vector& i) 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, 2000); + WASM_IMPORT_FUNC2(i, checkSignature, "check_sig", hfs, 300); WASM_IMPORT_FUNC2(i, computeSha512HalfHash, "compute_sha512_half", hfs, 2000); WASM_IMPORT_FUNC2(i, accountKeylet, "account_keylet", hfs, 350); - WASM_IMPORT_FUNC2(i, ammKeylet, "amm_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, 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, 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); @@ -84,7 +84,7 @@ setCommonHostFunctions(HostFunctions* hfs, std::vector& i) 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, 60); + 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); @@ -96,17 +96,17 @@ setCommonHostFunctions(HostFunctions* hfs, std::vector& i) 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, 1000); - WASM_IMPORT_FUNC2(i, floatFromUint, "float_from_uint", hfs, 1000); - WASM_IMPORT_FUNC2(i, floatSet, "float_set", hfs, 1000); - WASM_IMPORT_FUNC2(i, floatCompare, "float_compare", hfs, 1000); - WASM_IMPORT_FUNC2(i, floatAdd, "float_add", hfs, 1000); - WASM_IMPORT_FUNC2(i, floatSubtract, "float_subtract", hfs, 1000); - WASM_IMPORT_FUNC2(i, floatMultiply, "float_multiply", hfs, 1000); - WASM_IMPORT_FUNC2(i, floatDivide, "float_divide", hfs, 1000); - WASM_IMPORT_FUNC2(i, floatRoot, "float_root", hfs, 1000); - WASM_IMPORT_FUNC2(i, floatPower, "float_pow", hfs, 1000); - WASM_IMPORT_FUNC2(i, floatLog, "float_log", hfs, 1000); + 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 }