#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl { namespace { // What a host call answers when it could not be served at all: every method below hands it // to `guarded` as the answer for a body that throws. The engine reads -1 as its fatal // `Internal`, stops the run and reports `tecINTERNAL`. // // `HostFunctionError` spells -1 `Unimplemented`, so the two share a code. They also share // a meaning worth keeping together - "the host could not serve this call, and the contract // has no business interpreting why" - and they must share a fate. Named here so a call // site reads as what it is rather than as "unimplemented". constexpr std::int32_t kHostInternal = hfErrorToInt(HostFunctionError::Unimplemented); // Copy `value` into `out` only if the whole of it fits, and answer its true length either // way. A value too large for the guest's buffer must reach it in no part: a prefix would // be a wrong answer where a length is a usable one. std::int32_t answer(rust::Slice out, std::uint8_t const* value, std::size_t size) { if (size <= out.size()) std::memcpy(out.data(), value, size); return static_cast(size); } // A scalar the ABI carries as bytes, in the wire's byte order. // // `adjustWasmEndianess` is the one place that order is decided for the whole wasm boundary, // and it is `constexpr` with the swap under `if constexpr (std::endian::native == // std::endian::big)` - so this costs nothing on a little-endian host and is correct on a // big-endian one, which a hand-written shift sequence per call site would have to get right // each time. template std::int32_t answerScalar(rust::Slice out, T value) { auto const wire = adjustWasmEndianess(value); return answer(out, reinterpret_cast(&wire), sizeof(wire)); } // Decode an asset from its wire bytes, whose length selects the kind: an MPT id, a // bare currency (which must be XRP), or a currency followed by an issuer (which must // not be XRP). Any other length is malformed. This mirrors `getDataAsset` in the // C-ABI wrapper the wasm engine replaces. std::expected parseAsset(rust::Slice bytes) { if (bytes.size() == MPTID::size()) return Asset{MPTID::fromVoid(bytes.data())}; if (bytes.size() == Currency::size()) { auto const issue = Issue{Currency::fromVoid(bytes.data()), xrpAccount()}; if (!issue.native()) return std::unexpected(HostFunctionError::InvalidParams); return Asset{issue}; } if (bytes.size() == Currency::size() + AccountID::size()) { auto const issue = Issue( Currency::fromVoid(bytes.data()), AccountID::fromVoid(bytes.data() + Currency::size())); if (issue.native()) return std::unexpected(HostFunctionError::InvalidParams); return Asset{issue}; } return std::unexpected(HostFunctionError::InvalidParams); } } // namespace HostContext::HostContext(HostFunctions& hostFunctions) : hostFunctions_(hostFunctions) { } std::int32_t HostContext::getLedgerSqn(rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { auto const sqn = hostFunctions_.getLedgerSqn(); if (!sqn) return hfErrorToInt(sqn.error()); return answerScalar(out, *sqn); }); } std::int32_t HostContext::getParentLedgerTime(rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { auto const time = hostFunctions_.getParentLedgerTime(); if (!time) return hfErrorToInt(time.error()); return answerScalar(out, *time); }); } std::int32_t HostContext::getParentLedgerHash(rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { auto const hash = hostFunctions_.getParentLedgerHash(); if (!hash) return hfErrorToInt(hash.error()); return answer(out, hash->data(), hash->size()); }); } std::int32_t HostContext::getBaseFee(rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { auto const fee = hostFunctions_.getBaseFee(); if (!fee) return hfErrorToInt(fee.error()); return answerScalar(out, *fee); }); } std::int32_t HostContext::isAmendmentEnabled(rust::Slice amendment) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { // A 32-byte input may be an amendment id; try that first and fall through to // a name lookup if it is not an enabled amendment - the 32 bytes could spell // a name instead. if (amendment.size() == uint256::size()) { auto const enabled = hostFunctions_.isAmendmentEnabled(uint256::fromVoid(amendment.data())); if (enabled && *enabled == 1) return *enabled; } if (amendment.size() > 64) return hfErrorToInt(HostFunctionError::DataFieldTooLarge); auto const name = std::string_view(reinterpret_cast(amendment.data()), amendment.size()); auto const enabled = hostFunctions_.isAmendmentEnabled(name); if (!enabled) return hfErrorToInt(enabled.error()); return *enabled; }); } std::int32_t HostContext::cacheLedgerObj(rust::Slice objId, std::int32_t cacheIdx) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { if (objId.size() != uint256::size()) return hfErrorToInt(HostFunctionError::InvalidParams); auto const slot = hostFunctions_.cacheLedgerObj(uint256::fromVoid(objId.data()), cacheIdx); if (!slot) return hfErrorToInt(slot.error()); return *slot; }); } std::int32_t HostContext::getTxField(std::int32_t field, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { auto const& knownSFields = SField::getKnownCodeToField(); auto const it = knownSFields.find(field); if (it == knownSFields.end()) return hfErrorToInt(HostFunctionError::InvalidField); auto const value = hostFunctions_.getTxField(*it->second); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::getCurrentLedgerObjField(std::int32_t field, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { auto const& knownSFields = SField::getKnownCodeToField(); auto const it = knownSFields.find(field); if (it == knownSFields.end()) return hfErrorToInt(HostFunctionError::InvalidField); auto const value = hostFunctions_.getCurrentLedgerObjField(*it->second); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::getLedgerObjField( std::int32_t cacheIdx, std::int32_t field, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { auto const& knownSFields = SField::getKnownCodeToField(); auto const it = knownSFields.find(field); if (it == knownSFields.end()) return hfErrorToInt(HostFunctionError::InvalidField); auto const value = hostFunctions_.getLedgerObjField(cacheIdx, *it->second); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::getTxNestedField( rust::Slice locator, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { // A path of i32 steps: non-empty and a whole number of them. if (locator.empty() || (locator.size() & 3) != 0) return hfErrorToInt(HostFunctionError::LocatorMalformed); // Copy into an aligned int32 buffer rather than aliasing the slice, whose // bytes carry no int32 alignment guarantee. The wire byte order is kept; the // field getters below apply `adjustWasmEndianess` when they read a step. std::uint32_t const steps = locator.size() / sizeof(std::int32_t); std::vector locBuf(steps); std::memcpy(locBuf.data(), locator.data(), locator.size()); FieldLocator const fl(std::move(locBuf)); auto const value = hostFunctions_.getTxNestedField(fl); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::getCurrentLedgerObjNestedField( rust::Slice locator, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { if (locator.empty() || (locator.size() & 3) != 0) return hfErrorToInt(HostFunctionError::LocatorMalformed); std::uint32_t const steps = locator.size() / sizeof(std::int32_t); std::vector locBuf(steps); std::memcpy(locBuf.data(), locator.data(), locator.size()); FieldLocator const fl(std::move(locBuf)); auto const value = hostFunctions_.getCurrentLedgerObjNestedField(fl); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::getLedgerObjNestedField( std::int32_t cacheIdx, rust::Slice locator, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { if (locator.empty() || (locator.size() & 3) != 0) return hfErrorToInt(HostFunctionError::LocatorMalformed); std::uint32_t const steps = locator.size() / sizeof(std::int32_t); std::vector locBuf(steps); std::memcpy(locBuf.data(), locator.data(), locator.size()); FieldLocator const fl(std::move(locBuf)); auto const value = hostFunctions_.getLedgerObjNestedField(cacheIdx, fl); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::getTxArrayLen(std::int32_t field) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { auto const& knownSFields = SField::getKnownCodeToField(); auto const it = knownSFields.find(field); if (it == knownSFields.end()) return hfErrorToInt(HostFunctionError::InvalidField); auto const len = hostFunctions_.getTxArrayLen(*it->second); if (!len) return hfErrorToInt(len.error()); return *len; }); } std::int32_t HostContext::getCurrentLedgerObjArrayLen(std::int32_t field) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { auto const& knownSFields = SField::getKnownCodeToField(); auto const it = knownSFields.find(field); if (it == knownSFields.end()) return hfErrorToInt(HostFunctionError::InvalidField); auto const len = hostFunctions_.getCurrentLedgerObjArrayLen(*it->second); if (!len) return hfErrorToInt(len.error()); return *len; }); } std::int32_t HostContext::getLedgerObjArrayLen(std::int32_t cacheIdx, std::int32_t field) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { auto const& knownSFields = SField::getKnownCodeToField(); auto const it = knownSFields.find(field); if (it == knownSFields.end()) return hfErrorToInt(HostFunctionError::InvalidField); auto const len = hostFunctions_.getLedgerObjArrayLen(cacheIdx, *it->second); if (!len) return hfErrorToInt(len.error()); return *len; }); } std::int32_t HostContext::getTxNestedArrayLen(rust::Slice locator) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { if (locator.empty() || (locator.size() & 3) != 0) return hfErrorToInt(HostFunctionError::LocatorMalformed); std::uint32_t const steps = locator.size() / sizeof(std::int32_t); std::vector locBuf(steps); std::memcpy(locBuf.data(), locator.data(), locator.size()); FieldLocator const fl(std::move(locBuf)); auto const len = hostFunctions_.getTxNestedArrayLen(fl); if (!len) return hfErrorToInt(len.error()); return *len; }); } std::int32_t HostContext::getCurrentLedgerObjNestedArrayLen( rust::Slice locator) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { if (locator.empty() || (locator.size() & 3) != 0) return hfErrorToInt(HostFunctionError::LocatorMalformed); std::uint32_t const steps = locator.size() / sizeof(std::int32_t); std::vector locBuf(steps); std::memcpy(locBuf.data(), locator.data(), locator.size()); FieldLocator const fl(std::move(locBuf)); auto const len = hostFunctions_.getCurrentLedgerObjNestedArrayLen(fl); if (!len) return hfErrorToInt(len.error()); return *len; }); } std::int32_t HostContext::getLedgerObjNestedArrayLen( std::int32_t cacheIdx, rust::Slice locator) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { if (locator.empty() || (locator.size() & 3) != 0) return hfErrorToInt(HostFunctionError::LocatorMalformed); std::uint32_t const steps = locator.size() / sizeof(std::int32_t); std::vector locBuf(steps); std::memcpy(locBuf.data(), locator.data(), locator.size()); FieldLocator const fl(std::move(locBuf)); auto const len = hostFunctions_.getLedgerObjNestedArrayLen(cacheIdx, fl); if (!len) return hfErrorToInt(len.error()); return *len; }); } std::int32_t HostContext::checkSignature( rust::Slice message, rust::Slice signature, rust::Slice pubkey) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { auto const valid = hostFunctions_.checkSignature( Slice{message.data(), message.size()}, Slice{signature.data(), signature.size()}, Slice{pubkey.data(), pubkey.size()}); if (!valid) return hfErrorToInt(valid.error()); return *valid; }); } std::int32_t HostContext::accountKeylet(rust::Slice account, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { if (account.size() != AccountID::size()) return hfErrorToInt(HostFunctionError::InvalidParams); auto const value = hostFunctions_.accountKeylet(AccountID::fromVoid(account.data())); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::ammKeylet( rust::Slice asset1, rust::Slice asset2, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { auto const a1 = parseAsset(asset1); if (!a1) return hfErrorToInt(a1.error()); auto const a2 = parseAsset(asset2); if (!a2) return hfErrorToInt(a2.error()); auto const value = hostFunctions_.ammKeylet(*a1, *a2); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::checkKeylet( rust::Slice account, std::int32_t seq, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { if (account.size() != AccountID::size()) return hfErrorToInt(HostFunctionError::InvalidParams); // The guest's u32 seq arrives as its i32 bit pattern; recover it. auto const value = hostFunctions_.checkKeylet( AccountID::fromVoid(account.data()), static_cast(seq)); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::credentialKeylet( rust::Slice subject, rust::Slice issuer, rust::Slice credentialType, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { if (subject.size() != AccountID::size() || issuer.size() != AccountID::size()) return hfErrorToInt(HostFunctionError::InvalidParams); auto const value = hostFunctions_.credentialKeylet( AccountID::fromVoid(subject.data()), AccountID::fromVoid(issuer.data()), Slice{credentialType.data(), credentialType.size()}); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::delegateKeylet( rust::Slice account, rust::Slice authorize, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { if (account.size() != AccountID::size() || authorize.size() != AccountID::size()) return hfErrorToInt(HostFunctionError::InvalidParams); auto const value = hostFunctions_.delegateKeylet( AccountID::fromVoid(account.data()), AccountID::fromVoid(authorize.data())); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::depositPreauthKeylet( rust::Slice account, rust::Slice authorize, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { if (account.size() != AccountID::size() || authorize.size() != AccountID::size()) return hfErrorToInt(HostFunctionError::InvalidParams); auto const value = hostFunctions_.depositPreauthKeylet( AccountID::fromVoid(account.data()), AccountID::fromVoid(authorize.data())); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::didKeylet(rust::Slice account, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { if (account.size() != AccountID::size()) return hfErrorToInt(HostFunctionError::InvalidParams); auto const value = hostFunctions_.didKeylet(AccountID::fromVoid(account.data())); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::escrowKeylet( rust::Slice account, std::int32_t seq, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { if (account.size() != AccountID::size()) return hfErrorToInt(HostFunctionError::InvalidParams); // The guest's u32 seq arrives as its i32 bit pattern; recover it. auto const value = hostFunctions_.escrowKeylet( AccountID::fromVoid(account.data()), static_cast(seq)); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::trustLineKeylet( rust::Slice account1, rust::Slice account2, rust::Slice currency, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { if (account1.size() != AccountID::size() || account2.size() != AccountID::size() || currency.size() != Currency::size()) return hfErrorToInt(HostFunctionError::InvalidParams); auto const value = hostFunctions_.trustLineKeylet( AccountID::fromVoid(account1.data()), AccountID::fromVoid(account2.data()), Currency::fromVoid(currency.data())); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::mptokenIssuanceKeylet( rust::Slice issuer, std::int32_t seq, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { if (issuer.size() != AccountID::size()) return hfErrorToInt(HostFunctionError::InvalidParams); // The guest's u32 seq arrives as its i32 bit pattern; recover it. auto const value = hostFunctions_.mptokenIssuanceKeylet( AccountID::fromVoid(issuer.data()), static_cast(seq)); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::mptokenKeylet( rust::Slice mptid, rust::Slice holder, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { if (mptid.size() != MPTID::size() || holder.size() != AccountID::size()) return hfErrorToInt(HostFunctionError::InvalidParams); auto const value = hostFunctions_.mptokenKeylet( MPTID::fromVoid(mptid.data()), AccountID::fromVoid(holder.data())); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::nftokenOfferKeylet( rust::Slice account, std::int32_t seq, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { if (account.size() != AccountID::size()) return hfErrorToInt(HostFunctionError::InvalidParams); // The guest's u32 seq arrives as its i32 bit pattern; recover it. auto const value = hostFunctions_.nftokenOfferKeylet( AccountID::fromVoid(account.data()), static_cast(seq)); if (!value) return hfErrorToInt(value.error()); return answer(out, value->data(), value->size()); }); } std::int32_t HostContext::sha512Half(rust::Slice data, rust::Slice out) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { auto const digest = hostFunctions_.computeSha512HalfHash(Slice{data.data(), data.size()}); if (!digest) return hfErrorToInt(digest.error()); return answer(out, digest->data(), digest->size()); }); } std::int32_t HostContext::trace(rust::Str msg, rust::Slice data, bool asHex) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { auto const status = hostFunctions_.trace( std::string_view{msg.data(), msg.size()}, Slice{data.data(), data.size()}, asHex); if (!status) return hfErrorToInt(status.error()); return *status; }); } std::int32_t HostContext::traceNum(rust::Str msg, std::int64_t number) const noexcept { return guarded(hostFunctions_.getJournal(), kHostInternal, [&] { auto const status = hostFunctions_.traceNum(std::string_view{msg.data(), msg.size()}, number); if (!status) return hfErrorToInt(status.error()); return *status; }); } } // namespace xrpl