mirror of
https://github.com/XRPLF/rippled.git
synced 2026-08-21 14:20:56 +00:00
More tests
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <rust/cxx.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -14,6 +18,19 @@ HostContextTest::bytesOf(Bytes const& bytes)
|
||||
return rust::Slice<std::uint8_t const>{bytes.data(), bytes.size()};
|
||||
}
|
||||
|
||||
Bytes
|
||||
HostContextTest::bytesOfSteps(std::vector<std::int32_t> const& steps)
|
||||
{
|
||||
Bytes bytes;
|
||||
bytes.reserve(steps.size() * sizeof(std::int32_t));
|
||||
for (auto const step : steps)
|
||||
{
|
||||
auto const wire = bytesOfScalar(step);
|
||||
bytes.insert(bytes.end(), wire.begin(), wire.end());
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
HostContextTest::OutRegion::OutRegion(std::size_t capacity) : bytes(capacity, kSentinel)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
@@ -24,6 +25,31 @@ struct HostContextTest : testing::Test
|
||||
static rust::Slice<std::uint8_t const>
|
||||
bytesOf(Bytes const& bytes);
|
||||
|
||||
// A scalar's wire form: its bytes little-endian, the way a wasm guest lays them out in
|
||||
// memory.
|
||||
//
|
||||
// Spelled out with shifts rather than a `memcpy` of the value, which would mirror what
|
||||
// `answerScalar` does and so assert nothing about the byte order. That is the whole reason
|
||||
// this exists, so keep it a shift.
|
||||
template <class T>
|
||||
static Bytes
|
||||
bytesOfScalar(T value)
|
||||
{
|
||||
static_assert(std::is_integral_v<T>, "Only integral types");
|
||||
|
||||
auto const bits = static_cast<std::make_unsigned_t<T>>(value);
|
||||
Bytes bytes(sizeof(bits));
|
||||
for (std::size_t i = 0; i < sizeof(bits); ++i)
|
||||
{
|
||||
bytes[i] = static_cast<std::uint8_t>(bits >> (i * 8));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// A locator's wire form: each step as four little-endian bytes.
|
||||
static Bytes
|
||||
bytesOfSteps(std::vector<std::int32_t> const& steps);
|
||||
|
||||
// Filled with a sentinel rather than left at zero: an answer can itself be all zero, so
|
||||
// only a byte no answer produces tells "wrote nothing" apart from "wrote zeros".
|
||||
struct OutRegion
|
||||
@@ -53,4 +79,26 @@ struct HostContextTest : testing::Test
|
||||
logged() const;
|
||||
};
|
||||
|
||||
// `FieldLocator` has no `operator==` and is move-only, so an `EXPECT_CALL` needs a matcher
|
||||
// rather than `testing::Ref`/`testing::Eq`. `invokeWithLocator` builds it as a local that is
|
||||
// gone once the call returns, so the check has to happen inside the matcher.
|
||||
//
|
||||
// `MATCHER_P` emits a function of this name, and gmock matchers are CamelCase by convention.
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
MATCHER_P(LocatorEquals, steps, "")
|
||||
{
|
||||
if (arg.size() != static_cast<std::uint32_t>(steps.size()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for (std::uint32_t i = 0; i < arg.size(); ++i)
|
||||
{
|
||||
if (arg[i] != steps[i])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/tx/wasm/HostFunc.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
@@ -16,11 +22,12 @@ namespace xrpl::test {
|
||||
|
||||
// A mock of the host the wasm engine calls back into.
|
||||
//
|
||||
// Only a few of `HostFunctions`' methods are mocked here. That is the mock lagging the ABI,
|
||||
// not the ABI lacking coverage: `crates/xrpl-host-functions/src/lib.rs` already declares all
|
||||
// 61 entries. Each one not yet mocked keeps `HostFunctions`' own
|
||||
// `std::unexpected(Unimplemented)`, so a contract reaching for it fails the way production
|
||||
// would. Add a `MOCK_METHOD` here as tests for that method are written.
|
||||
// One `MOCK_METHOD` per `HostFunctions` entry, in that header's order, each signature taken
|
||||
// verbatim from it. The extra parentheses around a return type are what keeps the comma in
|
||||
// `std::expected<T, HostFunctionError>` from splitting the macro's arguments.
|
||||
//
|
||||
// No `ON_CALL` defaults, deliberately: this is always used through `StrictMock`, which fails
|
||||
// a call to a method carrying no `EXPECT_CALL`.
|
||||
struct MockHostFunctions : HostFunctions
|
||||
{
|
||||
explicit MockHostFunctions(beast::Journal journal) : HostFunctions(journal)
|
||||
@@ -35,12 +42,126 @@ struct MockHostFunctions : HostFunctions
|
||||
(),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::uint32_t, HostFunctionError>),
|
||||
getParentLedgerTime,
|
||||
(),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Hash, HostFunctionError>),
|
||||
getParentLedgerHash,
|
||||
(),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::uint32_t, HostFunctionError>),
|
||||
getBaseFee,
|
||||
(),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::int32_t, HostFunctionError>),
|
||||
isAmendmentEnabled,
|
||||
(uint256 const& amendmentId),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::int32_t, HostFunctionError>),
|
||||
isAmendmentEnabled,
|
||||
(std::string_view const& amendmentName),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::int32_t, HostFunctionError>),
|
||||
cacheLedgerObj,
|
||||
(uint256 const& objId, std::int32_t cacheIdx),
|
||||
(override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
getTxField,
|
||||
(SField const& fname),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
getCurrentLedgerObjField,
|
||||
(SField const& fname),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
getLedgerObjField,
|
||||
(std::int32_t cacheIdx, SField const& fname),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
getTxNestedField,
|
||||
(FieldLocator const& locator),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
getCurrentLedgerObjNestedField,
|
||||
(FieldLocator const& locator),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
getLedgerObjNestedField,
|
||||
(std::int32_t cacheIdx, FieldLocator const& locator),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::int32_t, HostFunctionError>),
|
||||
getTxArrayLen,
|
||||
(SField const& fname),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::int32_t, HostFunctionError>),
|
||||
getCurrentLedgerObjArrayLen,
|
||||
(SField const& fname),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::int32_t, HostFunctionError>),
|
||||
getLedgerObjArrayLen,
|
||||
(std::int32_t cacheIdx, SField const& fname),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::int32_t, HostFunctionError>),
|
||||
getTxNestedArrayLen,
|
||||
(FieldLocator const& locator),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::int32_t, HostFunctionError>),
|
||||
getCurrentLedgerObjNestedArrayLen,
|
||||
(FieldLocator const& locator),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::int32_t, HostFunctionError>),
|
||||
getLedgerObjNestedArrayLen,
|
||||
(std::int32_t cacheIdx, FieldLocator const& locator),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::int32_t, HostFunctionError>),
|
||||
updateData,
|
||||
(Slice const& data),
|
||||
(override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::int32_t, HostFunctionError>),
|
||||
checkSignature,
|
||||
(Slice const& message, Slice const& signature, Slice const& pubkey),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Hash, HostFunctionError>),
|
||||
computeSha512HalfHash,
|
||||
@@ -49,8 +170,152 @@ struct MockHostFunctions : HostFunctions
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
getTxField,
|
||||
(SField const& fname),
|
||||
accountKeylet,
|
||||
(AccountID const& account),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
ammKeylet,
|
||||
(Asset const& issue1, Asset const& issue2),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
checkKeylet,
|
||||
(AccountID const& account, std::uint32_t seq),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
credentialKeylet,
|
||||
(AccountID const& subject, AccountID const& issuer, Slice const& credentialType),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
didKeylet,
|
||||
(AccountID const& account),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
delegateKeylet,
|
||||
(AccountID const& account, AccountID const& authorize),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
depositPreauthKeylet,
|
||||
(AccountID const& account, AccountID const& authorize),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
escrowKeylet,
|
||||
(AccountID const& account, std::uint32_t seq),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
trustLineKeylet,
|
||||
(AccountID const& account1, AccountID const& account2, Currency const& currency),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
mptokenIssuanceKeylet,
|
||||
(AccountID const& issuer, std::uint32_t seq),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
mptokenKeylet,
|
||||
(MPTID const& mptid, AccountID const& holder),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
nftokenOfferKeylet,
|
||||
(AccountID const& account, std::uint32_t seq),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
offerKeylet,
|
||||
(AccountID const& account, std::uint32_t seq),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
oracleKeylet,
|
||||
(AccountID const& account, std::uint32_t docId),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
paychannelKeylet,
|
||||
(AccountID const& account, AccountID const& destination, std::uint32_t seq),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
permissionedDomainKeylet,
|
||||
(AccountID const& account, std::uint32_t seq),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
signerListKeylet,
|
||||
(AccountID const& account),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
ticketKeylet,
|
||||
(AccountID const& account, std::uint32_t seq),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
vaultKeylet,
|
||||
(AccountID const& account, std::uint32_t seq),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
getNFT,
|
||||
(AccountID const& account, uint256 const& nftId),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
getNFTIssuer,
|
||||
(uint256 const& nftId),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::uint32_t, HostFunctionError>),
|
||||
getNFTTaxon,
|
||||
(uint256 const& nftId),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::int32_t, HostFunctionError>),
|
||||
getNFTFlags,
|
||||
(uint256 const& nftId),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::int32_t, HostFunctionError>),
|
||||
getNFTTransferFee,
|
||||
(uint256 const& nftId),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::uint32_t, HostFunctionError>),
|
||||
getNFTSequence,
|
||||
(uint256 const& nftId),
|
||||
(const, override));
|
||||
|
||||
// Takes the rendered text, not the guest's buffer: rendering is `HostContext`'s, so what
|
||||
@@ -60,10 +325,97 @@ struct MockHostFunctions : HostFunctions
|
||||
trace,
|
||||
(std::string_view const& msg, std::string_view const& data),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
floatFromInt,
|
||||
(std::int64_t x, std::int32_t mode),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
floatFromUint,
|
||||
(std::uint64_t x, std::int32_t mode),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
floatFromSTAmount,
|
||||
(STAmount const& x, std::int32_t mode),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
floatFromSTNumber,
|
||||
(STNumber const& x, std::int32_t mode),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::int64_t, HostFunctionError>),
|
||||
floatToInt,
|
||||
(Slice const& x, std::int32_t mode),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<FloatPair, HostFunctionError>),
|
||||
floatToMantExp,
|
||||
(Slice const& x),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
floatFromMantExp,
|
||||
(std::int64_t mantissa, std::int32_t exponent, std::int32_t mode),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<std::int32_t, HostFunctionError>),
|
||||
floatCompare,
|
||||
(Slice const& x, Slice const& y),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
floatAdd,
|
||||
(Slice const& x, Slice const& y, std::int32_t mode),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
floatSubtract,
|
||||
(Slice const& x, Slice const& y, std::int32_t mode),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
floatMultiply,
|
||||
(Slice const& x, Slice const& y, std::int32_t mode),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
floatDivide,
|
||||
(Slice const& x, Slice const& y, std::int32_t mode),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
floatRoot,
|
||||
(Slice const& x, std::int32_t n, std::int32_t mode),
|
||||
(const, override));
|
||||
|
||||
MOCK_METHOD(
|
||||
(std::expected<Bytes, HostFunctionError>),
|
||||
floatPower,
|
||||
(Slice const& x, std::int32_t n, std::int32_t mode),
|
||||
(const, override));
|
||||
};
|
||||
|
||||
// Matches a `Slice` (or anything with `data()`/`size()`) against the bytes of a string, so
|
||||
// an expectation can say *what* the guest asked the host to work on.
|
||||
//
|
||||
// `MATCHER_P` emits a function of this name, and gmock matchers are CamelCase by convention.
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
MATCHER_P(BytesAre, expected, "")
|
||||
{
|
||||
return std::string_view{reinterpret_cast<char const*>(arg.data()), arg.size()} ==
|
||||
|
||||
126
src/tests/libxrpl/tx/wasm/host_context/AccountKeylet.cpp
Normal file
126
src/tests/libxrpl/tx/wasm/host_context/AccountKeylet.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
struct AccountKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const accountBytes{0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a,
|
||||
0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34};
|
||||
AccountID const account = AccountID::fromVoid(accountBytes.data());
|
||||
};
|
||||
|
||||
TEST_F(AccountKeyletCall, AccountIsForwardedKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, accountKeylet(account)).WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.accountKeylet(bytesOf(accountBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(AccountKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, accountKeylet(account))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.accountKeylet(bytesOf(accountBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(AccountKeyletCall, ShortAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const shortAccount(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, accountKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.accountKeylet(bytesOf(shortAccount), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(AccountKeyletCall, LongAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const longAccount(AccountID::size() + 1, 0x01);
|
||||
EXPECT_CALL(host, accountKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.accountKeylet(bytesOf(longAccount), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(AccountKeyletCall, EmptyAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, accountKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.accountKeylet(bytesOf(Bytes{}), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(AccountKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, accountKeylet(account))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"account keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.accountKeylet(bytesOf(accountBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("account keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("accountKeylet"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(AccountKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, accountKeylet(account)).WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.accountKeylet(bytesOf(accountBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(AccountKeyletCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, accountKeylet(account)).WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.accountKeylet(bytesOf(accountBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(AccountKeyletCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, accountKeylet(account)).WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.accountKeylet(bytesOf(accountBytes), out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
156
src/tests/libxrpl/tx/wasm/host_context/AmmKeylet.cpp
Normal file
156
src/tests/libxrpl/tx/wasm/host_context/AmmKeylet.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
namespace {
|
||||
|
||||
Bytes
|
||||
concatBytes(Bytes const& first, Bytes const& second)
|
||||
{
|
||||
Bytes bytes = first;
|
||||
bytes.insert(bytes.end(), second.begin(), second.end());
|
||||
return bytes;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not
|
||||
// here. This is `parseAsset`'s only coverage, so every branch of its length-based dispatch
|
||||
// is pinned below.
|
||||
struct AmmKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const mptWire = Bytes(24, 0x7a);
|
||||
Bytes const xrpWire = Bytes(20, 0x00);
|
||||
Bytes const currencyWire = Bytes(20, 0x42);
|
||||
Bytes const accountWire = Bytes(20, 0x99);
|
||||
Bytes const issueWire = concatBytes(currencyWire, accountWire);
|
||||
|
||||
Asset const mptAsset{MPTID::fromVoid(mptWire.data())};
|
||||
Asset const xrpAsset{xrpIssue()};
|
||||
Asset const issueAsset{
|
||||
Issue{Currency::fromVoid(currencyWire.data()), AccountID::fromVoid(accountWire.data())}};
|
||||
|
||||
Bytes const keylet = Bytes(32, 0xab);
|
||||
};
|
||||
|
||||
TEST_F(AmmKeyletCall, MptAndIssueAssetsForwardedAndKeyletWritten)
|
||||
{
|
||||
EXPECT_CALL(host, ammKeylet(testing::Eq(mptAsset), testing::Eq(issueAsset)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.ammKeylet(bytesOf(mptWire), bytesOf(issueWire), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(AmmKeyletCall, BareXrpCurrencyBytesBecomeNativeAssetHostIsAskedFor)
|
||||
{
|
||||
EXPECT_CALL(host, ammKeylet(testing::Eq(xrpAsset), testing::Eq(mptAsset)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.ammKeylet(bytesOf(xrpWire), bytesOf(mptWire), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(AmmKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, ammKeylet(testing::Eq(mptAsset), testing::Eq(issueAsset)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.ammKeylet(bytesOf(mptWire), bytesOf(issueWire), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(AmmKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, ammKeylet(testing::Eq(mptAsset), testing::Eq(issueAsset)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"amm keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.ammKeylet(bytesOf(mptWire), bytesOf(issueWire), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("amm keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("ammKeylet"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(AmmKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
EXPECT_CALL(host, ammKeylet(testing::Eq(mptAsset), testing::Eq(issueAsset)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.ammKeylet(bytesOf(mptWire), bytesOf(issueWire), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(AmmKeyletCall, BareNonXrpCurrencyIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, ammKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.ammKeylet(bytesOf(currencyWire), bytesOf(mptWire), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(AmmKeyletCall, IssueWithNativeCurrencyIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const nativeIssueWire = concatBytes(xrpWire, accountWire);
|
||||
EXPECT_CALL(host, ammKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.ammKeylet(bytesOf(nativeIssueWire), bytesOf(mptWire), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(AmmKeyletCall, EmptyAssetIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, ammKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.ammKeylet(bytesOf(Bytes{}), bytesOf(mptWire), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// asset1 is parsed before asset2, but `parseAsset` answers the same `InvalidParams` for every
|
||||
// malformed shape, so which one was rejected is not observable here. The two are malformed for
|
||||
// different reasons so the case is at least not a duplicate of the single-asset ones above.
|
||||
TEST_F(AmmKeyletCall, BothAssetsMalformedIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const wrongLength{1, 2, 3, 4, 5};
|
||||
EXPECT_CALL(host, ammKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.ammKeylet(bytesOf(wrongLength), bytesOf(currencyWire), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
109
src/tests/libxrpl/tx/wasm/host_context/BaseFee.cpp
Normal file
109
src/tests/libxrpl/tx/wasm/host_context/BaseFee.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// No D or F axis: `getBaseFee` takes no argument, so there is nothing to decode wrong and
|
||||
// nothing whose forwarded identity to check.
|
||||
struct BaseFeeCall : HostContextTest
|
||||
{
|
||||
static constexpr std::uint32_t kBaseFee = 0x12345678;
|
||||
Bytes const expectedBytes = bytesOfScalar(kBaseFee);
|
||||
};
|
||||
|
||||
TEST_F(BaseFeeCall, HostValueIsWrittenAsLittleEndianBytes)
|
||||
{
|
||||
EXPECT_CALL(host, getBaseFee()).WillOnce(testing::Return(kBaseFee));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.getBaseFee(out.slice()), 4);
|
||||
EXPECT_TRUE(out.holds(bytesOf(expectedBytes)));
|
||||
}
|
||||
|
||||
TEST_F(BaseFeeCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getBaseFee())
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::Unimplemented)));
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(hostContext.getBaseFee(out.slice()), hfErrorToInt(HostFunctionError::Unimplemented));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(BaseFeeCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getBaseFee())
|
||||
.WillOnce(testing::Throw(std::runtime_error{"base fee came apart"}));
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(hostContext.getBaseFee(out.slice()), hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("base fee came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getBaseFee"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(BaseFeeCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
EXPECT_CALL(host, getBaseFee()).WillOnce(testing::Return(kBaseFee));
|
||||
|
||||
OutRegion out{3};
|
||||
EXPECT_EQ(hostContext.getBaseFee(out.slice()), 4);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(BaseFeeCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
EXPECT_CALL(host, getBaseFee()).WillOnce(testing::Return(kBaseFee));
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(hostContext.getBaseFee(out.slice()), 4);
|
||||
EXPECT_TRUE(out.holds(bytesOf(expectedBytes)));
|
||||
}
|
||||
|
||||
// Cross-cutting: every `HostFunctionError` code crosses `hfErrorToInt` unchanged at this layer.
|
||||
// Unlike the engine-side `WasmVMTest.SoftHostErrorCodesCrossUnchanged`, nothing is excluded
|
||||
// here - `HostContext` does not distinguish a soft code from a fatal one, so `Unimplemented`
|
||||
// and `NoMemExported` cross the same as any other. `InternalFatal` sits outside the -1..-20
|
||||
// run other codes occupy (it is `INT32_MIN`), and crosses the same whether the host returns it
|
||||
// directly or `guarded` supplies it for a throw.
|
||||
TEST_F(BaseFeeCall, EveryHostFunctionErrorCodeCrossesHfErrorToIntUnchanged)
|
||||
{
|
||||
static constexpr HostFunctionError kAllErrors[] = {
|
||||
HostFunctionError::Unimplemented, HostFunctionError::FieldNotFound,
|
||||
HostFunctionError::BufferTooSmall, HostFunctionError::NoArray,
|
||||
HostFunctionError::NotLeafField, HostFunctionError::LocatorMalformed,
|
||||
HostFunctionError::SlotOutRange, HostFunctionError::SlotsFull,
|
||||
HostFunctionError::EmptySlot, HostFunctionError::LedgerObjNotFound,
|
||||
HostFunctionError::OutOfTransferLimit, HostFunctionError::DataFieldTooLarge,
|
||||
HostFunctionError::PointerOutOfBounds, HostFunctionError::NoMemExported,
|
||||
HostFunctionError::InvalidParams, HostFunctionError::InvalidAccount,
|
||||
HostFunctionError::InvalidField, HostFunctionError::IndexOutOfBounds,
|
||||
HostFunctionError::FloatInputMalformed, HostFunctionError::FloatComputationError,
|
||||
HostFunctionError::InternalFatal,
|
||||
};
|
||||
|
||||
auto refused = HostFunctionError::Unimplemented;
|
||||
EXPECT_CALL(host, getBaseFee())
|
||||
.WillRepeatedly([&refused]() -> std::expected<std::uint32_t, HostFunctionError> {
|
||||
return std::unexpected(refused);
|
||||
});
|
||||
|
||||
for (auto const error : kAllErrors)
|
||||
{
|
||||
refused = error;
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(hostContext.getBaseFee(out.slice()), hfErrorToInt(error));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
81
src/tests/libxrpl/tx/wasm/host_context/CacheLedgerObj.cpp
Normal file
81
src/tests/libxrpl/tx/wasm/host_context/CacheLedgerObj.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// `cacheLedgerObj` mutates the host's slot table, so it is non-`const`; it answers the slot
|
||||
// used directly, with no out region.
|
||||
struct CacheLedgerObjCall : HostContextTest
|
||||
{
|
||||
Bytes const objIdBytes = Bytes(uint256::size(), 0x33);
|
||||
uint256 const objId = uint256::fromVoid(objIdBytes.data());
|
||||
};
|
||||
|
||||
TEST_F(CacheLedgerObjCall, ObjIdAndCacheIdxForwardedSlotIsReturned)
|
||||
{
|
||||
EXPECT_CALL(host, cacheLedgerObj(testing::Eq(objId), 5)).WillOnce(testing::Return(7));
|
||||
|
||||
EXPECT_EQ(hostContext.cacheLedgerObj(bytesOf(objIdBytes), 5), 7);
|
||||
}
|
||||
|
||||
TEST_F(CacheLedgerObjCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, cacheLedgerObj(testing::Eq(objId), 5))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::SlotsFull)));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.cacheLedgerObj(bytesOf(objIdBytes), 5),
|
||||
hfErrorToInt(HostFunctionError::SlotsFull));
|
||||
}
|
||||
|
||||
TEST_F(CacheLedgerObjCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, cacheLedgerObj(testing::Eq(objId), 5))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"cache slot came apart"}));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.cacheLedgerObj(bytesOf(objIdBytes), 5),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("cache slot came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("cacheLedgerObj"));
|
||||
}
|
||||
|
||||
TEST_F(CacheLedgerObjCall, MalformedObjIdIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformed(uint256::size() - 1, 0x33);
|
||||
EXPECT_CALL(host, cacheLedgerObj).Times(0);
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.cacheLedgerObj(bytesOf(malformed), 5),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// 0 selects a free slot at the host - a meaningful argument here, not an absent one - and must
|
||||
// still cross unchanged.
|
||||
TEST_F(CacheLedgerObjCall, ZeroCacheIdxIsForwardedVerbatim)
|
||||
{
|
||||
EXPECT_CALL(host, cacheLedgerObj(testing::Eq(objId), 0)).WillOnce(testing::Return(0));
|
||||
|
||||
EXPECT_EQ(hostContext.cacheLedgerObj(bytesOf(objIdBytes), 0), 0);
|
||||
}
|
||||
|
||||
// Unlike `seq` elsewhere in this file's shape family, `cacheIdx` is not reinterpreted as
|
||||
// unsigned: a negative value reaches the host as itself.
|
||||
TEST_F(CacheLedgerObjCall, NegativeCacheIdxIsForwardedVerbatim)
|
||||
{
|
||||
EXPECT_CALL(host, cacheLedgerObj(testing::Eq(objId), -1))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::SlotOutRange)));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.cacheLedgerObj(bytesOf(objIdBytes), -1),
|
||||
hfErrorToInt(HostFunctionError::SlotOutRange));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
131
src/tests/libxrpl/tx/wasm/host_context/CheckKeylet.cpp
Normal file
131
src/tests/libxrpl/tx/wasm/host_context/CheckKeylet.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
struct CheckKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const accountBytes{0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
|
||||
0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54};
|
||||
AccountID const account = AccountID::fromVoid(accountBytes.data());
|
||||
std::int32_t const seq = 54321;
|
||||
};
|
||||
|
||||
TEST_F(CheckKeyletCall, AccountAndSeqAreForwardedKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, checkKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.checkKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(CheckKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, checkKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.checkKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(CheckKeyletCall, ShortAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const shortAccount(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, checkKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.checkKeylet(bytesOf(shortAccount), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(CheckKeyletCall, LongAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const longAccount(AccountID::size() + 1, 0x01);
|
||||
EXPECT_CALL(host, checkKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.checkKeylet(bytesOf(longAccount), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(CheckKeyletCall, EmptyAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, checkKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.checkKeylet(bytesOf(Bytes{}), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(CheckKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, checkKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"check keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.checkKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("check keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("checkKeylet"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(CheckKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, checkKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.checkKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(CheckKeyletCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, checkKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.checkKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(CheckKeyletCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, checkKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.checkKeylet(bytesOf(accountBytes), seq, out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
63
src/tests/libxrpl/tx/wasm/host_context/CheckSignature.cpp
Normal file
63
src/tests/libxrpl/tx/wasm/host_context/CheckSignature.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
#include <tx/wasm/MockHostFunctions.h>
|
||||
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// `checkSignature` validates nothing: message, signature and pubkey reach the host exactly as
|
||||
// given, with no length check on any of them - deliberately, not by oversight.
|
||||
struct CheckSignatureCall : HostContextTest
|
||||
{
|
||||
Bytes const message{'m', 's', 'g'};
|
||||
Bytes const signature{'s', 'i', 'g'};
|
||||
Bytes const pubkey{'k', 'e', 'y'};
|
||||
};
|
||||
|
||||
TEST_F(CheckSignatureCall, MessageSignatureAndPubkeyForwardedVerbatim)
|
||||
{
|
||||
EXPECT_CALL(host, checkSignature(BytesAre("msg"), BytesAre("sig"), BytesAre("key")))
|
||||
.WillOnce(testing::Return(1));
|
||||
|
||||
EXPECT_EQ(hostContext.checkSignature(bytesOf(message), bytesOf(signature), bytesOf(pubkey)), 1);
|
||||
}
|
||||
|
||||
// The absence of any length check is a decision, not an oversight: empty slices are not a
|
||||
// malformed shape here, they reach the host like any other.
|
||||
TEST_F(CheckSignatureCall, EmptySlicesReachHostUnvalidated)
|
||||
{
|
||||
auto const isEmpty = testing::Property(&Slice::empty, true);
|
||||
EXPECT_CALL(host, checkSignature(isEmpty, isEmpty, isEmpty)).WillOnce(testing::Return(0));
|
||||
|
||||
EXPECT_EQ(hostContext.checkSignature(bytesOf(Bytes{}), bytesOf(Bytes{}), bytesOf(Bytes{})), 0);
|
||||
}
|
||||
|
||||
TEST_F(CheckSignatureCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, checkSignature(BytesAre("msg"), BytesAre("sig"), BytesAre("key")))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::InvalidParams)));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.checkSignature(bytesOf(message), bytesOf(signature), bytesOf(pubkey)),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(CheckSignatureCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, checkSignature(BytesAre("msg"), BytesAre("sig"), BytesAre("key")))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"signature check came apart"}));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.checkSignature(bytesOf(message), bytesOf(signature), bytesOf(pubkey)),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("signature check came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("checkSignature"));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
179
src/tests/libxrpl/tx/wasm/host_context/CredentialKeylet.cpp
Normal file
179
src/tests/libxrpl/tx/wasm/host_context/CredentialKeylet.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
//
|
||||
// `subject` and `issuer` are distinct byte patterns: a happy path built from two copies of the
|
||||
// same account would still pass if the two were swapped.
|
||||
struct CredentialKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const subjectBytes{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
|
||||
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14};
|
||||
Bytes const issuerBytes{0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
|
||||
0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54};
|
||||
Bytes const credentialTypeBytes{0x74, 0x65, 0x72, 0x6d, 0x73};
|
||||
AccountID const subject = AccountID::fromVoid(subjectBytes.data());
|
||||
AccountID const issuer = AccountID::fromVoid(issuerBytes.data());
|
||||
Slice const credentialType{credentialTypeBytes.data(), credentialTypeBytes.size()};
|
||||
};
|
||||
|
||||
// `credentialType` crosses unvalidated: whatever bytes the guest gives reach the host as-is.
|
||||
TEST_F(CredentialKeyletCall, SubjectAndIssuerAreForwardedCredentialTypeUnvalidatedKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, credentialKeylet(subject, issuer, credentialType))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.credentialKeylet(
|
||||
bytesOf(subjectBytes), bytesOf(issuerBytes), bytesOf(credentialTypeBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
// The deliberate edge of "unvalidated": an empty `credentialType` is not a length the ABI
|
||||
// rejects, so it reaches the host as an empty `Slice` and the call still succeeds.
|
||||
TEST_F(CredentialKeyletCall, EmptyCredentialTypeIsForwardedUnvalidatedKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, credentialKeylet(subject, issuer, Slice{})).WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.credentialKeylet(
|
||||
bytesOf(subjectBytes), bytesOf(issuerBytes), bytesOf(Bytes{}), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(CredentialKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, credentialKeylet(subject, issuer, credentialType))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.credentialKeylet(
|
||||
bytesOf(subjectBytes), bytesOf(issuerBytes), bytesOf(credentialTypeBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(CredentialKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, credentialKeylet(subject, issuer, credentialType))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"credential keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.credentialKeylet(
|
||||
bytesOf(subjectBytes), bytesOf(issuerBytes), bytesOf(credentialTypeBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("credential keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("credentialKeylet"));
|
||||
}
|
||||
|
||||
TEST_F(CredentialKeyletCall, MalformedSubjectIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedSubject(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, credentialKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.credentialKeylet(
|
||||
bytesOf(malformedSubject),
|
||||
bytesOf(issuerBytes),
|
||||
bytesOf(credentialTypeBytes),
|
||||
out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(CredentialKeyletCall, MalformedIssuerIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedIssuer(AccountID::size() + 1, 0x41);
|
||||
EXPECT_CALL(host, credentialKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.credentialKeylet(
|
||||
bytesOf(subjectBytes),
|
||||
bytesOf(malformedIssuer),
|
||||
bytesOf(credentialTypeBytes),
|
||||
out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// Both ids fail one combined length check, so a call malformed in both places answers the
|
||||
// same `InvalidParams` as either alone; what's observable is that the host is never asked.
|
||||
TEST_F(CredentialKeyletCall, BothAccountsMalformedIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedSubject(AccountID::size() - 1, 0x01);
|
||||
Bytes const malformedIssuer(AccountID::size() - 1, 0x41);
|
||||
EXPECT_CALL(host, credentialKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.credentialKeylet(
|
||||
bytesOf(malformedSubject),
|
||||
bytesOf(malformedIssuer),
|
||||
bytesOf(credentialTypeBytes),
|
||||
out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(CredentialKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, credentialKeylet(subject, issuer, credentialType))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.credentialKeylet(
|
||||
bytesOf(subjectBytes), bytesOf(issuerBytes), bytesOf(credentialTypeBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(CredentialKeyletCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, credentialKeylet(subject, issuer, credentialType))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.credentialKeylet(
|
||||
bytesOf(subjectBytes), bytesOf(issuerBytes), bytesOf(credentialTypeBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(CredentialKeyletCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, credentialKeylet(subject, issuer, credentialType))
|
||||
.WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.credentialKeylet(
|
||||
bytesOf(subjectBytes), bytesOf(issuerBytes), bytesOf(credentialTypeBytes), out.slice()),
|
||||
0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -0,0 +1,63 @@
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// `getCurrentLedgerObjArrayLen` answers its count directly rather than through an out region:
|
||||
// no axis E, no `OutRegion`, and the happy path asserts the returned count.
|
||||
struct CurrentLedgerObjArrayLenCall : HostContextTest
|
||||
{
|
||||
std::int32_t fieldCode = sfBalance.getCode();
|
||||
};
|
||||
|
||||
TEST_F(CurrentLedgerObjArrayLenCall, FieldCodeBecomesSFieldHostIsAskedFor)
|
||||
{
|
||||
EXPECT_CALL(host, getCurrentLedgerObjArrayLen(testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(5));
|
||||
|
||||
EXPECT_EQ(hostContext.getCurrentLedgerObjArrayLen(fieldCode), 5);
|
||||
}
|
||||
|
||||
// `NoArray` is what a field that is not an array actually answers, so it stands in for axis B
|
||||
// here rather than an arbitrary code.
|
||||
TEST_F(CurrentLedgerObjArrayLenCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getCurrentLedgerObjArrayLen(testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::NoArray)));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjArrayLen(fieldCode),
|
||||
hfErrorToInt(HostFunctionError::NoArray));
|
||||
}
|
||||
|
||||
TEST_F(CurrentLedgerObjArrayLenCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getCurrentLedgerObjArrayLen(testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"current ledger obj array len came apart"}));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjArrayLen(fieldCode),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("current ledger obj array len came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getCurrentLedgerObjArrayLen"));
|
||||
}
|
||||
|
||||
TEST_F(CurrentLedgerObjArrayLenCall, UnknownFieldCodeIsRefusedWithoutAskingHost)
|
||||
{
|
||||
fieldCode = 0x7fff'0000; // a code nothing is registered under
|
||||
EXPECT_CALL(host, getCurrentLedgerObjArrayLen).Times(0);
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjArrayLen(fieldCode),
|
||||
hfErrorToInt(HostFunctionError::InvalidField));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
112
src/tests/libxrpl/tx/wasm/host_context/CurrentLedgerObjField.cpp
Normal file
112
src/tests/libxrpl/tx/wasm/host_context/CurrentLedgerObjField.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, the field cap, guest memory - are tested on the Rust
|
||||
// side, not here. The cross-cutting cases over this shape - a non-`std::exception` throw, and
|
||||
// a length past `kMaxWasmDataLength` - already live in `TxField.cpp`.
|
||||
//
|
||||
// Named `CurrentLedgerObjFieldDirectCall`, not `CurrentLedgerObjFieldCall`:
|
||||
// `host_calls/CurrentLedgerObjField.cpp` already owns that name in the same gtest binary.
|
||||
struct CurrentLedgerObjFieldDirectCall : HostContextTest
|
||||
{
|
||||
std::int32_t fieldCode = sfBalance.getCode();
|
||||
};
|
||||
|
||||
TEST_F(CurrentLedgerObjFieldDirectCall, FieldCodeBecomesSFieldHostIsAskedFor)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getCurrentLedgerObjField(testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjField(fieldCode, out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(value)));
|
||||
}
|
||||
|
||||
TEST_F(CurrentLedgerObjFieldDirectCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getCurrentLedgerObjField(testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::FieldNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjField(fieldCode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::FieldNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(CurrentLedgerObjFieldDirectCall, UnknownFieldCodeIsRefusedWithoutAskingHost)
|
||||
{
|
||||
fieldCode = 0x7fff'0000; // a code nothing is registered under
|
||||
EXPECT_CALL(host, getCurrentLedgerObjField).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjField(fieldCode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidField));
|
||||
}
|
||||
|
||||
TEST_F(CurrentLedgerObjFieldDirectCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getCurrentLedgerObjField(testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"current ledger obj field came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjField(fieldCode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("current ledger obj field came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getCurrentLedgerObjField"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(CurrentLedgerObjFieldDirectCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getCurrentLedgerObjField(testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{value.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjField(fieldCode, out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(CurrentLedgerObjFieldDirectCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getCurrentLedgerObjField(testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{value.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjField(fieldCode, out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(value)));
|
||||
}
|
||||
|
||||
TEST_F(CurrentLedgerObjFieldDirectCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, getCurrentLedgerObjField(testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.getCurrentLedgerObjField(fieldCode, out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -0,0 +1,78 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, the field cap, guest memory - are tested on the Rust
|
||||
// side, not here.
|
||||
//
|
||||
// No out region and no axis E: `getCurrentLedgerObjNestedArrayLen` answers the array's
|
||||
// element count directly rather than through a written buffer.
|
||||
struct CurrentLedgerObjNestedArrayLenCall : HostContextTest
|
||||
{
|
||||
std::vector<std::int32_t> const steps{5, -12, 130};
|
||||
Bytes const locatorBytes = bytesOfSteps(steps);
|
||||
};
|
||||
|
||||
TEST_F(CurrentLedgerObjNestedArrayLenCall, LocatorBytesBecomeFieldLocatorHostReturnsCount)
|
||||
{
|
||||
EXPECT_CALL(host, getCurrentLedgerObjNestedArrayLen(LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(7));
|
||||
|
||||
EXPECT_EQ(hostContext.getCurrentLedgerObjNestedArrayLen(bytesOf(locatorBytes)), 7);
|
||||
}
|
||||
|
||||
// `NoArray` - the field the locator resolves to is not an array - is the error this shape
|
||||
// most plausibly returns, so it stands in for axis B.
|
||||
TEST_F(CurrentLedgerObjNestedArrayLenCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getCurrentLedgerObjNestedArrayLen(LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::NoArray)));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjNestedArrayLen(bytesOf(locatorBytes)),
|
||||
hfErrorToInt(HostFunctionError::NoArray));
|
||||
}
|
||||
|
||||
TEST_F(CurrentLedgerObjNestedArrayLenCall, EmptyLocatorIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, getCurrentLedgerObjNestedArrayLen).Times(0);
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjNestedArrayLen(bytesOf(Bytes{})),
|
||||
hfErrorToInt(HostFunctionError::LocatorMalformed));
|
||||
}
|
||||
|
||||
// Distinct from an empty locator: `invokeWithLocator` checks the two conditions separately.
|
||||
TEST_F(CurrentLedgerObjNestedArrayLenCall, MisalignedLocatorLengthIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const oddLength{1, 2, 3};
|
||||
EXPECT_CALL(host, getCurrentLedgerObjNestedArrayLen).Times(0);
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjNestedArrayLen(bytesOf(oddLength)),
|
||||
hfErrorToInt(HostFunctionError::LocatorMalformed));
|
||||
}
|
||||
|
||||
TEST_F(CurrentLedgerObjNestedArrayLenCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getCurrentLedgerObjNestedArrayLen(LocatorEquals(steps)))
|
||||
.WillOnce(
|
||||
testing::Throw(std::runtime_error{"current ledger obj nested array len came apart"}));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjNestedArrayLen(bytesOf(locatorBytes)),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("current ledger obj nested array len came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getCurrentLedgerObjNestedArrayLen"));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -0,0 +1,121 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, the field cap, guest memory - are tested on the Rust
|
||||
// side, not here.
|
||||
struct CurrentLedgerObjNestedFieldCall : HostContextTest
|
||||
{
|
||||
std::vector<std::int32_t> const steps{5, -12, 130};
|
||||
Bytes const locatorBytes = bytesOfSteps(steps);
|
||||
};
|
||||
|
||||
TEST_F(CurrentLedgerObjNestedFieldCall, LocatorBytesBecomeFieldLocatorHostIsAskedFor)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getCurrentLedgerObjNestedField(LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjNestedField(bytesOf(locatorBytes), out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(value)));
|
||||
}
|
||||
|
||||
TEST_F(CurrentLedgerObjNestedFieldCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getCurrentLedgerObjNestedField(LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::NotLeafField)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjNestedField(bytesOf(locatorBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::NotLeafField));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(CurrentLedgerObjNestedFieldCall, EmptyLocatorIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, getCurrentLedgerObjNestedField).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjNestedField(bytesOf(Bytes{}), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LocatorMalformed));
|
||||
}
|
||||
|
||||
// Distinct from an empty locator: `invokeWithLocator` checks the two conditions separately.
|
||||
TEST_F(CurrentLedgerObjNestedFieldCall, MisalignedLocatorLengthIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const oddLength{1, 2, 3};
|
||||
EXPECT_CALL(host, getCurrentLedgerObjNestedField).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjNestedField(bytesOf(oddLength), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LocatorMalformed));
|
||||
}
|
||||
|
||||
TEST_F(CurrentLedgerObjNestedFieldCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getCurrentLedgerObjNestedField(LocatorEquals(steps)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"current ledger obj nested field came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjNestedField(bytesOf(locatorBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("current ledger obj nested field came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getCurrentLedgerObjNestedField"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(CurrentLedgerObjNestedFieldCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getCurrentLedgerObjNestedField(LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{value.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjNestedField(bytesOf(locatorBytes), out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(CurrentLedgerObjNestedFieldCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getCurrentLedgerObjNestedField(LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{value.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.getCurrentLedgerObjNestedField(bytesOf(locatorBytes), out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(value)));
|
||||
}
|
||||
|
||||
TEST_F(CurrentLedgerObjNestedFieldCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, getCurrentLedgerObjNestedField(LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.getCurrentLedgerObjNestedField(bytesOf(locatorBytes), out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
138
src/tests/libxrpl/tx/wasm/host_context/DelegateKeylet.cpp
Normal file
138
src/tests/libxrpl/tx/wasm/host_context/DelegateKeylet.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
//
|
||||
// `account` and `authorize` are distinct byte patterns: a happy path built from two copies of
|
||||
// the same account would still pass if the two were swapped.
|
||||
struct DelegateKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const accountBytes{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
|
||||
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14};
|
||||
Bytes const authorizeBytes{0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
|
||||
0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4};
|
||||
AccountID const account = AccountID::fromVoid(accountBytes.data());
|
||||
AccountID const authorize = AccountID::fromVoid(authorizeBytes.data());
|
||||
};
|
||||
|
||||
TEST_F(DelegateKeyletCall, AccountAndAuthorizeAreForwardedInOrderKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, delegateKeylet(account, authorize)).WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.delegateKeylet(bytesOf(accountBytes), bytesOf(authorizeBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(DelegateKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, delegateKeylet(account, authorize))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.delegateKeylet(bytesOf(accountBytes), bytesOf(authorizeBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(DelegateKeyletCall, MalformedAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedAccount(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, delegateKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.delegateKeylet(bytesOf(malformedAccount), bytesOf(authorizeBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(DelegateKeyletCall, MalformedAuthorizeIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedAuthorize(AccountID::size() + 1, 0xe1);
|
||||
EXPECT_CALL(host, delegateKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.delegateKeylet(bytesOf(accountBytes), bytesOf(malformedAuthorize), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// Both ids fail one combined length check, so a call malformed in both places answers the
|
||||
// same `InvalidParams` as either alone; what's observable is that the host is never asked.
|
||||
TEST_F(DelegateKeyletCall, BothAccountsMalformedIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedAccount(AccountID::size() - 1, 0x01);
|
||||
Bytes const malformedAuthorize(AccountID::size() - 1, 0xe1);
|
||||
EXPECT_CALL(host, delegateKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.delegateKeylet(
|
||||
bytesOf(malformedAccount), bytesOf(malformedAuthorize), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(DelegateKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, delegateKeylet(account, authorize))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"delegate keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.delegateKeylet(bytesOf(accountBytes), bytesOf(authorizeBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("delegate keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("delegateKeylet"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(DelegateKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, delegateKeylet(account, authorize)).WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.delegateKeylet(bytesOf(accountBytes), bytesOf(authorizeBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(DelegateKeyletCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, delegateKeylet(account, authorize)).WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.delegateKeylet(bytesOf(accountBytes), bytesOf(authorizeBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(DelegateKeyletCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, delegateKeylet(account, authorize)).WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.delegateKeylet(bytesOf(accountBytes), bytesOf(authorizeBytes), out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
147
src/tests/libxrpl/tx/wasm/host_context/DepositPreauthKeylet.cpp
Normal file
147
src/tests/libxrpl/tx/wasm/host_context/DepositPreauthKeylet.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
//
|
||||
// `account` and `authorize` are distinct byte patterns: a happy path built from two copies of
|
||||
// the same account would still pass if the two were swapped.
|
||||
struct DepositPreauthKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const accountBytes{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
|
||||
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14};
|
||||
Bytes const authorizeBytes{0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a,
|
||||
0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4};
|
||||
AccountID const account = AccountID::fromVoid(accountBytes.data());
|
||||
AccountID const authorize = AccountID::fromVoid(authorizeBytes.data());
|
||||
};
|
||||
|
||||
TEST_F(DepositPreauthKeyletCall, AccountAndAuthorizeAreForwardedInOrderKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, depositPreauthKeylet(account, authorize)).WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.depositPreauthKeylet(
|
||||
bytesOf(accountBytes), bytesOf(authorizeBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(DepositPreauthKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, depositPreauthKeylet(account, authorize))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.depositPreauthKeylet(
|
||||
bytesOf(accountBytes), bytesOf(authorizeBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(DepositPreauthKeyletCall, MalformedAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedAccount(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, depositPreauthKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.depositPreauthKeylet(
|
||||
bytesOf(malformedAccount), bytesOf(authorizeBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(DepositPreauthKeyletCall, MalformedAuthorizeIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedAuthorize(AccountID::size() + 1, 0x91);
|
||||
EXPECT_CALL(host, depositPreauthKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.depositPreauthKeylet(
|
||||
bytesOf(accountBytes), bytesOf(malformedAuthorize), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// Both ids fail one combined length check, so a call malformed in both places answers the
|
||||
// same `InvalidParams` as either alone; what's observable is that the host is never asked.
|
||||
TEST_F(DepositPreauthKeyletCall, BothAccountsMalformedIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedAccount(AccountID::size() - 1, 0x01);
|
||||
Bytes const malformedAuthorize(AccountID::size() - 1, 0x91);
|
||||
EXPECT_CALL(host, depositPreauthKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.depositPreauthKeylet(
|
||||
bytesOf(malformedAccount), bytesOf(malformedAuthorize), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(DepositPreauthKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, depositPreauthKeylet(account, authorize))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"deposit preauth keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.depositPreauthKeylet(
|
||||
bytesOf(accountBytes), bytesOf(authorizeBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("deposit preauth keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("depositPreauthKeylet"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(DepositPreauthKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, depositPreauthKeylet(account, authorize)).WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.depositPreauthKeylet(
|
||||
bytesOf(accountBytes), bytesOf(authorizeBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(DepositPreauthKeyletCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, depositPreauthKeylet(account, authorize)).WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.depositPreauthKeylet(
|
||||
bytesOf(accountBytes), bytesOf(authorizeBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(DepositPreauthKeyletCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, depositPreauthKeylet(account, authorize)).WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.depositPreauthKeylet(
|
||||
bytesOf(accountBytes), bytesOf(authorizeBytes), out.slice()),
|
||||
0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
126
src/tests/libxrpl/tx/wasm/host_context/DidKeylet.cpp
Normal file
126
src/tests/libxrpl/tx/wasm/host_context/DidKeylet.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
struct DidKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const accountBytes{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a,
|
||||
0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44};
|
||||
AccountID const account = AccountID::fromVoid(accountBytes.data());
|
||||
};
|
||||
|
||||
TEST_F(DidKeyletCall, AccountIsForwardedKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, didKeylet(account)).WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.didKeylet(bytesOf(accountBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(DidKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, didKeylet(account))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.didKeylet(bytesOf(accountBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(DidKeyletCall, ShortAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const shortAccount(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, didKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.didKeylet(bytesOf(shortAccount), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(DidKeyletCall, LongAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const longAccount(AccountID::size() + 1, 0x01);
|
||||
EXPECT_CALL(host, didKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.didKeylet(bytesOf(longAccount), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(DidKeyletCall, EmptyAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, didKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.didKeylet(bytesOf(Bytes{}), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(DidKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, didKeylet(account))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"did keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.didKeylet(bytesOf(accountBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("did keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("didKeylet"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(DidKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, didKeylet(account)).WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.didKeylet(bytesOf(accountBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(DidKeyletCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, didKeylet(account)).WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.didKeylet(bytesOf(accountBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(DidKeyletCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, didKeylet(account)).WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.didKeylet(bytesOf(accountBytes), out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
152
src/tests/libxrpl/tx/wasm/host_context/EscrowKeylet.cpp
Normal file
152
src/tests/libxrpl/tx/wasm/host_context/EscrowKeylet.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
//
|
||||
// The first file over the account-in, keylet-out shape `invokeWithAccount` gives eleven other
|
||||
// methods, so `account` is a distinctive 20 bytes rather than all-zero: a forwarding mistake
|
||||
// (a swapped byte, a truncated copy) would still pass against an all-zero id.
|
||||
struct EscrowKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const accountBytes{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
|
||||
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14};
|
||||
AccountID const account = AccountID::fromVoid(accountBytes.data());
|
||||
std::int32_t const seq = 12345;
|
||||
};
|
||||
|
||||
TEST_F(EscrowKeyletCall, AccountAndSeqAreForwardedKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, escrowKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.escrowKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(EscrowKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, escrowKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.escrowKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(EscrowKeyletCall, ShortAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const shortAccount(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, escrowKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.escrowKeylet(bytesOf(shortAccount), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(EscrowKeyletCall, LongAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const longAccount(AccountID::size() + 1, 0x01);
|
||||
EXPECT_CALL(host, escrowKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.escrowKeylet(bytesOf(longAccount), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(EscrowKeyletCall, EmptyAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, escrowKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.escrowKeylet(bytesOf(Bytes{}), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(EscrowKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, escrowKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"escrow keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.escrowKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("escrow keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("escrowKeylet"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(EscrowKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, escrowKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.escrowKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(EscrowKeyletCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, escrowKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.escrowKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(EscrowKeyletCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, escrowKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.escrowKeylet(bytesOf(accountBytes), seq, out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
// `seq` crosses the ABI as an `i32` bit pattern, not a signed count: the guest's
|
||||
// `4294967295u` is this `-1`, and `escrowKeylet` must hand the host back `4294967295u`, not a
|
||||
// sign-extended or clamped value.
|
||||
TEST_F(EscrowKeyletCall, NegativeSeqArrivesAtHostAsUnsignedBitPattern)
|
||||
{
|
||||
std::int32_t const negativeSeq = -1;
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, escrowKeylet(account, std::numeric_limits<std::uint32_t>::max()))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.escrowKeylet(bytesOf(accountBytes), negativeSeq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
89
src/tests/libxrpl/tx/wasm/host_context/FloatAdd.cpp
Normal file
89
src/tests/libxrpl/tx/wasm/host_context/FloatAdd.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
#include <tx/wasm/MockHostFunctions.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// Every input slice passes straight through to the host, unlike `invokeWithAccount`'s
|
||||
// twenty-byte check or `parseUint64`'s eight: nothing here is validated, so there is no D
|
||||
// axis. `x` and `y` carry different content, so a call that swapped them would fail to match.
|
||||
struct FloatAddCall : HostContextTest
|
||||
{
|
||||
Bytes const x{'a', 'd', 'd', '-', 'x'};
|
||||
Bytes const y{'a', 'd', 'd', '-', 'y', 'y'};
|
||||
std::int32_t const mode = 7;
|
||||
};
|
||||
|
||||
TEST_F(FloatAddCall, OperandsAndModeAreForwardedResultIsWritten)
|
||||
{
|
||||
Bytes const result{9, 8, 7};
|
||||
EXPECT_CALL(host, floatAdd(BytesAre("add-x"), BytesAre("add-yy"), mode))
|
||||
.WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatAdd(bytesOf(x), bytesOf(y), mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(result)));
|
||||
}
|
||||
|
||||
TEST_F(FloatAddCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, floatAdd(BytesAre("add-x"), BytesAre("add-yy"), mode))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::FloatComputationError)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatAdd(bytesOf(x), bytesOf(y), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::FloatComputationError));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(FloatAddCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, floatAdd(BytesAre("add-x"), BytesAre("add-yy"), mode))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"float add came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatAdd(bytesOf(x), bytesOf(y), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("float add came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("floatAdd"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(FloatAddCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const result{9, 8, 7};
|
||||
EXPECT_CALL(host, floatAdd(BytesAre("add-x"), BytesAre("add-yy"), mode))
|
||||
.WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{result.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatAdd(bytesOf(x), bytesOf(y), mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
// No length rule exists at this layer: a differently sized operand still reaches the host
|
||||
// rather than being refused.
|
||||
TEST_F(FloatAddCall, OddSizedOperandReachesHostUnchanged)
|
||||
{
|
||||
Bytes const shortX{0x2a};
|
||||
EXPECT_CALL(host, floatAdd(testing::_, BytesAre("add-yy"), mode))
|
||||
.WillOnce(testing::Return(Bytes{1}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.floatAdd(bytesOf(shortX), bytesOf(y), mode, out.slice()), 1);
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
64
src/tests/libxrpl/tx/wasm/host_context/FloatCompare.cpp
Normal file
64
src/tests/libxrpl/tx/wasm/host_context/FloatCompare.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
#include <tx/wasm/MockHostFunctions.h>
|
||||
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// Every input slice passes straight through to the host, unlike `invokeWithAccount`'s
|
||||
// twenty-byte check or `parseUint64`'s eight: nothing here is validated, so there is no D axis.
|
||||
// `x` and `y` carry different content, so a call that swapped them would fail to match.
|
||||
// `floatCompare` answers its comparison directly rather than through `answer`, so there is no
|
||||
// out region and no axis E.
|
||||
struct FloatCompareCall : HostContextTest
|
||||
{
|
||||
Bytes const x{'c', 'm', 'p', '-', 'x'};
|
||||
Bytes const y{'c', 'm', 'p', '-', 'y', 'y'};
|
||||
};
|
||||
|
||||
TEST_F(FloatCompareCall, XAndYAreForwardedResultReturnedDirectly)
|
||||
{
|
||||
EXPECT_CALL(host, floatCompare(BytesAre("cmp-x"), BytesAre("cmp-yy")))
|
||||
.WillOnce(testing::Return(1));
|
||||
|
||||
EXPECT_EQ(hostContext.floatCompare(bytesOf(x), bytesOf(y)), 1);
|
||||
}
|
||||
|
||||
TEST_F(FloatCompareCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, floatCompare(BytesAre("cmp-x"), BytesAre("cmp-yy")))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::FloatComputationError)));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.floatCompare(bytesOf(x), bytesOf(y)),
|
||||
hfErrorToInt(HostFunctionError::FloatComputationError));
|
||||
}
|
||||
|
||||
TEST_F(FloatCompareCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, floatCompare(BytesAre("cmp-x"), BytesAre("cmp-yy")))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"float compare came apart"}));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.floatCompare(bytesOf(x), bytesOf(y)),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("float compare came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("floatCompare"));
|
||||
}
|
||||
|
||||
// No length rule exists at this layer: a differently sized operand still reaches the host
|
||||
// rather than being refused.
|
||||
TEST_F(FloatCompareCall, OddSizedOperandReachesHostUnchanged)
|
||||
{
|
||||
Bytes const oddX{0x2a};
|
||||
EXPECT_CALL(host, floatCompare(testing::_, BytesAre("cmp-yy"))).WillOnce(testing::Return(0));
|
||||
|
||||
EXPECT_EQ(hostContext.floatCompare(bytesOf(oddX), bytesOf(y)), 0);
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
89
src/tests/libxrpl/tx/wasm/host_context/FloatDivide.cpp
Normal file
89
src/tests/libxrpl/tx/wasm/host_context/FloatDivide.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
#include <tx/wasm/MockHostFunctions.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// Every input slice passes straight through to the host, unlike `invokeWithAccount`'s
|
||||
// twenty-byte check or `parseUint64`'s eight: nothing here is validated, so there is no D
|
||||
// axis. `x` and `y` carry different content, so a call that swapped them would fail to match.
|
||||
struct FloatDivideCall : HostContextTest
|
||||
{
|
||||
Bytes const x{'d', 'i', 'v', '-', 'x'};
|
||||
Bytes const y{'d', 'i', 'v', '-', 'y', 'y'};
|
||||
std::int32_t const mode = 42;
|
||||
};
|
||||
|
||||
TEST_F(FloatDivideCall, OperandsAndModeAreForwardedResultIsWritten)
|
||||
{
|
||||
Bytes const result{9, 8, 7};
|
||||
EXPECT_CALL(host, floatDivide(BytesAre("div-x"), BytesAre("div-yy"), mode))
|
||||
.WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatDivide(bytesOf(x), bytesOf(y), mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(result)));
|
||||
}
|
||||
|
||||
TEST_F(FloatDivideCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, floatDivide(BytesAre("div-x"), BytesAre("div-yy"), mode))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::FloatComputationError)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatDivide(bytesOf(x), bytesOf(y), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::FloatComputationError));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(FloatDivideCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, floatDivide(BytesAre("div-x"), BytesAre("div-yy"), mode))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"float divide came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatDivide(bytesOf(x), bytesOf(y), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("float divide came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("floatDivide"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(FloatDivideCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const result{9, 8, 7};
|
||||
EXPECT_CALL(host, floatDivide(BytesAre("div-x"), BytesAre("div-yy"), mode))
|
||||
.WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{result.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatDivide(bytesOf(x), bytesOf(y), mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
// No length rule exists at this layer: a differently sized operand still reaches the host
|
||||
// rather than being refused.
|
||||
TEST_F(FloatDivideCall, OddSizedOperandReachesHostUnchanged)
|
||||
{
|
||||
Bytes const shortX{0x2a};
|
||||
EXPECT_CALL(host, floatDivide(testing::_, BytesAre("div-yy"), mode))
|
||||
.WillOnce(testing::Return(Bytes{1}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.floatDivide(bytesOf(shortX), bytesOf(y), mode, out.slice()), 1);
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
84
src/tests/libxrpl/tx/wasm/host_context/FloatFromInt.cpp
Normal file
84
src/tests/libxrpl/tx/wasm/host_context/FloatFromInt.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// `x` arrives as a wasm scalar, not as bytes to decode, so there is nothing here to get wrong
|
||||
// about its shape - no D axis.
|
||||
struct FloatFromIntCall : HostContextTest
|
||||
{
|
||||
std::int64_t const x = 123456789;
|
||||
std::int32_t const mode = 1;
|
||||
};
|
||||
|
||||
TEST_F(FloatFromIntCall, ValueAndModeAreForwardedResultIsWritten)
|
||||
{
|
||||
Bytes const result{1, 2, 3};
|
||||
EXPECT_CALL(host, floatFromInt(x, mode)).WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromInt(x, mode, out.slice()), static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(result)));
|
||||
}
|
||||
|
||||
// `mode` is forwarded verbatim: this layer validates nothing about it, so a nonsense value
|
||||
// still reaches the host unchanged.
|
||||
TEST_F(FloatFromIntCall, ModeIsForwardedVerbatim)
|
||||
{
|
||||
std::int32_t const nonsenseMode = -12345;
|
||||
Bytes const result{1};
|
||||
EXPECT_CALL(host, floatFromInt(x, nonsenseMode)).WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromInt(x, nonsenseMode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
}
|
||||
|
||||
TEST_F(FloatFromIntCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, floatFromInt(x, mode))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::FloatComputationError)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromInt(x, mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::FloatComputationError));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(FloatFromIntCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, floatFromInt(x, mode))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"float from int came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromInt(x, mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("float from int came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("floatFromInt"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(FloatFromIntCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const result{1, 2, 3};
|
||||
EXPECT_CALL(host, floatFromInt(x, mode)).WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{result.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromInt(x, mode, out.slice()), static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
73
src/tests/libxrpl/tx/wasm/host_context/FloatFromMantExp.cpp
Normal file
73
src/tests/libxrpl/tx/wasm/host_context/FloatFromMantExp.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// `mantissa`, `exponent` and `mode` all arrive as wasm scalars, not as bytes to decode, so
|
||||
// there is nothing here to get wrong about their shape - no D axis.
|
||||
struct FloatFromMantExpCall : HostContextTest
|
||||
{
|
||||
std::int64_t const mantissa = 123456789;
|
||||
std::int32_t const exponent = -5;
|
||||
std::int32_t const mode = 1;
|
||||
};
|
||||
|
||||
TEST_F(FloatFromMantExpCall, MantissaExponentAndModeAreForwardedResultIsWritten)
|
||||
{
|
||||
Bytes const result{1, 2, 3};
|
||||
EXPECT_CALL(host, floatFromMantExp(mantissa, exponent, mode)).WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromMantExp(mantissa, exponent, mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(result)));
|
||||
}
|
||||
|
||||
TEST_F(FloatFromMantExpCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, floatFromMantExp(mantissa, exponent, mode))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::FloatComputationError)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromMantExp(mantissa, exponent, mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::FloatComputationError));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(FloatFromMantExpCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, floatFromMantExp(mantissa, exponent, mode))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"float from mant exp came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromMantExp(mantissa, exponent, mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("float from mant exp came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("floatFromMantExp"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(FloatFromMantExpCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const result{1, 2, 3};
|
||||
EXPECT_CALL(host, floatFromMantExp(mantissa, exponent, mode)).WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{result.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromMantExp(mantissa, exponent, mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
102
src/tests/libxrpl/tx/wasm/host_context/FloatFromSTAmount.cpp
Normal file
102
src/tests/libxrpl/tx/wasm/host_context/FloatFromSTAmount.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
namespace {
|
||||
|
||||
Bytes
|
||||
serialized(STAmount const& amount)
|
||||
{
|
||||
Serializer s;
|
||||
amount.add(s);
|
||||
return s.getData();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// The only file exercising `parseST<STAmount>`. A malformed buffer throws inside `STAmount`'s
|
||||
// deserializing constructor; `parseST` catches that itself, so the host is never asked - unlike
|
||||
// a `guarded`-caught throw from the host's own body.
|
||||
struct FloatFromSTAmountCall : HostContextTest
|
||||
{
|
||||
STAmount const amount{XRPAmount{1000}};
|
||||
Bytes const wireBytes = serialized(amount);
|
||||
std::int32_t const mode = 1;
|
||||
};
|
||||
|
||||
TEST_F(FloatFromSTAmountCall, SerializedAmountDecodesToValueHostIsAskedFor)
|
||||
{
|
||||
Bytes const result{1, 2, 3};
|
||||
EXPECT_CALL(host, floatFromSTAmount(testing::Eq(amount), mode))
|
||||
.WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromSTAmount(bytesOf(wireBytes), mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(result)));
|
||||
}
|
||||
|
||||
TEST_F(FloatFromSTAmountCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, floatFromSTAmount(testing::Eq(amount), mode))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::FloatComputationError)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromSTAmount(bytesOf(wireBytes), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::FloatComputationError));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(FloatFromSTAmountCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, floatFromSTAmount(testing::Eq(amount), mode))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"float from st amount came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromSTAmount(bytesOf(wireBytes), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("float from st amount came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("floatFromSTAmount"));
|
||||
}
|
||||
|
||||
// `parseST` catches its own failure: a malformed buffer never reaches the host at all.
|
||||
TEST_F(FloatFromSTAmountCall, MalformedBytesAreRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedBytes{0xff, 0xff, 0xff};
|
||||
EXPECT_CALL(host, floatFromSTAmount).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromSTAmount(bytesOf(malformedBytes), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(FloatFromSTAmountCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const result{1, 2, 3};
|
||||
EXPECT_CALL(host, floatFromSTAmount(testing::Eq(amount), mode))
|
||||
.WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{result.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromSTAmount(bytesOf(wireBytes), mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
110
src/tests/libxrpl/tx/wasm/host_context/FloatFromSTNumber.cpp
Normal file
110
src/tests/libxrpl/tx/wasm/host_context/FloatFromSTNumber.cpp
Normal file
@@ -0,0 +1,110 @@
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
namespace {
|
||||
|
||||
// The wire form `STNumber(SerialIter&, SField const&)` expects: an eight-byte mantissa
|
||||
// followed by a four-byte exponent. Built directly rather than through `STNumber::add`, which
|
||||
// asserts its field is bound to `STI_NUMBER` - an assertion `sfGeneric` does not satisfy.
|
||||
Bytes
|
||||
serialized(std::int64_t mantissa, std::int32_t exponent)
|
||||
{
|
||||
Serializer s;
|
||||
s.add64(mantissa);
|
||||
s.add32(exponent);
|
||||
return s.getData();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// The only file exercising `parseST<STNumber>`. A malformed buffer throws inside `STNumber`'s
|
||||
// deserializing constructor; `parseST` catches that itself, so the host is never asked - unlike
|
||||
// a `guarded`-caught throw from the host's own body.
|
||||
struct FloatFromSTNumberCall : HostContextTest
|
||||
{
|
||||
std::int64_t const mantissa = 123456789;
|
||||
std::int32_t const exponent = -5;
|
||||
STNumber const number{sfGeneric, Number{mantissa, exponent}};
|
||||
Bytes const wireBytes = serialized(mantissa, exponent);
|
||||
std::int32_t const mode = 1;
|
||||
};
|
||||
|
||||
TEST_F(FloatFromSTNumberCall, SerializedNumberDecodesToValueHostIsAskedFor)
|
||||
{
|
||||
Bytes const result{1, 2, 3};
|
||||
EXPECT_CALL(host, floatFromSTNumber(testing::Eq(number), mode))
|
||||
.WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromSTNumber(bytesOf(wireBytes), mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(result)));
|
||||
}
|
||||
|
||||
TEST_F(FloatFromSTNumberCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, floatFromSTNumber(testing::Eq(number), mode))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::FloatComputationError)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromSTNumber(bytesOf(wireBytes), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::FloatComputationError));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(FloatFromSTNumberCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, floatFromSTNumber(testing::Eq(number), mode))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"float from st number came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromSTNumber(bytesOf(wireBytes), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("float from st number came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("floatFromSTNumber"));
|
||||
}
|
||||
|
||||
// `parseST` catches its own failure: a malformed buffer never reaches the host at all.
|
||||
TEST_F(FloatFromSTNumberCall, MalformedBytesAreRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedBytes{0xff, 0xff, 0xff};
|
||||
EXPECT_CALL(host, floatFromSTNumber).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromSTNumber(bytesOf(malformedBytes), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(FloatFromSTNumberCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const result{1, 2, 3};
|
||||
EXPECT_CALL(host, floatFromSTNumber(testing::Eq(number), mode))
|
||||
.WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{result.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromSTNumber(bytesOf(wireBytes), mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
131
src/tests/libxrpl/tx/wasm/host_context/FloatFromUint.cpp
Normal file
131
src/tests/libxrpl/tx/wasm/host_context/FloatFromUint.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The only file exercising `parseUint64`: exactly eight bytes, little-endian.
|
||||
struct FloatFromUintCall : HostContextTest
|
||||
{
|
||||
// Every byte distinct, so a byte-order mistake in `parseUint64` would decode to a
|
||||
// different value rather than the same one by coincidence.
|
||||
std::uint64_t const value = 0x0102'0304'0506'0708ULL;
|
||||
Bytes const wireBytes = bytesOfScalar(value);
|
||||
std::int32_t const mode = 1;
|
||||
};
|
||||
|
||||
TEST_F(FloatFromUintCall, LittleEndianWireBytesDecodeToValueHostIsAskedFor)
|
||||
{
|
||||
Bytes const result{1, 2, 3};
|
||||
EXPECT_CALL(host, floatFromUint(value, mode)).WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromUint(bytesOf(wireBytes), mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(result)));
|
||||
}
|
||||
|
||||
TEST_F(FloatFromUintCall, ModeIsForwardedVerbatim)
|
||||
{
|
||||
std::int32_t const nonsenseMode = -12345;
|
||||
Bytes const result{1};
|
||||
EXPECT_CALL(host, floatFromUint(value, nonsenseMode)).WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromUint(bytesOf(wireBytes), nonsenseMode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
}
|
||||
|
||||
TEST_F(FloatFromUintCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, floatFromUint(value, mode))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::FloatInputMalformed)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromUint(bytesOf(wireBytes), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::FloatInputMalformed));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(FloatFromUintCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, floatFromUint(value, mode))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"uint came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromUint(bytesOf(wireBytes), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("uint came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("floatFromUint"));
|
||||
}
|
||||
|
||||
TEST_F(FloatFromUintCall, SevenByteRegionIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const shortBytes(7, 0);
|
||||
EXPECT_CALL(host, floatFromUint).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromUint(bytesOf(shortBytes), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(FloatFromUintCall, NineByteRegionIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const longBytes(9, 0);
|
||||
EXPECT_CALL(host, floatFromUint).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromUint(bytesOf(longBytes), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(FloatFromUintCall, EmptyRegionIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, floatFromUint).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromUint(bytesOf(Bytes{}), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(FloatFromUintCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const result{1, 2, 3};
|
||||
EXPECT_CALL(host, floatFromUint(value, mode)).WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{result.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromUint(bytesOf(wireBytes), mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(FloatFromUintCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const result{1, 2, 3};
|
||||
EXPECT_CALL(host, floatFromUint(value, mode)).WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{result.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatFromUint(bytesOf(wireBytes), mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(result)));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
89
src/tests/libxrpl/tx/wasm/host_context/FloatMultiply.cpp
Normal file
89
src/tests/libxrpl/tx/wasm/host_context/FloatMultiply.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
#include <tx/wasm/MockHostFunctions.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// Every input slice passes straight through to the host, unlike `invokeWithAccount`'s
|
||||
// twenty-byte check or `parseUint64`'s eight: nothing here is validated, so there is no D
|
||||
// axis. `x` and `y` carry different content, so a call that swapped them would fail to match.
|
||||
struct FloatMultiplyCall : HostContextTest
|
||||
{
|
||||
Bytes const x{'m', 'u', 'l', '-', 'x'};
|
||||
Bytes const y{'m', 'u', 'l', '-', 'y', 'y'};
|
||||
std::int32_t const mode = 21;
|
||||
};
|
||||
|
||||
TEST_F(FloatMultiplyCall, OperandsAndModeAreForwardedResultIsWritten)
|
||||
{
|
||||
Bytes const result{9, 8, 7};
|
||||
EXPECT_CALL(host, floatMultiply(BytesAre("mul-x"), BytesAre("mul-yy"), mode))
|
||||
.WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatMultiply(bytesOf(x), bytesOf(y), mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(result)));
|
||||
}
|
||||
|
||||
TEST_F(FloatMultiplyCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, floatMultiply(BytesAre("mul-x"), BytesAre("mul-yy"), mode))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::FloatComputationError)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatMultiply(bytesOf(x), bytesOf(y), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::FloatComputationError));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(FloatMultiplyCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, floatMultiply(BytesAre("mul-x"), BytesAre("mul-yy"), mode))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"float multiply came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatMultiply(bytesOf(x), bytesOf(y), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("float multiply came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("floatMultiply"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(FloatMultiplyCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const result{9, 8, 7};
|
||||
EXPECT_CALL(host, floatMultiply(BytesAre("mul-x"), BytesAre("mul-yy"), mode))
|
||||
.WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{result.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatMultiply(bytesOf(x), bytesOf(y), mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
// No length rule exists at this layer: a differently sized operand still reaches the host
|
||||
// rather than being refused.
|
||||
TEST_F(FloatMultiplyCall, OddSizedOperandReachesHostUnchanged)
|
||||
{
|
||||
Bytes const shortX{0x2a};
|
||||
EXPECT_CALL(host, floatMultiply(testing::_, BytesAre("mul-yy"), mode))
|
||||
.WillOnce(testing::Return(Bytes{1}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.floatMultiply(bytesOf(shortX), bytesOf(y), mode, out.slice()), 1);
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
103
src/tests/libxrpl/tx/wasm/host_context/FloatPower.cpp
Normal file
103
src/tests/libxrpl/tx/wasm/host_context/FloatPower.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
#include <tx/wasm/MockHostFunctions.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// Every input slice passes straight through to the host, unlike `invokeWithAccount`'s
|
||||
// twenty-byte check or `parseUint64`'s eight: nothing here is validated, so there is no D
|
||||
// axis. `n` and `mode` carry different values, so a call that swapped them would fail to
|
||||
// match.
|
||||
struct FloatPowerCall : HostContextTest
|
||||
{
|
||||
Bytes const x{'p', 'o', 'w', '-', 'x'};
|
||||
std::int32_t const n = 4;
|
||||
std::int32_t const mode = 22;
|
||||
};
|
||||
|
||||
TEST_F(FloatPowerCall, OperandNAndModeAreForwardedResultIsWritten)
|
||||
{
|
||||
Bytes const result{4, 5, 6};
|
||||
EXPECT_CALL(host, floatPower(BytesAre("pow-x"), n, mode)).WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatPower(bytesOf(x), n, mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(result)));
|
||||
}
|
||||
|
||||
TEST_F(FloatPowerCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, floatPower(BytesAre("pow-x"), n, mode))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::FloatComputationError)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatPower(bytesOf(x), n, mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::FloatComputationError));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(FloatPowerCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, floatPower(BytesAre("pow-x"), n, mode))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"float power came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatPower(bytesOf(x), n, mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("float power came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("floatPower"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(FloatPowerCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const result{4, 5, 6};
|
||||
EXPECT_CALL(host, floatPower(BytesAre("pow-x"), n, mode)).WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{result.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatPower(bytesOf(x), n, mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
// No length rule exists at this layer: a differently sized operand still reaches the host
|
||||
// rather than being refused.
|
||||
TEST_F(FloatPowerCall, OddSizedOperandReachesHostUnchanged)
|
||||
{
|
||||
Bytes const shortX{0x2a};
|
||||
EXPECT_CALL(host, floatPower(testing::_, n, mode)).WillOnce(testing::Return(Bytes{1}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.floatPower(bytesOf(shortX), n, mode, out.slice()), 1);
|
||||
}
|
||||
|
||||
// `mode` and `n` validate nothing at this layer and cross verbatim, including values with no
|
||||
// real meaning. Worth pinning once across the float family rather than in every file.
|
||||
TEST_F(FloatPowerCall, ModeAndNAreForwardedVerbatim)
|
||||
{
|
||||
std::int32_t const nonsenseN = -999;
|
||||
std::int32_t const nonsenseMode = 424242;
|
||||
Bytes const result{1};
|
||||
EXPECT_CALL(host, floatPower(BytesAre("pow-x"), nonsenseN, nonsenseMode))
|
||||
.WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatPower(bytesOf(x), nonsenseN, nonsenseMode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
87
src/tests/libxrpl/tx/wasm/host_context/FloatRoot.cpp
Normal file
87
src/tests/libxrpl/tx/wasm/host_context/FloatRoot.cpp
Normal file
@@ -0,0 +1,87 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
#include <tx/wasm/MockHostFunctions.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// Every input slice passes straight through to the host, unlike `invokeWithAccount`'s
|
||||
// twenty-byte check or `parseUint64`'s eight: nothing here is validated, so there is no D
|
||||
// axis. `n` and `mode` carry different values, so a call that swapped them would fail to
|
||||
// match.
|
||||
struct FloatRootCall : HostContextTest
|
||||
{
|
||||
Bytes const x{'r', 'o', 'o', 't', '-', 'x'};
|
||||
std::int32_t const n = 3;
|
||||
std::int32_t const mode = 11;
|
||||
};
|
||||
|
||||
TEST_F(FloatRootCall, OperandNAndModeAreForwardedResultIsWritten)
|
||||
{
|
||||
Bytes const result{4, 5, 6};
|
||||
EXPECT_CALL(host, floatRoot(BytesAre("root-x"), n, mode)).WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatRoot(bytesOf(x), n, mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(result)));
|
||||
}
|
||||
|
||||
TEST_F(FloatRootCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, floatRoot(BytesAre("root-x"), n, mode))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::FloatComputationError)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatRoot(bytesOf(x), n, mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::FloatComputationError));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(FloatRootCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, floatRoot(BytesAre("root-x"), n, mode))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"float root came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatRoot(bytesOf(x), n, mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("float root came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("floatRoot"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(FloatRootCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const result{4, 5, 6};
|
||||
EXPECT_CALL(host, floatRoot(BytesAre("root-x"), n, mode)).WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{result.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatRoot(bytesOf(x), n, mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
// No length rule exists at this layer: a differently sized operand still reaches the host
|
||||
// rather than being refused.
|
||||
TEST_F(FloatRootCall, OddSizedOperandReachesHostUnchanged)
|
||||
{
|
||||
Bytes const shortX{0x2a};
|
||||
EXPECT_CALL(host, floatRoot(testing::_, n, mode)).WillOnce(testing::Return(Bytes{1}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.floatRoot(bytesOf(shortX), n, mode, out.slice()), 1);
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
89
src/tests/libxrpl/tx/wasm/host_context/FloatSubtract.cpp
Normal file
89
src/tests/libxrpl/tx/wasm/host_context/FloatSubtract.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
#include <tx/wasm/MockHostFunctions.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// Every input slice passes straight through to the host, unlike `invokeWithAccount`'s
|
||||
// twenty-byte check or `parseUint64`'s eight: nothing here is validated, so there is no D
|
||||
// axis. `x` and `y` carry different content, so a call that swapped them would fail to match.
|
||||
struct FloatSubtractCall : HostContextTest
|
||||
{
|
||||
Bytes const x{'s', 'u', 'b', '-', 'x'};
|
||||
Bytes const y{'s', 'u', 'b', '-', 'y', 'y'};
|
||||
std::int32_t const mode = 13;
|
||||
};
|
||||
|
||||
TEST_F(FloatSubtractCall, OperandsAndModeAreForwardedResultIsWritten)
|
||||
{
|
||||
Bytes const result{9, 8, 7};
|
||||
EXPECT_CALL(host, floatSubtract(BytesAre("sub-x"), BytesAre("sub-yy"), mode))
|
||||
.WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatSubtract(bytesOf(x), bytesOf(y), mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(result)));
|
||||
}
|
||||
|
||||
TEST_F(FloatSubtractCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, floatSubtract(BytesAre("sub-x"), BytesAre("sub-yy"), mode))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::FloatComputationError)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatSubtract(bytesOf(x), bytesOf(y), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::FloatComputationError));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(FloatSubtractCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, floatSubtract(BytesAre("sub-x"), BytesAre("sub-yy"), mode))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"float subtract came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatSubtract(bytesOf(x), bytesOf(y), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("float subtract came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("floatSubtract"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(FloatSubtractCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const result{9, 8, 7};
|
||||
EXPECT_CALL(host, floatSubtract(BytesAre("sub-x"), BytesAre("sub-yy"), mode))
|
||||
.WillOnce(testing::Return(result));
|
||||
|
||||
OutRegion out{result.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatSubtract(bytesOf(x), bytesOf(y), mode, out.slice()),
|
||||
static_cast<std::int32_t>(result.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
// No length rule exists at this layer: a differently sized operand still reaches the host
|
||||
// rather than being refused.
|
||||
TEST_F(FloatSubtractCall, OddSizedOperandReachesHostUnchanged)
|
||||
{
|
||||
Bytes const shortX{0x2a};
|
||||
EXPECT_CALL(host, floatSubtract(testing::_, BytesAre("sub-yy"), mode))
|
||||
.WillOnce(testing::Return(Bytes{1}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.floatSubtract(bytesOf(shortX), bytesOf(y), mode, out.slice()), 1);
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
81
src/tests/libxrpl/tx/wasm/host_context/FloatToInt.cpp
Normal file
81
src/tests/libxrpl/tx/wasm/host_context/FloatToInt.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
#include <tx/wasm/MockHostFunctions.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The input slice passes straight through to the host, unlike `invokeWithAccount`'s twenty-byte
|
||||
// check or `parseUint64`'s eight: nothing here is validated, so there is no D axis.
|
||||
struct FloatToIntCall : HostContextTest
|
||||
{
|
||||
Bytes const x{'t', 'o', 'i', 'n', 't'};
|
||||
std::int32_t const mode = 3;
|
||||
};
|
||||
|
||||
TEST_F(FloatToIntCall, OperandAndModeAreForwardedResultWrittenAsLittleEndianBytes)
|
||||
{
|
||||
std::int64_t const value = -123456789;
|
||||
EXPECT_CALL(host, floatToInt(BytesAre("toint"), mode)).WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.floatToInt(bytesOf(x), mode, out.slice()), 8);
|
||||
EXPECT_TRUE(out.holds(bytesOf(bytesOfScalar(value))));
|
||||
}
|
||||
|
||||
TEST_F(FloatToIntCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, floatToInt(BytesAre("toint"), mode))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::FloatComputationError)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatToInt(bytesOf(x), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::FloatComputationError));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(FloatToIntCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, floatToInt(BytesAre("toint"), mode))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"float to int came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatToInt(bytesOf(x), mode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("float to int came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("floatToInt"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(FloatToIntCall, SevenByteOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
std::int64_t const value = 42;
|
||||
EXPECT_CALL(host, floatToInt(BytesAre("toint"), mode)).WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{7};
|
||||
EXPECT_EQ(hostContext.floatToInt(bytesOf(x), mode, out.slice()), 8);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
// No length rule exists at this layer: a differently sized operand still reaches the host
|
||||
// rather than being refused.
|
||||
TEST_F(FloatToIntCall, OddSizedOperandReachesHostUnchanged)
|
||||
{
|
||||
Bytes const oddX{0x2a};
|
||||
EXPECT_CALL(host, floatToInt(testing::_, mode)).WillOnce(testing::Return(std::int64_t{7}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.floatToInt(bytesOf(oddX), mode, out.slice()), 8);
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
102
src/tests/libxrpl/tx/wasm/host_context/FloatToMantExp.cpp
Normal file
102
src/tests/libxrpl/tx/wasm/host_context/FloatToMantExp.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
#include <tx/wasm/MockHostFunctions.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The input slice passes straight through to the host, unlike `invokeWithAccount`'s twenty-byte
|
||||
// check or `parseUint64`'s eight: nothing here is validated, so there is no D axis. The two out
|
||||
// regions are each checked and written independently; the return is their summed true length.
|
||||
struct FloatToMantExpCall : HostContextTest
|
||||
{
|
||||
Bytes const x{'m', 'a', 'n', 't', 'e', 'x', 'p'};
|
||||
std::int64_t const mantissa = 0x0102'0304'0506'0708LL;
|
||||
std::int32_t const exponent = -5;
|
||||
FloatPair const pair{mantissa, exponent};
|
||||
};
|
||||
|
||||
TEST_F(FloatToMantExpCall, OperandIsForwardedMantissaAndExponentWrittenAsLittleEndianBytes)
|
||||
{
|
||||
EXPECT_CALL(host, floatToMantExp(BytesAre("mantexp"))).WillOnce(testing::Return(pair));
|
||||
|
||||
OutRegion mantissaOut{8};
|
||||
OutRegion exponentOut{4};
|
||||
EXPECT_EQ(hostContext.floatToMantExp(bytesOf(x), mantissaOut.slice(), exponentOut.slice()), 12);
|
||||
EXPECT_TRUE(mantissaOut.holds(bytesOf(bytesOfScalar(mantissa))));
|
||||
EXPECT_TRUE(exponentOut.holds(bytesOf(bytesOfScalar(exponent))));
|
||||
}
|
||||
|
||||
TEST_F(FloatToMantExpCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, floatToMantExp(BytesAre("mantexp")))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::FloatComputationError)));
|
||||
|
||||
OutRegion mantissaOut{8};
|
||||
OutRegion exponentOut{4};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatToMantExp(bytesOf(x), mantissaOut.slice(), exponentOut.slice()),
|
||||
hfErrorToInt(HostFunctionError::FloatComputationError));
|
||||
EXPECT_FALSE(mantissaOut.wasWritten());
|
||||
EXPECT_FALSE(exponentOut.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(FloatToMantExpCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, floatToMantExp(BytesAre("mantexp")))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"float to mant exp came apart"}));
|
||||
|
||||
OutRegion mantissaOut{8};
|
||||
OutRegion exponentOut{4};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatToMantExp(bytesOf(x), mantissaOut.slice(), exponentOut.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("float to mant exp came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("floatToMantExp"));
|
||||
}
|
||||
|
||||
// Each region is checked independently: a short mantissa region does not stop the exponent
|
||||
// from being written, and the sum still counts the mantissa's true length.
|
||||
TEST_F(FloatToMantExpCall, ShortMantissaRegionWritesNothingThereSumStillCountsIt)
|
||||
{
|
||||
EXPECT_CALL(host, floatToMantExp(BytesAre("mantexp"))).WillOnce(testing::Return(pair));
|
||||
|
||||
OutRegion mantissaOut{7};
|
||||
OutRegion exponentOut{4};
|
||||
EXPECT_EQ(hostContext.floatToMantExp(bytesOf(x), mantissaOut.slice(), exponentOut.slice()), 12);
|
||||
EXPECT_FALSE(mantissaOut.wasWritten());
|
||||
EXPECT_TRUE(exponentOut.holds(bytesOf(bytesOfScalar(exponent))));
|
||||
}
|
||||
|
||||
TEST_F(FloatToMantExpCall, ShortExponentRegionWritesNothingThereSumStillCountsIt)
|
||||
{
|
||||
EXPECT_CALL(host, floatToMantExp(BytesAre("mantexp"))).WillOnce(testing::Return(pair));
|
||||
|
||||
OutRegion mantissaOut{8};
|
||||
OutRegion exponentOut{3};
|
||||
EXPECT_EQ(hostContext.floatToMantExp(bytesOf(x), mantissaOut.slice(), exponentOut.slice()), 12);
|
||||
EXPECT_TRUE(mantissaOut.holds(bytesOf(bytesOfScalar(mantissa))));
|
||||
EXPECT_FALSE(exponentOut.wasWritten());
|
||||
}
|
||||
|
||||
// No length rule exists at this layer: a differently sized operand still reaches the host
|
||||
// rather than being refused.
|
||||
TEST_F(FloatToMantExpCall, OddSizedOperandReachesHostUnchanged)
|
||||
{
|
||||
Bytes const oddX{0x2a};
|
||||
EXPECT_CALL(host, floatToMantExp(testing::_)).WillOnce(testing::Return(pair));
|
||||
|
||||
OutRegion mantissaOut{8};
|
||||
OutRegion exponentOut{4};
|
||||
EXPECT_EQ(
|
||||
hostContext.floatToMantExp(bytesOf(oddX), mantissaOut.slice(), exponentOut.slice()), 12);
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
108
src/tests/libxrpl/tx/wasm/host_context/IsAmendmentEnabled.cpp
Normal file
108
src/tests/libxrpl/tx/wasm/host_context/IsAmendmentEnabled.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The whole point of this file: a 32-byte input tries as an amendment id first, and falls back
|
||||
// to a name lookup - on those same bytes - only if that id lookup does not answer enabled.
|
||||
struct IsAmendmentEnabledCall : HostContextTest
|
||||
{
|
||||
Bytes const idBytes = Bytes(uint256::size(), 0x11);
|
||||
uint256 const id = uint256::fromVoid(idBytes.data());
|
||||
};
|
||||
|
||||
TEST_F(IsAmendmentEnabledCall, ThirtyTwoByteEnabledIdAnswersOneWithoutNameLookup)
|
||||
{
|
||||
EXPECT_CALL(host, isAmendmentEnabled(testing::Matcher<uint256 const&>(testing::Eq(id))))
|
||||
.WillOnce(testing::Return(1));
|
||||
EXPECT_CALL(host, isAmendmentEnabled(testing::Matcher<std::string_view const&>(testing::_)))
|
||||
.Times(0);
|
||||
|
||||
EXPECT_EQ(hostContext.isAmendmentEnabled(bytesOf(idBytes)), 1);
|
||||
}
|
||||
|
||||
// The same 32 bytes, read first as an id and, once that is not an enabled one, as a name.
|
||||
TEST_F(IsAmendmentEnabledCall, ThirtyTwoByteDisabledIdFallsThroughToNameLookupWithSameBytes)
|
||||
{
|
||||
std::string_view const nameFromBytes{
|
||||
reinterpret_cast<char const*>(idBytes.data()), idBytes.size()};
|
||||
EXPECT_CALL(host, isAmendmentEnabled(testing::Matcher<uint256 const&>(testing::Eq(id))))
|
||||
.WillOnce(testing::Return(0));
|
||||
EXPECT_CALL(
|
||||
host,
|
||||
isAmendmentEnabled(testing::Matcher<std::string_view const&>(testing::Eq(nameFromBytes))))
|
||||
.WillOnce(testing::Return(1));
|
||||
|
||||
EXPECT_EQ(hostContext.isAmendmentEnabled(bytesOf(idBytes)), 1);
|
||||
}
|
||||
|
||||
// An id lookup that errors is treated the same as one that says no: both fall through to the
|
||||
// name lookup rather than surfacing the error.
|
||||
TEST_F(IsAmendmentEnabledCall, ThirtyTwoByteIdLookupErrorFallsThroughToNameLookup)
|
||||
{
|
||||
EXPECT_CALL(host, isAmendmentEnabled(testing::Matcher<uint256 const&>(testing::Eq(id))))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::Unimplemented)));
|
||||
EXPECT_CALL(host, isAmendmentEnabled(testing::Matcher<std::string_view const&>(testing::_)))
|
||||
.WillOnce(testing::Return(1));
|
||||
|
||||
EXPECT_EQ(hostContext.isAmendmentEnabled(bytesOf(idBytes)), 1);
|
||||
}
|
||||
|
||||
// Over 64 bytes cannot be a 32-byte id nor a name short enough to matter, so it is refused
|
||||
// before either overload runs.
|
||||
TEST_F(IsAmendmentEnabledCall, InputOverSixtyFourBytesIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const tooLong(65, 0x22);
|
||||
EXPECT_CALL(host, isAmendmentEnabled(testing::Matcher<uint256 const&>(testing::_))).Times(0);
|
||||
EXPECT_CALL(host, isAmendmentEnabled(testing::Matcher<std::string_view const&>(testing::_)))
|
||||
.Times(0);
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.isAmendmentEnabled(bytesOf(tooLong)),
|
||||
hfErrorToInt(HostFunctionError::DataFieldTooLarge));
|
||||
}
|
||||
|
||||
TEST_F(IsAmendmentEnabledCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
Bytes const name{'F', 'e', 'a', 't', 'u', 'r', 'e'};
|
||||
EXPECT_CALL(host, isAmendmentEnabled(testing::Matcher<std::string_view const&>(testing::_)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::FieldNotFound)));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.isAmendmentEnabled(bytesOf(name)),
|
||||
hfErrorToInt(HostFunctionError::FieldNotFound));
|
||||
}
|
||||
|
||||
TEST_F(IsAmendmentEnabledCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
Bytes const name{'F', 'e', 'a', 't', 'u', 'r', 'e'};
|
||||
EXPECT_CALL(host, isAmendmentEnabled(testing::Matcher<std::string_view const&>(testing::_)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"amendment lookup came apart"}));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.isAmendmentEnabled(bytesOf(name)),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("amendment lookup came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("isAmendmentEnabled"));
|
||||
}
|
||||
|
||||
TEST_F(IsAmendmentEnabledCall, NameBytesForwardedVerbatimToNameLookup)
|
||||
{
|
||||
std::string_view const name{"MyAmendment"};
|
||||
Bytes const nameBytes{name.begin(), name.end()};
|
||||
EXPECT_CALL(
|
||||
host, isAmendmentEnabled(testing::Matcher<std::string_view const&>(testing::Eq(name))))
|
||||
.WillOnce(testing::Return(1));
|
||||
|
||||
EXPECT_EQ(hostContext.isAmendmentEnabled(bytesOf(nameBytes)), 1);
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
84
src/tests/libxrpl/tx/wasm/host_context/LedgerObjArrayLen.cpp
Normal file
84
src/tests/libxrpl/tx/wasm/host_context/LedgerObjArrayLen.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// `getLedgerObjArrayLen` answers its count directly rather than through an out region: no axis
|
||||
// E, no `OutRegion`, and the happy path asserts the returned count.
|
||||
struct LedgerObjArrayLenCall : HostContextTest
|
||||
{
|
||||
std::int32_t fieldCode = sfBalance.getCode();
|
||||
std::int32_t cacheIdx = 7;
|
||||
};
|
||||
|
||||
TEST_F(LedgerObjArrayLenCall, FieldCodeBecomesSFieldHostIsAskedFor)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerObjArrayLen(cacheIdx, testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(5));
|
||||
|
||||
EXPECT_EQ(hostContext.getLedgerObjArrayLen(cacheIdx, fieldCode), 5);
|
||||
}
|
||||
|
||||
// `NoArray` is what a field that is not an array actually answers, so it stands in for axis B
|
||||
// here rather than an arbitrary code.
|
||||
TEST_F(LedgerObjArrayLenCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerObjArrayLen(cacheIdx, testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::NoArray)));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjArrayLen(cacheIdx, fieldCode),
|
||||
hfErrorToInt(HostFunctionError::NoArray));
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjArrayLenCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerObjArrayLen(cacheIdx, testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"ledger obj array len came apart"}));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjArrayLen(cacheIdx, fieldCode),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("ledger obj array len came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getLedgerObjArrayLen"));
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjArrayLenCall, UnknownFieldCodeIsRefusedWithoutAskingHost)
|
||||
{
|
||||
fieldCode = 0x7fff'0000; // a code nothing is registered under
|
||||
EXPECT_CALL(host, getLedgerObjArrayLen).Times(0);
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjArrayLen(cacheIdx, fieldCode),
|
||||
hfErrorToInt(HostFunctionError::InvalidField));
|
||||
}
|
||||
|
||||
// `cacheIdx` is forwarded verbatim, including the two values a guest is likeliest to send: 0
|
||||
// (pick a free slot) and a negative one.
|
||||
TEST_F(LedgerObjArrayLenCall, CacheIdxOfZeroIsForwardedVerbatim)
|
||||
{
|
||||
cacheIdx = 0;
|
||||
EXPECT_CALL(host, getLedgerObjArrayLen(0, testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(5));
|
||||
|
||||
EXPECT_EQ(hostContext.getLedgerObjArrayLen(cacheIdx, fieldCode), 5);
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjArrayLenCall, NegativeCacheIdxIsForwardedVerbatim)
|
||||
{
|
||||
cacheIdx = -7;
|
||||
EXPECT_CALL(host, getLedgerObjArrayLen(-7, testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(5));
|
||||
|
||||
EXPECT_EQ(hostContext.getLedgerObjArrayLen(cacheIdx, fieldCode), 5);
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
137
src/tests/libxrpl/tx/wasm/host_context/LedgerObjField.cpp
Normal file
137
src/tests/libxrpl/tx/wasm/host_context/LedgerObjField.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, the field cap, guest memory - are tested on the Rust
|
||||
// side, not here. The cross-cutting cases over this shape already live in `TxField.cpp`.
|
||||
struct LedgerObjFieldCall : HostContextTest
|
||||
{
|
||||
std::int32_t fieldCode = sfBalance.getCode();
|
||||
std::int32_t cacheIdx = 7;
|
||||
};
|
||||
|
||||
TEST_F(LedgerObjFieldCall, FieldCodeBecomesSFieldHostIsAskedFor)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getLedgerObjField(cacheIdx, testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjField(cacheIdx, fieldCode, out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(value)));
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjFieldCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerObjField(cacheIdx, testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::FieldNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjField(cacheIdx, fieldCode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::FieldNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjFieldCall, UnknownFieldCodeIsRefusedWithoutAskingHost)
|
||||
{
|
||||
fieldCode = 0x7fff'0000; // a code nothing is registered under
|
||||
EXPECT_CALL(host, getLedgerObjField).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjField(cacheIdx, fieldCode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidField));
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjFieldCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerObjField(cacheIdx, testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"ledger obj field came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjField(cacheIdx, fieldCode, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("ledger obj field came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getLedgerObjField"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(LedgerObjFieldCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getLedgerObjField(cacheIdx, testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{value.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjField(cacheIdx, fieldCode, out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjFieldCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getLedgerObjField(cacheIdx, testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{value.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjField(cacheIdx, fieldCode, out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(value)));
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjFieldCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerObjField(cacheIdx, testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.getLedgerObjField(cacheIdx, fieldCode, out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
// `cacheIdx` is forwarded verbatim, including the two values a guest is likeliest to send: 0
|
||||
// (pick a free slot) and a negative one.
|
||||
TEST_F(LedgerObjFieldCall, CacheIdxOfZeroIsForwardedVerbatim)
|
||||
{
|
||||
cacheIdx = 0;
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getLedgerObjField(0, testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjField(cacheIdx, fieldCode, out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjFieldCall, NegativeCacheIdxIsForwardedVerbatim)
|
||||
{
|
||||
cacheIdx = -7;
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getLedgerObjField(-7, testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjField(cacheIdx, fieldCode, out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -0,0 +1,97 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, the field cap, guest memory - are tested on the Rust
|
||||
// side, not here.
|
||||
//
|
||||
// No out region and no axis E: `getLedgerObjNestedArrayLen` answers the array's element
|
||||
// count directly rather than through a written buffer.
|
||||
struct LedgerObjNestedArrayLenCall : HostContextTest
|
||||
{
|
||||
std::int32_t const cacheIdx = 7;
|
||||
std::vector<std::int32_t> const steps{5, -12, 130};
|
||||
Bytes const locatorBytes = bytesOfSteps(steps);
|
||||
};
|
||||
|
||||
TEST_F(LedgerObjNestedArrayLenCall, LocatorBytesBecomeFieldLocatorHostReturnsCount)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerObjNestedArrayLen(cacheIdx, LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(7));
|
||||
|
||||
EXPECT_EQ(hostContext.getLedgerObjNestedArrayLen(cacheIdx, bytesOf(locatorBytes)), 7);
|
||||
}
|
||||
|
||||
// `NoArray` - the field the locator resolves to is not an array - is the error this shape
|
||||
// most plausibly returns, so it stands in for axis B.
|
||||
TEST_F(LedgerObjNestedArrayLenCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerObjNestedArrayLen(cacheIdx, LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::NoArray)));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjNestedArrayLen(cacheIdx, bytesOf(locatorBytes)),
|
||||
hfErrorToInt(HostFunctionError::NoArray));
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjNestedArrayLenCall, EmptyLocatorIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerObjNestedArrayLen).Times(0);
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjNestedArrayLen(cacheIdx, bytesOf(Bytes{})),
|
||||
hfErrorToInt(HostFunctionError::LocatorMalformed));
|
||||
}
|
||||
|
||||
// Distinct from an empty locator: `invokeWithLocator` checks the two conditions separately.
|
||||
TEST_F(LedgerObjNestedArrayLenCall, MisalignedLocatorLengthIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const oddLength{1, 2, 3};
|
||||
EXPECT_CALL(host, getLedgerObjNestedArrayLen).Times(0);
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjNestedArrayLen(cacheIdx, bytesOf(oddLength)),
|
||||
hfErrorToInt(HostFunctionError::LocatorMalformed));
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjNestedArrayLenCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerObjNestedArrayLen(cacheIdx, LocatorEquals(steps)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"ledger obj nested array len came apart"}));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjNestedArrayLen(cacheIdx, bytesOf(locatorBytes)),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("ledger obj nested array len came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getLedgerObjNestedArrayLen"));
|
||||
}
|
||||
|
||||
// `cacheIdx` crosses to the host as its own `std::int32_t`, unlike a keylet method's `seq`:
|
||||
// no cast to an unsigned bit pattern, so 0 and a negative slot both cross unchanged.
|
||||
TEST_F(LedgerObjNestedArrayLenCall, ZeroCacheIdxArrivesAtHostUnchanged)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerObjNestedArrayLen(0, LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(7));
|
||||
|
||||
EXPECT_EQ(hostContext.getLedgerObjNestedArrayLen(0, bytesOf(locatorBytes)), 7);
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjNestedArrayLenCall, NegativeCacheIdxArrivesAtHostUnchanged)
|
||||
{
|
||||
std::int32_t const negativeCacheIdx = -3;
|
||||
EXPECT_CALL(host, getLedgerObjNestedArrayLen(negativeCacheIdx, LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(7));
|
||||
|
||||
EXPECT_EQ(hostContext.getLedgerObjNestedArrayLen(negativeCacheIdx, bytesOf(locatorBytes)), 7);
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
149
src/tests/libxrpl/tx/wasm/host_context/LedgerObjNestedField.cpp
Normal file
149
src/tests/libxrpl/tx/wasm/host_context/LedgerObjNestedField.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, the field cap, guest memory - are tested on the Rust
|
||||
// side, not here.
|
||||
struct LedgerObjNestedFieldCall : HostContextTest
|
||||
{
|
||||
std::int32_t const cacheIdx = 7;
|
||||
std::vector<std::int32_t> const steps{5, -12, 130};
|
||||
Bytes const locatorBytes = bytesOfSteps(steps);
|
||||
};
|
||||
|
||||
TEST_F(LedgerObjNestedFieldCall, LocatorBytesBecomeFieldLocatorHostIsAskedFor)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getLedgerObjNestedField(cacheIdx, LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjNestedField(cacheIdx, bytesOf(locatorBytes), out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(value)));
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjNestedFieldCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerObjNestedField(cacheIdx, LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::NotLeafField)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjNestedField(cacheIdx, bytesOf(locatorBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::NotLeafField));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjNestedFieldCall, EmptyLocatorIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerObjNestedField).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjNestedField(cacheIdx, bytesOf(Bytes{}), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LocatorMalformed));
|
||||
}
|
||||
|
||||
// Distinct from an empty locator: `invokeWithLocator` checks the two conditions separately.
|
||||
TEST_F(LedgerObjNestedFieldCall, MisalignedLocatorLengthIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const oddLength{1, 2, 3};
|
||||
EXPECT_CALL(host, getLedgerObjNestedField).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjNestedField(cacheIdx, bytesOf(oddLength), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LocatorMalformed));
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjNestedFieldCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerObjNestedField(cacheIdx, LocatorEquals(steps)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"ledger obj nested field came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjNestedField(cacheIdx, bytesOf(locatorBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("ledger obj nested field came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getLedgerObjNestedField"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(LedgerObjNestedFieldCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getLedgerObjNestedField(cacheIdx, LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{value.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjNestedField(cacheIdx, bytesOf(locatorBytes), out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjNestedFieldCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getLedgerObjNestedField(cacheIdx, LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{value.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjNestedField(cacheIdx, bytesOf(locatorBytes), out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(value)));
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjNestedFieldCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerObjNestedField(cacheIdx, LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.getLedgerObjNestedField(cacheIdx, bytesOf(locatorBytes), out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
// `cacheIdx` crosses to the host as its own `std::int32_t`, unlike a keylet method's `seq`:
|
||||
// no cast to an unsigned bit pattern, so 0 and a negative slot both cross unchanged.
|
||||
TEST_F(LedgerObjNestedFieldCall, ZeroCacheIdxArrivesAtHostUnchanged)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getLedgerObjNestedField(0, LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjNestedField(0, bytesOf(locatorBytes), out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
}
|
||||
|
||||
TEST_F(LedgerObjNestedFieldCall, NegativeCacheIdxArrivesAtHostUnchanged)
|
||||
{
|
||||
std::int32_t const negativeCacheIdx = -3;
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getLedgerObjNestedField(negativeCacheIdx, LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerObjNestedField(negativeCacheIdx, bytesOf(locatorBytes), out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
76
src/tests/libxrpl/tx/wasm/host_context/LedgerSqn.cpp
Normal file
76
src/tests/libxrpl/tx/wasm/host_context/LedgerSqn.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// No D or F axis: `getLedgerSqn` takes no argument, so there is nothing to decode wrong and
|
||||
// nothing whose forwarded identity to check.
|
||||
//
|
||||
// Named `LedgerSqnDirectCall`, not `LedgerSqnCall`: `host_calls/LedgerSqn.cpp` already owns
|
||||
// that name in the same gtest binary.
|
||||
struct LedgerSqnDirectCall : HostContextTest
|
||||
{
|
||||
static constexpr std::uint32_t kLedgerSqn = 0x12345678;
|
||||
Bytes const expectedBytes = bytesOfScalar(kLedgerSqn);
|
||||
};
|
||||
|
||||
TEST_F(LedgerSqnDirectCall, HostValueIsWrittenAsLittleEndianBytes)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerSqn()).WillOnce(testing::Return(kLedgerSqn));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.getLedgerSqn(out.slice()), 4);
|
||||
EXPECT_TRUE(out.holds(bytesOf(expectedBytes)));
|
||||
}
|
||||
|
||||
TEST_F(LedgerSqnDirectCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerSqn())
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::Unimplemented)));
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerSqn(out.slice()), hfErrorToInt(HostFunctionError::Unimplemented));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(LedgerSqnDirectCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerSqn())
|
||||
.WillOnce(testing::Throw(std::runtime_error{"ledger sqn came apart"}));
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(
|
||||
hostContext.getLedgerSqn(out.slice()), hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("ledger sqn came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getLedgerSqn"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(LedgerSqnDirectCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerSqn()).WillOnce(testing::Return(kLedgerSqn));
|
||||
|
||||
OutRegion out{3};
|
||||
EXPECT_EQ(hostContext.getLedgerSqn(out.slice()), 4);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(LedgerSqnDirectCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
EXPECT_CALL(host, getLedgerSqn()).WillOnce(testing::Return(kLedgerSqn));
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(hostContext.getLedgerSqn(out.slice()), 4);
|
||||
EXPECT_TRUE(out.holds(bytesOf(expectedBytes)));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
131
src/tests/libxrpl/tx/wasm/host_context/MptokenIssuanceKeylet.cpp
Normal file
131
src/tests/libxrpl/tx/wasm/host_context/MptokenIssuanceKeylet.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
struct MptokenIssuanceKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const issuerBytes{0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,
|
||||
0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64};
|
||||
AccountID const issuer = AccountID::fromVoid(issuerBytes.data());
|
||||
std::int32_t const seq = 98765;
|
||||
};
|
||||
|
||||
TEST_F(MptokenIssuanceKeyletCall, IssuerAndSeqAreForwardedKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, mptokenIssuanceKeylet(issuer, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.mptokenIssuanceKeylet(bytesOf(issuerBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(MptokenIssuanceKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, mptokenIssuanceKeylet(issuer, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.mptokenIssuanceKeylet(bytesOf(issuerBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(MptokenIssuanceKeyletCall, ShortIssuerIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const shortIssuer(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, mptokenIssuanceKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.mptokenIssuanceKeylet(bytesOf(shortIssuer), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(MptokenIssuanceKeyletCall, LongIssuerIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const longIssuer(AccountID::size() + 1, 0x01);
|
||||
EXPECT_CALL(host, mptokenIssuanceKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.mptokenIssuanceKeylet(bytesOf(longIssuer), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(MptokenIssuanceKeyletCall, EmptyIssuerIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, mptokenIssuanceKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.mptokenIssuanceKeylet(bytesOf(Bytes{}), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(MptokenIssuanceKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, mptokenIssuanceKeylet(issuer, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"mptoken issuance keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.mptokenIssuanceKeylet(bytesOf(issuerBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("mptoken issuance keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("mptokenIssuanceKeylet"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(MptokenIssuanceKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, mptokenIssuanceKeylet(issuer, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.mptokenIssuanceKeylet(bytesOf(issuerBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(MptokenIssuanceKeyletCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, mptokenIssuanceKeylet(issuer, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.mptokenIssuanceKeylet(bytesOf(issuerBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(MptokenIssuanceKeyletCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, mptokenIssuanceKeylet(issuer, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.mptokenIssuanceKeylet(bytesOf(issuerBytes), seq, out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
119
src/tests/libxrpl/tx/wasm/host_context/MptokenKeylet.cpp
Normal file
119
src/tests/libxrpl/tx/wasm/host_context/MptokenKeylet.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
// `mptid` and `holder` are checked together in one condition rather than through
|
||||
// `invokeWithAccount`, so which one fired is not observable when both are malformed.
|
||||
struct MptokenKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const mptidBytes = Bytes(24, 0x7a);
|
||||
Bytes const holderBytes{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
|
||||
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14};
|
||||
|
||||
MPTID const mptid = MPTID::fromVoid(mptidBytes.data());
|
||||
AccountID const holder = AccountID::fromVoid(holderBytes.data());
|
||||
|
||||
Bytes const keylet = Bytes(32, 0xab);
|
||||
};
|
||||
|
||||
TEST_F(MptokenKeyletCall, MptidAndHolderForwardedAndKeyletWritten)
|
||||
{
|
||||
EXPECT_CALL(host, mptokenKeylet(testing::Eq(mptid), testing::Eq(holder)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.mptokenKeylet(bytesOf(mptidBytes), bytesOf(holderBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(MptokenKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, mptokenKeylet(testing::Eq(mptid), testing::Eq(holder)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.mptokenKeylet(bytesOf(mptidBytes), bytesOf(holderBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(MptokenKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, mptokenKeylet(testing::Eq(mptid), testing::Eq(holder)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"mptoken keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.mptokenKeylet(bytesOf(mptidBytes), bytesOf(holderBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("mptoken keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("mptokenKeylet"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(MptokenKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
EXPECT_CALL(host, mptokenKeylet(testing::Eq(mptid), testing::Eq(holder)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.mptokenKeylet(bytesOf(mptidBytes), bytesOf(holderBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(MptokenKeyletCall, MalformedMptidIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedMptid(MPTID::size() - 1, 0x7a);
|
||||
EXPECT_CALL(host, mptokenKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.mptokenKeylet(bytesOf(malformedMptid), bytesOf(holderBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// Distinct from a malformed mptid: the mptid is well-formed here, so this exercises the
|
||||
// holder's own check rather than the mptid's.
|
||||
TEST_F(MptokenKeyletCall, MalformedHolderIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedHolder(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, mptokenKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.mptokenKeylet(bytesOf(mptidBytes), bytesOf(malformedHolder), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// Both lengths are checked in one condition and both answer the same `InvalidParams`, so
|
||||
// which one fired is not observable here. What is: neither argument reaches the host.
|
||||
TEST_F(MptokenKeyletCall, BothArgumentsMalformedIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedMptid(MPTID::size() - 1, 0x7a);
|
||||
Bytes const malformedHolder(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, mptokenKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.mptokenKeylet(bytesOf(malformedMptid), bytesOf(malformedHolder), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
142
src/tests/libxrpl/tx/wasm/host_context/NFT.cpp
Normal file
142
src/tests/libxrpl/tx/wasm/host_context/NFT.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, the field cap, guest memory - are tested on the Rust
|
||||
// side, not here.
|
||||
struct NFTCall : HostContextTest
|
||||
{
|
||||
Bytes const accountBytes{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
|
||||
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14};
|
||||
Bytes const nftIdBytes{0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
|
||||
0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36,
|
||||
0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40};
|
||||
AccountID const account = AccountID::fromVoid(accountBytes.data());
|
||||
uint256 const nftId = uint256::fromVoid(nftIdBytes.data());
|
||||
};
|
||||
|
||||
TEST_F(NFTCall, AccountAndNftIdBecomeTypedArgumentsHostIsAskedFor)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getNFT(testing::Eq(account), testing::Eq(nftId)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFT(bytesOf(accountBytes), bytesOf(nftIdBytes), out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(value)));
|
||||
}
|
||||
|
||||
TEST_F(NFTCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getNFT(testing::Eq(account), testing::Eq(nftId)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFT(bytesOf(accountBytes), bytesOf(nftIdBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(NFTCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getNFT(testing::Eq(account), testing::Eq(nftId)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"nft came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFT(bytesOf(accountBytes), bytesOf(nftIdBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("nft came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getNFT"));
|
||||
}
|
||||
|
||||
TEST_F(NFTCall, MalformedAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedAccount(AccountID::size() - 1, 0xff);
|
||||
EXPECT_CALL(host, getNFT).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFT(bytesOf(malformedAccount), bytesOf(nftIdBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// Distinct from a malformed account: the account is well-formed here, so this exercises the
|
||||
// nft id's own check rather than the account's.
|
||||
TEST_F(NFTCall, MalformedNftIdIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedNftId(uint256::size() - 1, 0xff);
|
||||
EXPECT_CALL(host, getNFT).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFT(bytesOf(accountBytes), bytesOf(malformedNftId), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// The account's length is checked before the nft id's, but both checks answer `InvalidParams`,
|
||||
// so which one fired is not observable here. What is: neither argument reaches the host.
|
||||
TEST_F(NFTCall, BothArgumentsMalformedIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedAccount(AccountID::size() - 1, 0xff);
|
||||
Bytes const malformedNftId(uint256::size() - 1, 0xff);
|
||||
EXPECT_CALL(host, getNFT).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFT(bytesOf(malformedAccount), bytesOf(malformedNftId), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(NFTCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getNFT(testing::Eq(account), testing::Eq(nftId)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{value.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFT(bytesOf(accountBytes), bytesOf(nftIdBytes), out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(NFTCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getNFT(testing::Eq(account), testing::Eq(nftId)))
|
||||
.WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{value.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFT(bytesOf(accountBytes), bytesOf(nftIdBytes), out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(value)));
|
||||
}
|
||||
|
||||
TEST_F(NFTCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, getNFT(testing::Eq(account), testing::Eq(nftId)))
|
||||
.WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.getNFT(bytesOf(accountBytes), bytesOf(nftIdBytes), out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
81
src/tests/libxrpl/tx/wasm/host_context/NFTFlags.cpp
Normal file
81
src/tests/libxrpl/tx/wasm/host_context/NFTFlags.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
// `getNFTFlags` answers its value directly rather than through `answer`, so there is no out
|
||||
// region and no axis E.
|
||||
struct NFTFlagsCall : HostContextTest
|
||||
{
|
||||
Bytes const nftIdBytes{0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb,
|
||||
0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6,
|
||||
0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0};
|
||||
uint256 const nftId = uint256::fromVoid(nftIdBytes.data());
|
||||
};
|
||||
|
||||
TEST_F(NFTFlagsCall, NftIdBytesBecomeTypedArgumentHostIsAskedFor)
|
||||
{
|
||||
static constexpr std::int32_t kFlags = 0x0b;
|
||||
EXPECT_CALL(host, getNFTFlags(testing::Eq(nftId))).WillOnce(testing::Return(kFlags));
|
||||
|
||||
EXPECT_EQ(hostContext.getNFTFlags(bytesOf(nftIdBytes)), kFlags);
|
||||
}
|
||||
|
||||
TEST_F(NFTFlagsCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTFlags(testing::Eq(nftId)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTFlags(bytesOf(nftIdBytes)),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
}
|
||||
|
||||
TEST_F(NFTFlagsCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTFlags(testing::Eq(nftId)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"nft flags came apart"}));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTFlags(bytesOf(nftIdBytes)),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("nft flags came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getNFTFlags"));
|
||||
}
|
||||
|
||||
TEST_F(NFTFlagsCall, MalformedNftIdIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedNftId(uint256::size() - 1, 0xff);
|
||||
EXPECT_CALL(host, getNFTFlags).Times(0);
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTFlags(bytesOf(malformedNftId)),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// `getNFTFlags` answers its value directly rather than through `answer`, so a legitimate
|
||||
// flags word with the high bit set is bit-for-bit the same value as
|
||||
// `HostFunctionError::InternalFatal` (`INT32_MIN`) - the code `guarded` supplies for a thrown
|
||||
// exception. The ABI at this layer has no way to tell the two apart; this is a property of
|
||||
// the shape, not a bug to fix.
|
||||
TEST_F(NFTFlagsCall, HighBitFlagsAreIndistinguishableFromInternalFatal)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTFlags(testing::Eq(nftId)))
|
||||
.WillOnce(testing::Return(std::numeric_limits<std::int32_t>::min()));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTFlags(bytesOf(nftIdBytes)),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
106
src/tests/libxrpl/tx/wasm/host_context/NFTIssuer.cpp
Normal file
106
src/tests/libxrpl/tx/wasm/host_context/NFTIssuer.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
struct NFTIssuerCall : HostContextTest
|
||||
{
|
||||
Bytes const nftIdBytes{0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b,
|
||||
0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,
|
||||
0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70};
|
||||
uint256 const nftId = uint256::fromVoid(nftIdBytes.data());
|
||||
};
|
||||
|
||||
TEST_F(NFTIssuerCall, NftIdBytesBecomeTypedArgumentHostIsAskedFor)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getNFTIssuer(testing::Eq(nftId))).WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTIssuer(bytesOf(nftIdBytes), out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(value)));
|
||||
}
|
||||
|
||||
TEST_F(NFTIssuerCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTIssuer(testing::Eq(nftId)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTIssuer(bytesOf(nftIdBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(NFTIssuerCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTIssuer(testing::Eq(nftId)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"nft issuer came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTIssuer(bytesOf(nftIdBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("nft issuer came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getNFTIssuer"));
|
||||
}
|
||||
|
||||
TEST_F(NFTIssuerCall, MalformedNftIdIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedNftId(uint256::size() - 1, 0xff);
|
||||
EXPECT_CALL(host, getNFTIssuer).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTIssuer(bytesOf(malformedNftId), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(NFTIssuerCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getNFTIssuer(testing::Eq(nftId))).WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{value.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTIssuer(bytesOf(nftIdBytes), out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(NFTIssuerCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getNFTIssuer(testing::Eq(nftId))).WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{value.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTIssuer(bytesOf(nftIdBytes), out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(value)));
|
||||
}
|
||||
|
||||
TEST_F(NFTIssuerCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTIssuer(testing::Eq(nftId))).WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.getNFTIssuer(bytesOf(nftIdBytes), out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
90
src/tests/libxrpl/tx/wasm/host_context/NFTSequence.cpp
Normal file
90
src/tests/libxrpl/tx/wasm/host_context/NFTSequence.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
struct NFTSequenceCall : HostContextTest
|
||||
{
|
||||
Bytes const nftIdBytes{0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b,
|
||||
0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6,
|
||||
0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0};
|
||||
uint256 const nftId = uint256::fromVoid(nftIdBytes.data());
|
||||
static constexpr std::uint32_t kSequence = 0x89abcdef;
|
||||
Bytes const expectedBytes = bytesOfScalar(kSequence);
|
||||
};
|
||||
|
||||
TEST_F(NFTSequenceCall, NftIdBytesBecomeTypedArgumentHostIsAskedFor)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTSequence(testing::Eq(nftId))).WillOnce(testing::Return(kSequence));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.getNFTSequence(bytesOf(nftIdBytes), out.slice()), 4);
|
||||
EXPECT_TRUE(out.holds(bytesOf(expectedBytes)));
|
||||
}
|
||||
|
||||
TEST_F(NFTSequenceCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTSequence(testing::Eq(nftId)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTSequence(bytesOf(nftIdBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(NFTSequenceCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTSequence(testing::Eq(nftId)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"nft sequence came apart"}));
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTSequence(bytesOf(nftIdBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("nft sequence came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getNFTSequence"));
|
||||
}
|
||||
|
||||
TEST_F(NFTSequenceCall, MalformedNftIdIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedNftId(uint256::size() - 1, 0xff);
|
||||
EXPECT_CALL(host, getNFTSequence).Times(0);
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTSequence(bytesOf(malformedNftId), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(NFTSequenceCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTSequence(testing::Eq(nftId))).WillOnce(testing::Return(kSequence));
|
||||
|
||||
OutRegion out{3};
|
||||
EXPECT_EQ(hostContext.getNFTSequence(bytesOf(nftIdBytes), out.slice()), 4);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(NFTSequenceCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTSequence(testing::Eq(nftId))).WillOnce(testing::Return(kSequence));
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(hostContext.getNFTSequence(bytesOf(nftIdBytes), out.slice()), 4);
|
||||
EXPECT_TRUE(out.holds(bytesOf(expectedBytes)));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
90
src/tests/libxrpl/tx/wasm/host_context/NFTTaxon.cpp
Normal file
90
src/tests/libxrpl/tx/wasm/host_context/NFTTaxon.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
struct NFTTaxonCall : HostContextTest
|
||||
{
|
||||
Bytes const nftIdBytes{0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b,
|
||||
0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86,
|
||||
0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90};
|
||||
uint256 const nftId = uint256::fromVoid(nftIdBytes.data());
|
||||
static constexpr std::uint32_t kTaxon = 0x12345678;
|
||||
Bytes const expectedBytes = bytesOfScalar(kTaxon);
|
||||
};
|
||||
|
||||
TEST_F(NFTTaxonCall, NftIdBytesBecomeTypedArgumentHostIsAskedFor)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTTaxon(testing::Eq(nftId))).WillOnce(testing::Return(kTaxon));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.getNFTTaxon(bytesOf(nftIdBytes), out.slice()), 4);
|
||||
EXPECT_TRUE(out.holds(bytesOf(expectedBytes)));
|
||||
}
|
||||
|
||||
TEST_F(NFTTaxonCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTTaxon(testing::Eq(nftId)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTTaxon(bytesOf(nftIdBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(NFTTaxonCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTTaxon(testing::Eq(nftId)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"nft taxon came apart"}));
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTTaxon(bytesOf(nftIdBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("nft taxon came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getNFTTaxon"));
|
||||
}
|
||||
|
||||
TEST_F(NFTTaxonCall, MalformedNftIdIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedNftId(uint256::size() - 1, 0xff);
|
||||
EXPECT_CALL(host, getNFTTaxon).Times(0);
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTTaxon(bytesOf(malformedNftId), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(NFTTaxonCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTTaxon(testing::Eq(nftId))).WillOnce(testing::Return(kTaxon));
|
||||
|
||||
OutRegion out{3};
|
||||
EXPECT_EQ(hostContext.getNFTTaxon(bytesOf(nftIdBytes), out.slice()), 4);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(NFTTaxonCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTTaxon(testing::Eq(nftId))).WillOnce(testing::Return(kTaxon));
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(hostContext.getNFTTaxon(bytesOf(nftIdBytes), out.slice()), 4);
|
||||
EXPECT_TRUE(out.holds(bytesOf(expectedBytes)));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
66
src/tests/libxrpl/tx/wasm/host_context/NFTTransferFee.cpp
Normal file
66
src/tests/libxrpl/tx/wasm/host_context/NFTTransferFee.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
// `getNFTTransferFee` answers its value directly rather than through `answer`, so there is no
|
||||
// out region and no axis E.
|
||||
struct NFTTransferFeeCall : HostContextTest
|
||||
{
|
||||
Bytes const nftIdBytes{0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb,
|
||||
0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6,
|
||||
0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0};
|
||||
uint256 const nftId = uint256::fromVoid(nftIdBytes.data());
|
||||
};
|
||||
|
||||
TEST_F(NFTTransferFeeCall, NftIdBytesBecomeTypedArgumentHostIsAskedFor)
|
||||
{
|
||||
static constexpr std::int32_t kTransferFee = 314;
|
||||
EXPECT_CALL(host, getNFTTransferFee(testing::Eq(nftId)))
|
||||
.WillOnce(testing::Return(kTransferFee));
|
||||
|
||||
EXPECT_EQ(hostContext.getNFTTransferFee(bytesOf(nftIdBytes)), kTransferFee);
|
||||
}
|
||||
|
||||
TEST_F(NFTTransferFeeCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTTransferFee(testing::Eq(nftId)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTTransferFee(bytesOf(nftIdBytes)),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
}
|
||||
|
||||
TEST_F(NFTTransferFeeCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getNFTTransferFee(testing::Eq(nftId)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"nft transfer fee came apart"}));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTTransferFee(bytesOf(nftIdBytes)),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("nft transfer fee came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getNFTTransferFee"));
|
||||
}
|
||||
|
||||
TEST_F(NFTTransferFeeCall, MalformedNftIdIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedNftId(uint256::size() - 1, 0xff);
|
||||
EXPECT_CALL(host, getNFTTransferFee).Times(0);
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getNFTTransferFee(bytesOf(malformedNftId)),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
131
src/tests/libxrpl/tx/wasm/host_context/NftokenOfferKeylet.cpp
Normal file
131
src/tests/libxrpl/tx/wasm/host_context/NftokenOfferKeylet.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
struct NftokenOfferKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const accountBytes{0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,
|
||||
0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74};
|
||||
AccountID const account = AccountID::fromVoid(accountBytes.data());
|
||||
std::int32_t const seq = 13579;
|
||||
};
|
||||
|
||||
TEST_F(NftokenOfferKeyletCall, AccountAndSeqAreForwardedKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, nftokenOfferKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.nftokenOfferKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(NftokenOfferKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, nftokenOfferKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.nftokenOfferKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(NftokenOfferKeyletCall, ShortAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const shortAccount(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, nftokenOfferKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.nftokenOfferKeylet(bytesOf(shortAccount), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(NftokenOfferKeyletCall, LongAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const longAccount(AccountID::size() + 1, 0x01);
|
||||
EXPECT_CALL(host, nftokenOfferKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.nftokenOfferKeylet(bytesOf(longAccount), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(NftokenOfferKeyletCall, EmptyAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, nftokenOfferKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.nftokenOfferKeylet(bytesOf(Bytes{}), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(NftokenOfferKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, nftokenOfferKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"nftoken offer keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.nftokenOfferKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("nftoken offer keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("nftokenOfferKeylet"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(NftokenOfferKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, nftokenOfferKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.nftokenOfferKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(NftokenOfferKeyletCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, nftokenOfferKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.nftokenOfferKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(NftokenOfferKeyletCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, nftokenOfferKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.nftokenOfferKeylet(bytesOf(accountBytes), seq, out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
131
src/tests/libxrpl/tx/wasm/host_context/OfferKeylet.cpp
Normal file
131
src/tests/libxrpl/tx/wasm/host_context/OfferKeylet.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
struct OfferKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const accountBytes{0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
|
||||
0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84};
|
||||
AccountID const account = AccountID::fromVoid(accountBytes.data());
|
||||
std::int32_t const seq = 24680;
|
||||
};
|
||||
|
||||
TEST_F(OfferKeyletCall, AccountAndSeqAreForwardedKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, offerKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.offerKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(OfferKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, offerKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.offerKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(OfferKeyletCall, ShortAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const shortAccount(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, offerKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.offerKeylet(bytesOf(shortAccount), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(OfferKeyletCall, LongAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const longAccount(AccountID::size() + 1, 0x01);
|
||||
EXPECT_CALL(host, offerKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.offerKeylet(bytesOf(longAccount), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(OfferKeyletCall, EmptyAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, offerKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.offerKeylet(bytesOf(Bytes{}), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(OfferKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, offerKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"offer keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.offerKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("offer keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("offerKeylet"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(OfferKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, offerKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.offerKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(OfferKeyletCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, offerKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.offerKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(OfferKeyletCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, offerKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.offerKeylet(bytesOf(accountBytes), seq, out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
131
src/tests/libxrpl/tx/wasm/host_context/OracleKeylet.cpp
Normal file
131
src/tests/libxrpl/tx/wasm/host_context/OracleKeylet.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
struct OracleKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const accountBytes{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
|
||||
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14};
|
||||
AccountID const account = AccountID::fromVoid(accountBytes.data());
|
||||
std::int32_t const docId = 12345;
|
||||
};
|
||||
|
||||
TEST_F(OracleKeyletCall, AccountAndDocIdAreForwardedKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, oracleKeylet(account, static_cast<std::uint32_t>(docId)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.oracleKeylet(bytesOf(accountBytes), docId, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(OracleKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, oracleKeylet(account, static_cast<std::uint32_t>(docId)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.oracleKeylet(bytesOf(accountBytes), docId, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(OracleKeyletCall, ShortAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const shortAccount(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, oracleKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.oracleKeylet(bytesOf(shortAccount), docId, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(OracleKeyletCall, LongAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const longAccount(AccountID::size() + 1, 0x01);
|
||||
EXPECT_CALL(host, oracleKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.oracleKeylet(bytesOf(longAccount), docId, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(OracleKeyletCall, EmptyAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, oracleKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.oracleKeylet(bytesOf(Bytes{}), docId, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(OracleKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, oracleKeylet(account, static_cast<std::uint32_t>(docId)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"oracle keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.oracleKeylet(bytesOf(accountBytes), docId, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("oracle keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("oracleKeylet"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(OracleKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, oracleKeylet(account, static_cast<std::uint32_t>(docId)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.oracleKeylet(bytesOf(accountBytes), docId, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(OracleKeyletCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, oracleKeylet(account, static_cast<std::uint32_t>(docId)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.oracleKeylet(bytesOf(accountBytes), docId, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(OracleKeyletCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, oracleKeylet(account, static_cast<std::uint32_t>(docId)))
|
||||
.WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.oracleKeylet(bytesOf(accountBytes), docId, out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
86
src/tests/libxrpl/tx/wasm/host_context/ParentLedgerHash.cpp
Normal file
86
src/tests/libxrpl/tx/wasm/host_context/ParentLedgerHash.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// No D or F axis: `getParentLedgerHash` takes no argument, so there is nothing to decode wrong
|
||||
// and nothing whose forwarded identity to check.
|
||||
//
|
||||
// Unlike `getLedgerSqn`/`getParentLedgerTime`, the result is a `Hash` (a `uint256`) written
|
||||
// whole through `answer` (`invoke<false>`), not a scalar through `answerScalar` - so it is
|
||||
// asserted as bytes, the way `TxField.cpp` asserts its `Bytes` result, rather than as a
|
||||
// little-endian scalar.
|
||||
struct ParentLedgerHashCall : HostContextTest
|
||||
{
|
||||
Bytes const hashBytes{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
|
||||
0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
|
||||
0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20};
|
||||
Hash const hash = uint256::fromVoid(hashBytes.data());
|
||||
};
|
||||
|
||||
TEST_F(ParentLedgerHashCall, HostValueIsWrittenAsBytes)
|
||||
{
|
||||
EXPECT_CALL(host, getParentLedgerHash()).WillOnce(testing::Return(hash));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getParentLedgerHash(out.slice()), static_cast<std::int32_t>(hashBytes.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(hashBytes)));
|
||||
}
|
||||
|
||||
TEST_F(ParentLedgerHashCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getParentLedgerHash())
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getParentLedgerHash(out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(ParentLedgerHashCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getParentLedgerHash())
|
||||
.WillOnce(testing::Throw(std::runtime_error{"parent ledger hash came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getParentLedgerHash(out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("parent ledger hash came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getParentLedgerHash"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(ParentLedgerHashCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
EXPECT_CALL(host, getParentLedgerHash()).WillOnce(testing::Return(hash));
|
||||
|
||||
OutRegion out{hashBytes.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.getParentLedgerHash(out.slice()), static_cast<std::int32_t>(hashBytes.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(ParentLedgerHashCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
EXPECT_CALL(host, getParentLedgerHash()).WillOnce(testing::Return(hash));
|
||||
|
||||
OutRegion out{hashBytes.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.getParentLedgerHash(out.slice()), static_cast<std::int32_t>(hashBytes.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(hashBytes)));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
75
src/tests/libxrpl/tx/wasm/host_context/ParentLedgerTime.cpp
Normal file
75
src/tests/libxrpl/tx/wasm/host_context/ParentLedgerTime.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// No D or F axis: `getParentLedgerTime` takes no argument, so there is nothing to decode wrong
|
||||
// and nothing whose forwarded identity to check.
|
||||
struct ParentLedgerTimeCall : HostContextTest
|
||||
{
|
||||
static constexpr std::uint32_t kParentLedgerTime = 0x12345678;
|
||||
Bytes const expectedBytes = bytesOfScalar(kParentLedgerTime);
|
||||
};
|
||||
|
||||
TEST_F(ParentLedgerTimeCall, HostValueIsWrittenAsLittleEndianBytes)
|
||||
{
|
||||
EXPECT_CALL(host, getParentLedgerTime()).WillOnce(testing::Return(kParentLedgerTime));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.getParentLedgerTime(out.slice()), 4);
|
||||
EXPECT_TRUE(out.holds(bytesOf(expectedBytes)));
|
||||
}
|
||||
|
||||
TEST_F(ParentLedgerTimeCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getParentLedgerTime())
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::Unimplemented)));
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(
|
||||
hostContext.getParentLedgerTime(out.slice()),
|
||||
hfErrorToInt(HostFunctionError::Unimplemented));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(ParentLedgerTimeCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getParentLedgerTime())
|
||||
.WillOnce(testing::Throw(std::runtime_error{"parent ledger time came apart"}));
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(
|
||||
hostContext.getParentLedgerTime(out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("parent ledger time came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getParentLedgerTime"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(ParentLedgerTimeCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
EXPECT_CALL(host, getParentLedgerTime()).WillOnce(testing::Return(kParentLedgerTime));
|
||||
|
||||
OutRegion out{3};
|
||||
EXPECT_EQ(hostContext.getParentLedgerTime(out.slice()), 4);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(ParentLedgerTimeCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
EXPECT_CALL(host, getParentLedgerTime()).WillOnce(testing::Return(kParentLedgerTime));
|
||||
|
||||
OutRegion out{4};
|
||||
EXPECT_EQ(hostContext.getParentLedgerTime(out.slice()), 4);
|
||||
EXPECT_TRUE(out.holds(bytesOf(expectedBytes)));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
152
src/tests/libxrpl/tx/wasm/host_context/PaychannelKeylet.cpp
Normal file
152
src/tests/libxrpl/tx/wasm/host_context/PaychannelKeylet.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
//
|
||||
// `account` and `destination` are distinct byte patterns: a happy path built from two copies of
|
||||
// the same account would still pass if the two were swapped.
|
||||
struct PaychannelKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const accountBytes{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
|
||||
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14};
|
||||
Bytes const destinationBytes{0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
|
||||
0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84};
|
||||
AccountID const account = AccountID::fromVoid(accountBytes.data());
|
||||
AccountID const destination = AccountID::fromVoid(destinationBytes.data());
|
||||
std::int32_t const seq = 54321;
|
||||
};
|
||||
|
||||
TEST_F(PaychannelKeyletCall, AccountsAndSeqAreForwardedKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, paychannelKeylet(account, destination, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.paychannelKeylet(
|
||||
bytesOf(accountBytes), bytesOf(destinationBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(PaychannelKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, paychannelKeylet(account, destination, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.paychannelKeylet(
|
||||
bytesOf(accountBytes), bytesOf(destinationBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(PaychannelKeyletCall, MalformedAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedAccount(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, paychannelKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.paychannelKeylet(
|
||||
bytesOf(malformedAccount), bytesOf(destinationBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(PaychannelKeyletCall, MalformedDestinationIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedDestination(AccountID::size() + 1, 0x71);
|
||||
EXPECT_CALL(host, paychannelKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.paychannelKeylet(
|
||||
bytesOf(accountBytes), bytesOf(malformedDestination), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// Both ids fail one combined length check, so a call malformed in both places answers the
|
||||
// same `InvalidParams` as either alone; what's observable is that the host is never asked.
|
||||
TEST_F(PaychannelKeyletCall, BothAccountsMalformedIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedAccount(AccountID::size() - 1, 0x01);
|
||||
Bytes const malformedDestination(AccountID::size() - 1, 0x71);
|
||||
EXPECT_CALL(host, paychannelKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.paychannelKeylet(
|
||||
bytesOf(malformedAccount), bytesOf(malformedDestination), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(PaychannelKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, paychannelKeylet(account, destination, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"paychannel keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.paychannelKeylet(
|
||||
bytesOf(accountBytes), bytesOf(destinationBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("paychannel keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("paychannelKeylet"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(PaychannelKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, paychannelKeylet(account, destination, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.paychannelKeylet(
|
||||
bytesOf(accountBytes), bytesOf(destinationBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(PaychannelKeyletCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, paychannelKeylet(account, destination, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.paychannelKeylet(
|
||||
bytesOf(accountBytes), bytesOf(destinationBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(PaychannelKeyletCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, paychannelKeylet(account, destination, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.paychannelKeylet(
|
||||
bytesOf(accountBytes), bytesOf(destinationBytes), seq, out.slice()),
|
||||
0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
@@ -0,0 +1,131 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
struct PermissionedDomainKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const accountBytes{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
|
||||
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14};
|
||||
AccountID const account = AccountID::fromVoid(accountBytes.data());
|
||||
std::int32_t const seq = 12345;
|
||||
};
|
||||
|
||||
TEST_F(PermissionedDomainKeyletCall, AccountAndSeqAreForwardedKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, permissionedDomainKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.permissionedDomainKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(PermissionedDomainKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, permissionedDomainKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.permissionedDomainKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(PermissionedDomainKeyletCall, ShortAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const shortAccount(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, permissionedDomainKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.permissionedDomainKeylet(bytesOf(shortAccount), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(PermissionedDomainKeyletCall, LongAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const longAccount(AccountID::size() + 1, 0x01);
|
||||
EXPECT_CALL(host, permissionedDomainKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.permissionedDomainKeylet(bytesOf(longAccount), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(PermissionedDomainKeyletCall, EmptyAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, permissionedDomainKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.permissionedDomainKeylet(bytesOf(Bytes{}), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(PermissionedDomainKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, permissionedDomainKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"permissioned domain keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.permissionedDomainKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("permissioned domain keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("permissionedDomainKeylet"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(PermissionedDomainKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, permissionedDomainKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.permissionedDomainKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(PermissionedDomainKeyletCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, permissionedDomainKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.permissionedDomainKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(PermissionedDomainKeyletCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, permissionedDomainKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.permissionedDomainKeylet(bytesOf(accountBytes), seq, out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
81
src/tests/libxrpl/tx/wasm/host_context/Sha512Half.cpp
Normal file
81
src/tests/libxrpl/tx/wasm/host_context/Sha512Half.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
#include <tx/wasm/MockHostFunctions.h>
|
||||
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// `host_calls/Sha512Half.cpp` runs the digest through the engine; what is left at this layer is
|
||||
// its own contract - the out-region rule, `guarded`, and an empty input.
|
||||
struct Sha512HalfDirectCall : HostContextTest
|
||||
{
|
||||
Bytes const data{'a', 'b', 'c'};
|
||||
Bytes const digestBytes = Bytes(32, 0x0a);
|
||||
Hash const digest = uint256::fromVoid(digestBytes.data());
|
||||
};
|
||||
|
||||
TEST_F(Sha512HalfDirectCall, DataForwardedAndDigestWritten)
|
||||
{
|
||||
EXPECT_CALL(host, computeSha512HalfHash(BytesAre("abc"))).WillOnce(testing::Return(digest));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.sha512Half(bytesOf(data), out.slice()), 32);
|
||||
EXPECT_TRUE(out.holds(bytesOf(digestBytes)));
|
||||
}
|
||||
|
||||
TEST_F(Sha512HalfDirectCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, computeSha512HalfHash)
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::InvalidParams)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.sha512Half(bytesOf(data), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(Sha512HalfDirectCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, computeSha512HalfHash)
|
||||
.WillOnce(testing::Throw(std::runtime_error{"sha512 half came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.sha512Half(bytesOf(data), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("sha512 half came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("sha512Half"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(Sha512HalfDirectCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
EXPECT_CALL(host, computeSha512HalfHash(BytesAre("abc"))).WillOnce(testing::Return(digest));
|
||||
|
||||
OutRegion out{31};
|
||||
EXPECT_EQ(hostContext.sha512Half(bytesOf(data), out.slice()), 32);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
// Nothing in the hash requires a non-empty input, so an empty slice is hashed like any other,
|
||||
// not refused.
|
||||
TEST_F(Sha512HalfDirectCall, EmptyInputIsHashedLikeAnyOther)
|
||||
{
|
||||
EXPECT_CALL(host, computeSha512HalfHash(testing::Property(&Slice::empty, true)))
|
||||
.WillOnce(testing::Return(digest));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.sha512Half(bytesOf(Bytes{}), out.slice()), 32);
|
||||
EXPECT_TRUE(out.holds(bytesOf(digestBytes)));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
126
src/tests/libxrpl/tx/wasm/host_context/SignerListKeylet.cpp
Normal file
126
src/tests/libxrpl/tx/wasm/host_context/SignerListKeylet.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
struct SignerListKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const accountBytes{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
|
||||
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14};
|
||||
AccountID const account = AccountID::fromVoid(accountBytes.data());
|
||||
};
|
||||
|
||||
TEST_F(SignerListKeyletCall, AccountIsForwardedKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, signerListKeylet(account)).WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.signerListKeylet(bytesOf(accountBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(SignerListKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, signerListKeylet(account))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.signerListKeylet(bytesOf(accountBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(SignerListKeyletCall, ShortAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const shortAccount(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, signerListKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.signerListKeylet(bytesOf(shortAccount), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(SignerListKeyletCall, LongAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const longAccount(AccountID::size() + 1, 0x01);
|
||||
EXPECT_CALL(host, signerListKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.signerListKeylet(bytesOf(longAccount), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(SignerListKeyletCall, EmptyAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, signerListKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.signerListKeylet(bytesOf(Bytes{}), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(SignerListKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, signerListKeylet(account))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"signer list keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.signerListKeylet(bytesOf(accountBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("signer list keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("signerListKeylet"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(SignerListKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, signerListKeylet(account)).WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.signerListKeylet(bytesOf(accountBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(SignerListKeyletCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, signerListKeylet(account)).WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.signerListKeylet(bytesOf(accountBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(SignerListKeyletCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, signerListKeylet(account)).WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.signerListKeylet(bytesOf(accountBytes), out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
131
src/tests/libxrpl/tx/wasm/host_context/TicketKeylet.cpp
Normal file
131
src/tests/libxrpl/tx/wasm/host_context/TicketKeylet.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
struct TicketKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const accountBytes{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
|
||||
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14};
|
||||
AccountID const account = AccountID::fromVoid(accountBytes.data());
|
||||
std::int32_t const seq = 12345;
|
||||
};
|
||||
|
||||
TEST_F(TicketKeyletCall, AccountAndSeqAreForwardedKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, ticketKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.ticketKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(TicketKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, ticketKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.ticketKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(TicketKeyletCall, ShortAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const shortAccount(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, ticketKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.ticketKeylet(bytesOf(shortAccount), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(TicketKeyletCall, LongAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const longAccount(AccountID::size() + 1, 0x01);
|
||||
EXPECT_CALL(host, ticketKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.ticketKeylet(bytesOf(longAccount), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(TicketKeyletCall, EmptyAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, ticketKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.ticketKeylet(bytesOf(Bytes{}), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(TicketKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, ticketKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"ticket keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.ticketKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("ticket keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("ticketKeylet"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(TicketKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, ticketKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.ticketKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(TicketKeyletCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, ticketKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.ticketKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(TicketKeyletCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, ticketKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.ticketKeylet(bytesOf(accountBytes), seq, out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
54
src/tests/libxrpl/tx/wasm/host_context/Trace.cpp
Normal file
54
src/tests/libxrpl/tx/wasm/host_context/Trace.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
// For `TraceDataType`, which the bridge declares and this header defines.
|
||||
#include <xrpl_wasm_vm_ffi_cxxbridge/lib.h>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// `trace` returns `void` and answers the guest nothing in every case, success or failure alike.
|
||||
// It is not wrapped in `guarded`: it has its own try/catch, so a throw is swallowed here rather
|
||||
// than escaping a `noexcept` method. `host_calls/Trace.cpp` already renders all seven
|
||||
// `TraceDataType`s through the engine; this file does not repeat that.
|
||||
struct TraceDirectCall : HostContextTest
|
||||
{
|
||||
};
|
||||
|
||||
// One rendering, to show this layer forwards at all - every `TraceDataType` is
|
||||
// `host_calls/Trace.cpp`'s job.
|
||||
TEST_F(TraceDirectCall, MessageAndDataReachHostAsRenderedText)
|
||||
{
|
||||
EXPECT_CALL(host, trace(std::string_view("note"), std::string_view("hi")));
|
||||
|
||||
hostContext.trace("note", bytesOf(Bytes{'h', 'i'}), TraceDataType::AsText);
|
||||
}
|
||||
|
||||
// The catch sits in `trace` itself, not in `guarded`. It logs at trace level, below the
|
||||
// fixture's default threshold, so the threshold is lowered to observe it.
|
||||
TEST_F(TraceDirectCall, HostExceptionIsSwallowedRatherThanEscaping)
|
||||
{
|
||||
sink.threshold(beast::Severity::Trace);
|
||||
EXPECT_CALL(host, trace).WillOnce(testing::Throw(std::runtime_error{"trace sink came apart"}));
|
||||
|
||||
hostContext.trace("note", bytesOf(Bytes{'h', 'i'}), TraceDataType::AsText);
|
||||
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("trace sink came apart"));
|
||||
}
|
||||
|
||||
// The cap is on message and data together, not on data alone.
|
||||
TEST_F(TraceDirectCall, MessagePlusDataPastCapIsDroppedWithoutAskingHost)
|
||||
{
|
||||
Bytes const data(kMaxWasmDataLength, 0x41);
|
||||
EXPECT_CALL(host, trace).Times(0);
|
||||
|
||||
hostContext.trace("x", bytesOf(data), TraceDataType::AsText);
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
180
src/tests/libxrpl/tx/wasm/host_context/TrustLineKeylet.cpp
Normal file
180
src/tests/libxrpl/tx/wasm/host_context/TrustLineKeylet.cpp
Normal file
@@ -0,0 +1,180 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
//
|
||||
// `account1` and `account2` are distinct byte patterns: a happy path built from two copies of
|
||||
// the same account would still pass if the two were swapped.
|
||||
struct TrustLineKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const account1Bytes{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
|
||||
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14};
|
||||
Bytes const account2Bytes{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a,
|
||||
0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44};
|
||||
Bytes const currencyBytes{0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,
|
||||
0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74};
|
||||
AccountID const account1 = AccountID::fromVoid(account1Bytes.data());
|
||||
AccountID const account2 = AccountID::fromVoid(account2Bytes.data());
|
||||
Currency const currency = Currency::fromVoid(currencyBytes.data());
|
||||
};
|
||||
|
||||
TEST_F(TrustLineKeyletCall, AccountsAndCurrencyAreForwardedKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, trustLineKeylet(account1, account2, currency))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.trustLineKeylet(
|
||||
bytesOf(account1Bytes), bytesOf(account2Bytes), bytesOf(currencyBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(TrustLineKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, trustLineKeylet(account1, account2, currency))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.trustLineKeylet(
|
||||
bytesOf(account1Bytes), bytesOf(account2Bytes), bytesOf(currencyBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(TrustLineKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, trustLineKeylet(account1, account2, currency))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"trust line keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.trustLineKeylet(
|
||||
bytesOf(account1Bytes), bytesOf(account2Bytes), bytesOf(currencyBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("trust line keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("trustLineKeylet"));
|
||||
}
|
||||
|
||||
TEST_F(TrustLineKeyletCall, MalformedAccount1IsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedAccount1(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, trustLineKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.trustLineKeylet(
|
||||
bytesOf(malformedAccount1),
|
||||
bytesOf(account2Bytes),
|
||||
bytesOf(currencyBytes),
|
||||
out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(TrustLineKeyletCall, MalformedAccount2IsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedAccount2(AccountID::size() + 1, 0x31);
|
||||
EXPECT_CALL(host, trustLineKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.trustLineKeylet(
|
||||
bytesOf(account1Bytes),
|
||||
bytesOf(malformedAccount2),
|
||||
bytesOf(currencyBytes),
|
||||
out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(TrustLineKeyletCall, MalformedCurrencyIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedCurrency(Currency::size() - 1, 0x61);
|
||||
EXPECT_CALL(host, trustLineKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.trustLineKeylet(
|
||||
bytesOf(account1Bytes),
|
||||
bytesOf(account2Bytes),
|
||||
bytesOf(malformedCurrency),
|
||||
out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// The currency length is checked before either account's, but every malformed shape answers
|
||||
// the same `InvalidParams`, so a call malformed in both places cannot show which check fired.
|
||||
// What's observable: the host is never asked.
|
||||
TEST_F(TrustLineKeyletCall, CurrencyAndAccountBothMalformedIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const malformedCurrency(Currency::size() - 1, 0x61);
|
||||
Bytes const malformedAccount1(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, trustLineKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.trustLineKeylet(
|
||||
bytesOf(malformedAccount1),
|
||||
bytesOf(account2Bytes),
|
||||
bytesOf(malformedCurrency),
|
||||
out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(TrustLineKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, trustLineKeylet(account1, account2, currency))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.trustLineKeylet(
|
||||
bytesOf(account1Bytes), bytesOf(account2Bytes), bytesOf(currencyBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(TrustLineKeyletCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, trustLineKeylet(account1, account2, currency))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.trustLineKeylet(
|
||||
bytesOf(account1Bytes), bytesOf(account2Bytes), bytesOf(currencyBytes), out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(TrustLineKeyletCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, trustLineKeylet(account1, account2, currency))
|
||||
.WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.trustLineKeylet(
|
||||
bytesOf(account1Bytes), bytesOf(account2Bytes), bytesOf(currencyBytes), out.slice()),
|
||||
0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
56
src/tests/libxrpl/tx/wasm/host_context/TxArrayLen.cpp
Normal file
56
src/tests/libxrpl/tx/wasm/host_context/TxArrayLen.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
#include <xrpl/protocol/SField.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// `getTxArrayLen` answers its count directly rather than through an out region: no axis E, no
|
||||
// `OutRegion`, and the happy path asserts the returned count.
|
||||
struct TxArrayLenCall : HostContextTest
|
||||
{
|
||||
std::int32_t fieldCode = sfBalance.getCode();
|
||||
};
|
||||
|
||||
TEST_F(TxArrayLenCall, FieldCodeBecomesSFieldHostIsAskedFor)
|
||||
{
|
||||
EXPECT_CALL(host, getTxArrayLen(testing::Ref(sfBalance))).WillOnce(testing::Return(5));
|
||||
|
||||
EXPECT_EQ(hostContext.getTxArrayLen(fieldCode), 5);
|
||||
}
|
||||
|
||||
// `NoArray` is what a field that is not an array actually answers, so it stands in for axis B
|
||||
// here rather than an arbitrary code.
|
||||
TEST_F(TxArrayLenCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getTxArrayLen(testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::NoArray)));
|
||||
|
||||
EXPECT_EQ(hostContext.getTxArrayLen(fieldCode), hfErrorToInt(HostFunctionError::NoArray));
|
||||
}
|
||||
|
||||
TEST_F(TxArrayLenCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getTxArrayLen(testing::Ref(sfBalance)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"tx array len came apart"}));
|
||||
|
||||
EXPECT_EQ(hostContext.getTxArrayLen(fieldCode), hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("tx array len came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getTxArrayLen"));
|
||||
}
|
||||
|
||||
TEST_F(TxArrayLenCall, UnknownFieldCodeIsRefusedWithoutAskingHost)
|
||||
{
|
||||
fieldCode = 0x7fff'0000; // a code nothing is registered under
|
||||
EXPECT_CALL(host, getTxArrayLen).Times(0);
|
||||
|
||||
EXPECT_EQ(hostContext.getTxArrayLen(fieldCode), hfErrorToInt(HostFunctionError::InvalidField));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
76
src/tests/libxrpl/tx/wasm/host_context/TxNestedArrayLen.cpp
Normal file
76
src/tests/libxrpl/tx/wasm/host_context/TxNestedArrayLen.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, the field cap, guest memory - are tested on the Rust
|
||||
// side, not here.
|
||||
//
|
||||
// No out region and no axis E: `getTxNestedArrayLen` answers the array's element count
|
||||
// directly rather than through a written buffer.
|
||||
struct TxNestedArrayLenCall : HostContextTest
|
||||
{
|
||||
std::vector<std::int32_t> const steps{5, -12, 130};
|
||||
Bytes const locatorBytes = bytesOfSteps(steps);
|
||||
};
|
||||
|
||||
TEST_F(TxNestedArrayLenCall, LocatorBytesBecomeFieldLocatorHostReturnsCount)
|
||||
{
|
||||
EXPECT_CALL(host, getTxNestedArrayLen(LocatorEquals(steps))).WillOnce(testing::Return(7));
|
||||
|
||||
EXPECT_EQ(hostContext.getTxNestedArrayLen(bytesOf(locatorBytes)), 7);
|
||||
}
|
||||
|
||||
// `NoArray` - the field the locator resolves to is not an array - is the error this shape
|
||||
// most plausibly returns, so it stands in for axis B.
|
||||
TEST_F(TxNestedArrayLenCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getTxNestedArrayLen(LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::NoArray)));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getTxNestedArrayLen(bytesOf(locatorBytes)),
|
||||
hfErrorToInt(HostFunctionError::NoArray));
|
||||
}
|
||||
|
||||
TEST_F(TxNestedArrayLenCall, EmptyLocatorIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, getTxNestedArrayLen).Times(0);
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getTxNestedArrayLen(bytesOf(Bytes{})),
|
||||
hfErrorToInt(HostFunctionError::LocatorMalformed));
|
||||
}
|
||||
|
||||
// Distinct from an empty locator: `invokeWithLocator` checks the two conditions separately.
|
||||
TEST_F(TxNestedArrayLenCall, MisalignedLocatorLengthIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const oddLength{1, 2, 3};
|
||||
EXPECT_CALL(host, getTxNestedArrayLen).Times(0);
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getTxNestedArrayLen(bytesOf(oddLength)),
|
||||
hfErrorToInt(HostFunctionError::LocatorMalformed));
|
||||
}
|
||||
|
||||
TEST_F(TxNestedArrayLenCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getTxNestedArrayLen(LocatorEquals(steps)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"tx nested array len came apart"}));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.getTxNestedArrayLen(bytesOf(locatorBytes)),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("tx nested array len came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getTxNestedArrayLen"));
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
117
src/tests/libxrpl/tx/wasm/host_context/TxNestedField.cpp
Normal file
117
src/tests/libxrpl/tx/wasm/host_context/TxNestedField.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
#include <vector>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, the field cap, guest memory - are tested on the Rust
|
||||
// side, not here.
|
||||
struct TxNestedFieldCall : HostContextTest
|
||||
{
|
||||
std::vector<std::int32_t> const steps{5, -12, 130};
|
||||
Bytes const locatorBytes = bytesOfSteps(steps);
|
||||
};
|
||||
|
||||
TEST_F(TxNestedFieldCall, LocatorBytesBecomeFieldLocatorHostIsAskedFor)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getTxNestedField(LocatorEquals(steps))).WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getTxNestedField(bytesOf(locatorBytes), out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(value)));
|
||||
}
|
||||
|
||||
TEST_F(TxNestedFieldCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, getTxNestedField(LocatorEquals(steps)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::NotLeafField)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getTxNestedField(bytesOf(locatorBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::NotLeafField));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(TxNestedFieldCall, EmptyLocatorIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, getTxNestedField).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getTxNestedField(bytesOf(Bytes{}), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LocatorMalformed));
|
||||
}
|
||||
|
||||
// Distinct from an empty locator: `invokeWithLocator` checks the two conditions separately.
|
||||
TEST_F(TxNestedFieldCall, MisalignedLocatorLengthIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const oddLength{1, 2, 3};
|
||||
EXPECT_CALL(host, getTxNestedField).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getTxNestedField(bytesOf(oddLength), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LocatorMalformed));
|
||||
}
|
||||
|
||||
TEST_F(TxNestedFieldCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, getTxNestedField(LocatorEquals(steps)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"nested field came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.getTxNestedField(bytesOf(locatorBytes), out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("nested field came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("getTxNestedField"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(TxNestedFieldCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getTxNestedField(LocatorEquals(steps))).WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{value.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.getTxNestedField(bytesOf(locatorBytes), out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(TxNestedFieldCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const value{1, 2, 3};
|
||||
EXPECT_CALL(host, getTxNestedField(LocatorEquals(steps))).WillOnce(testing::Return(value));
|
||||
|
||||
OutRegion out{value.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.getTxNestedField(bytesOf(locatorBytes), out.slice()),
|
||||
static_cast<std::int32_t>(value.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(value)));
|
||||
}
|
||||
|
||||
TEST_F(TxNestedFieldCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, getTxNestedField(LocatorEquals(steps))).WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.getTxNestedField(bytesOf(locatorBytes), out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
58
src/tests/libxrpl/tx/wasm/host_context/UpdateData.cpp
Normal file
58
src/tests/libxrpl/tx/wasm/host_context/UpdateData.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#include <xrpl/basics/Slice.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
#include <tx/wasm/MockHostFunctions.h>
|
||||
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The other non-`const` host method; it answers the byte count stored directly, with no out
|
||||
// region.
|
||||
struct UpdateDataCall : HostContextTest
|
||||
{
|
||||
Bytes const data{'h', 'e', 'l', 'l', 'o'};
|
||||
};
|
||||
|
||||
TEST_F(UpdateDataCall, DataForwardedByteCountReturned)
|
||||
{
|
||||
EXPECT_CALL(host, updateData(BytesAre("hello"))).WillOnce(testing::Return(5));
|
||||
|
||||
EXPECT_EQ(hostContext.updateData(bytesOf(data)), 5);
|
||||
}
|
||||
|
||||
TEST_F(UpdateDataCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, updateData(BytesAre("hello")))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::DataFieldTooLarge)));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.updateData(bytesOf(data)), hfErrorToInt(HostFunctionError::DataFieldTooLarge));
|
||||
}
|
||||
|
||||
TEST_F(UpdateDataCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, updateData(BytesAre("hello")))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"update data came apart"}));
|
||||
|
||||
EXPECT_EQ(
|
||||
hostContext.updateData(bytesOf(data)), hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("update data came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("updateData"));
|
||||
}
|
||||
|
||||
// An empty `rust::Slice` has a null `data()`; `updateData` forwards it as an empty `Slice`
|
||||
// rather than treating it as malformed.
|
||||
TEST_F(UpdateDataCall, EmptyInputRegionForwardsAsEmptySlice)
|
||||
{
|
||||
EXPECT_CALL(host, updateData(testing::Property(&Slice::empty, true)))
|
||||
.WillOnce(testing::Return(0));
|
||||
|
||||
EXPECT_EQ(hostContext.updateData(bytesOf(Bytes{})), 0);
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
131
src/tests/libxrpl/tx/wasm/host_context/VaultKeylet.cpp
Normal file
131
src/tests/libxrpl/tx/wasm/host_context/VaultKeylet.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/tx/wasm/WasmCommon.h>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <tx/wasm/HostContextFixture.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace xrpl::test {
|
||||
|
||||
// The engine's own rules - buffer-fit, guest memory - are tested on the Rust side, not here.
|
||||
struct VaultKeyletCall : HostContextTest
|
||||
{
|
||||
Bytes const accountBytes{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a,
|
||||
0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14};
|
||||
AccountID const account = AccountID::fromVoid(accountBytes.data());
|
||||
std::int32_t const seq = 12345;
|
||||
};
|
||||
|
||||
TEST_F(VaultKeyletCall, AccountAndSeqAreForwardedKeyletIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, vaultKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.vaultKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(VaultKeyletCall, HostErrorBecomesContractReturnValue)
|
||||
{
|
||||
EXPECT_CALL(host, vaultKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(std::unexpected(HostFunctionError::LedgerObjNotFound)));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.vaultKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::LedgerObjNotFound));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(VaultKeyletCall, ShortAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const shortAccount(AccountID::size() - 1, 0x01);
|
||||
EXPECT_CALL(host, vaultKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.vaultKeylet(bytesOf(shortAccount), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(VaultKeyletCall, LongAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
Bytes const longAccount(AccountID::size() + 1, 0x01);
|
||||
EXPECT_CALL(host, vaultKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.vaultKeylet(bytesOf(longAccount), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(VaultKeyletCall, EmptyAccountIsRefusedWithoutAskingHost)
|
||||
{
|
||||
EXPECT_CALL(host, vaultKeylet).Times(0);
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.vaultKeylet(bytesOf(Bytes{}), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InvalidParams));
|
||||
}
|
||||
|
||||
TEST_F(VaultKeyletCall, HostExceptionBecomesInternalFatalAndIsLogged)
|
||||
{
|
||||
EXPECT_CALL(host, vaultKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Throw(std::runtime_error{"vault keylet came apart"}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(
|
||||
hostContext.vaultKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
hfErrorToInt(HostFunctionError::InternalFatal));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("vault keylet came apart"));
|
||||
EXPECT_THAT(logged(), testing::HasSubstr("vaultKeylet"));
|
||||
}
|
||||
|
||||
// The out-region contract: write only if the whole value fits, and return the true length
|
||||
// either way.
|
||||
TEST_F(VaultKeyletCall, ShortOutRegionWritesNothingAndReturnsTrueLength)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, vaultKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size() - 1};
|
||||
EXPECT_EQ(
|
||||
hostContext.vaultKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
TEST_F(VaultKeyletCall, OutRegionOfExactSizeIsWritten)
|
||||
{
|
||||
Bytes const keylet(32, 0xab);
|
||||
EXPECT_CALL(host, vaultKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(keylet));
|
||||
|
||||
OutRegion out{keylet.size()};
|
||||
EXPECT_EQ(
|
||||
hostContext.vaultKeylet(bytesOf(accountBytes), seq, out.slice()),
|
||||
static_cast<std::int32_t>(keylet.size()));
|
||||
EXPECT_TRUE(out.holds(bytesOf(keylet)));
|
||||
}
|
||||
|
||||
TEST_F(VaultKeyletCall, EmptyResultAnswersZeroAndWritesNothing)
|
||||
{
|
||||
EXPECT_CALL(host, vaultKeylet(account, static_cast<std::uint32_t>(seq)))
|
||||
.WillOnce(testing::Return(Bytes{}));
|
||||
|
||||
OutRegion out{32};
|
||||
EXPECT_EQ(hostContext.vaultKeylet(bytesOf(accountBytes), seq, out.slice()), 0);
|
||||
EXPECT_FALSE(out.wasWritten());
|
||||
}
|
||||
|
||||
} // namespace xrpl::test
|
||||
Reference in New Issue
Block a user