mirror of
https://github.com/XRPLF/clio.git
synced 2025-11-20 11:45:53 +00:00
1670 lines
69 KiB
C++
1670 lines
69 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of clio: https://github.com/XRPLF/clio
|
|
Copyright (c) 2023, the clio developers.
|
|
|
|
Permission to use, copy, modify, and distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
//==============================================================================
|
|
|
|
#include "data/AmendmentCenter.hpp"
|
|
#include "data/Types.hpp"
|
|
#include "rpc/Errors.hpp"
|
|
#include "rpc/RPCHelpers.hpp"
|
|
#include "rpc/common/AnyHandler.hpp"
|
|
#include "rpc/common/Types.hpp"
|
|
#include "rpc/handlers/BookOffers.hpp"
|
|
#include "util/HandlerBaseTestFixture.hpp"
|
|
#include "util/MockAmendmentCenter.hpp"
|
|
#include "util/NameGenerator.hpp"
|
|
#include "util/TestObject.hpp"
|
|
|
|
#include <boost/asio/spawn.hpp>
|
|
#include <boost/json/parse.hpp>
|
|
#include <fmt/format.h>
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
#include <xrpl/basics/Blob.h>
|
|
#include <xrpl/basics/base_uint.h>
|
|
#include <xrpl/protocol/AccountID.h>
|
|
#include <xrpl/protocol/Indexes.h>
|
|
#include <xrpl/protocol/LedgerFormats.h>
|
|
#include <xrpl/protocol/LedgerHeader.h>
|
|
#include <xrpl/protocol/STObject.h>
|
|
#include <xrpl/protocol/UintTypes.h>
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <iterator>
|
|
#include <map>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
constexpr auto kACCOUNT = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn";
|
|
constexpr auto kACCOUNT2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun";
|
|
|
|
constexpr auto kLEDGER_HASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652";
|
|
constexpr auto kINDEX1 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC";
|
|
constexpr auto kINDEX2 = "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321";
|
|
|
|
constexpr auto kPAYS20_USD_GETS10_XRP_BOOK_DIR = "43B83ADC452B85FCBADA6CAEAC5181C255A213630D58FFD455071AFD498D0000";
|
|
|
|
constexpr auto kPAYS20_XRP_GETS10_USD_BOOK_DIR = "7B1767D41DBCE79D9585CF9D0262A5FEC45E5206FF524F8B55071AFD498D0000";
|
|
|
|
constexpr auto kTRANSFER_RATE_X2 = 2000000000;
|
|
|
|
constexpr auto kDOMAIN = "F10D0CC9A0F9A3CBF585B80BE09A186483668FDBDD39AA7E3370F3649CE134E5";
|
|
|
|
struct ParameterTestBundle {
|
|
std::string testName;
|
|
std::string testJson;
|
|
std::string expectedError;
|
|
std::string expectedErrorMessage;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
using namespace rpc;
|
|
using namespace data;
|
|
namespace json = boost::json;
|
|
using namespace testing;
|
|
|
|
struct RPCBookOffersHandlerTest : HandlerBaseTest {
|
|
RPCBookOffersHandlerTest()
|
|
{
|
|
backend_->setRange(10, 300);
|
|
}
|
|
|
|
protected:
|
|
StrictMockAmendmentCenterSharedPtr mockAmendmentCenterPtr_;
|
|
};
|
|
|
|
struct RPCBookOffersParameterTest : RPCBookOffersHandlerTest, WithParamInterface<ParameterTestBundle> {};
|
|
|
|
TEST_P(RPCBookOffersParameterTest, CheckError)
|
|
{
|
|
auto bundle = GetParam();
|
|
auto const handler = AnyHandler{BookOffersHandler{backend_, mockAmendmentCenterPtr_}};
|
|
runSpawn([&](boost::asio::yield_context yield) {
|
|
auto const output = handler.process(json::parse(bundle.testJson), Context{.yield = yield});
|
|
ASSERT_FALSE(output);
|
|
auto const err = rpc::makeError(output.result.error());
|
|
EXPECT_EQ(err.at("error").as_string(), bundle.expectedError);
|
|
EXPECT_EQ(err.at("error_message").as_string(), bundle.expectedErrorMessage);
|
|
});
|
|
}
|
|
|
|
static auto
|
|
generateParameterBookOffersTestBundles()
|
|
{
|
|
return std::vector<ParameterTestBundle>{
|
|
ParameterTestBundle{
|
|
.testName = "MissingTakerGets",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "USD",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
}
|
|
})JSON",
|
|
.expectedError = "invalidParams",
|
|
.expectedErrorMessage = "Required field 'taker_gets' missing"
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "MissingTakerPays",
|
|
.testJson = R"JSON({
|
|
"taker_gets": {
|
|
"currency": "USD",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
}
|
|
})JSON",
|
|
.expectedError = "invalidParams",
|
|
.expectedErrorMessage = "Required field 'taker_pays' missing"
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "WrongTypeTakerPays",
|
|
.testJson = R"JSON({
|
|
"taker_pays": "wrong",
|
|
"taker_gets": {
|
|
"currency": "XRP"
|
|
}
|
|
})JSON",
|
|
.expectedError = "invalidParams",
|
|
.expectedErrorMessage = "Invalid parameters."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "WrongTypeTakerGets",
|
|
.testJson = R"JSON({
|
|
"taker_gets": "wrong",
|
|
"taker_pays": {
|
|
"currency": "XRP"
|
|
}
|
|
})JSON",
|
|
.expectedError = "invalidParams",
|
|
.expectedErrorMessage = "Invalid parameters."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "TakerPaysMissingCurrency",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {},
|
|
"taker_gets": {
|
|
"currency": "XRP"
|
|
}
|
|
})JSON",
|
|
.expectedError = "invalidParams",
|
|
.expectedErrorMessage = "Required field 'currency' missing"
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "TakerGetsMissingCurrency",
|
|
.testJson = R"JSON({
|
|
"taker_gets": {},
|
|
"taker_pays": {
|
|
"currency": "XRP"
|
|
}
|
|
})JSON",
|
|
.expectedError = "invalidParams",
|
|
.expectedErrorMessage = "Required field 'currency' missing"
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "TakerGetsWrongCurrency",
|
|
.testJson = R"JSON({
|
|
"taker_gets": {
|
|
"currency": "CNYY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_pays": {
|
|
"currency": "XRP"
|
|
}
|
|
})JSON",
|
|
.expectedError = "dstAmtMalformed",
|
|
.expectedErrorMessage = "Destination amount/currency/issuer is malformed."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "TakerPaysWrongCurrency",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "CNYY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "XRP"
|
|
}
|
|
})JSON",
|
|
.expectedError = "srcCurMalformed",
|
|
.expectedErrorMessage = "Source currency is malformed."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "TakerGetsCurrencyNotString",
|
|
.testJson = R"JSON({
|
|
"taker_gets": {
|
|
"currency": 123,
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_pays": {
|
|
"currency": "XRP"
|
|
}
|
|
})JSON",
|
|
.expectedError = "dstAmtMalformed",
|
|
.expectedErrorMessage = "Destination amount/currency/issuer is malformed."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "TakerPaysCurrencyNotString",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": 123,
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "XRP"
|
|
}
|
|
})JSON",
|
|
.expectedError = "srcCurMalformed",
|
|
.expectedErrorMessage = "Source currency is malformed."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "TakerGetsWrongIssuer",
|
|
.testJson = R"JSON({
|
|
"taker_gets": {
|
|
"currency": "CNY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs5"
|
|
},
|
|
"taker_pays": {
|
|
"currency": "XRP"
|
|
}
|
|
})JSON",
|
|
.expectedError = "dstIsrMalformed",
|
|
.expectedErrorMessage = "Destination issuer is malformed."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "TakerPaysWrongIssuer",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "CNY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs5"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "XRP"
|
|
}
|
|
})JSON",
|
|
.expectedError = "srcIsrMalformed",
|
|
.expectedErrorMessage = "Source issuer is malformed."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "InvalidTaker",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "CNY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "XRP"
|
|
},
|
|
"taker": "123"
|
|
})JSON",
|
|
.expectedError = "invalidParams",
|
|
.expectedErrorMessage = "Invalid field 'taker'."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "TakerNotString",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "CNY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "XRP"
|
|
},
|
|
"taker": 123
|
|
})JSON",
|
|
.expectedError = "invalidParams",
|
|
.expectedErrorMessage = "Invalid field 'taker'."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "Domain_InvalidType",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "CNY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "XRP"
|
|
},
|
|
"domain": 0
|
|
})JSON",
|
|
.expectedError = "domainMalformed",
|
|
.expectedErrorMessage = "Unable to parse domain."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "Domain_InvalidInt",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "CNY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "XRP"
|
|
},
|
|
"domain": "123"
|
|
})JSON",
|
|
.expectedError = "domainMalformed",
|
|
.expectedErrorMessage = "Unable to parse domain."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "Domain_InvalidObject",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "CNY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "XRP"
|
|
},
|
|
"domain": {}
|
|
})JSON",
|
|
.expectedError = "domainMalformed",
|
|
.expectedErrorMessage = "Unable to parse domain."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "LimitNotInt",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "CNY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "XRP"
|
|
},
|
|
"limit": "123"
|
|
})JSON",
|
|
.expectedError = "invalidParams",
|
|
.expectedErrorMessage = "Invalid parameters."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "LimitNegative",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "CNY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "XRP"
|
|
},
|
|
"limit": -1
|
|
})JSON",
|
|
.expectedError = "invalidParams",
|
|
.expectedErrorMessage = "Invalid parameters."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "LimitZero",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "CNY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "XRP"
|
|
},
|
|
"limit": 0
|
|
})JSON",
|
|
.expectedError = "invalidParams",
|
|
.expectedErrorMessage = "Invalid parameters."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "LedgerIndexInvalid",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "CNY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "XRP"
|
|
},
|
|
"ledger_index": "xxx"
|
|
})JSON",
|
|
.expectedError = "invalidParams",
|
|
.expectedErrorMessage = "ledgerIndexMalformed"
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "LedgerHashInvalid",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "CNY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "XRP"
|
|
},
|
|
"ledger_hash": "xxx"
|
|
})JSON",
|
|
.expectedError = "invalidParams",
|
|
.expectedErrorMessage = "ledger_hashMalformed"
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "LedgerHashNotString",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "CNY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "XRP"
|
|
},
|
|
"ledger_hash": 123
|
|
})JSON",
|
|
.expectedError = "invalidParams",
|
|
.expectedErrorMessage = "ledger_hashNotString"
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "GetsPaysXRPWithIssuer",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "XRP",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "CNY",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"
|
|
}
|
|
})JSON",
|
|
.expectedError = "srcIsrMalformed",
|
|
.expectedErrorMessage = "Unneeded field 'taker_pays.issuer' for XRP currency specification."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "PaysCurrencyWithXRPIssuer",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "JPY"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "CNY",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"
|
|
}
|
|
})JSON",
|
|
.expectedError = "srcIsrMalformed",
|
|
.expectedErrorMessage = "Invalid field 'taker_pays.issuer', expected non-XRP issuer."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "GetsCurrencyWithXRPIssuer",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "XRP"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "CNY"
|
|
}
|
|
})JSON",
|
|
.expectedError = "dstIsrMalformed",
|
|
.expectedErrorMessage = "Invalid field 'taker_gets.issuer', expected non-XRP issuer."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "GetsXRPWithIssuer",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "CNY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "XRP",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
}
|
|
})JSON",
|
|
.expectedError = "dstIsrMalformed",
|
|
.expectedErrorMessage = "Unneeded field 'taker_gets.issuer' for XRP currency specification."
|
|
},
|
|
ParameterTestBundle{
|
|
.testName = "BadMarket",
|
|
.testJson = R"JSON({
|
|
"taker_pays": {
|
|
"currency": "CNY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
},
|
|
"taker_gets": {
|
|
"currency": "CNY",
|
|
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B"
|
|
}
|
|
})JSON",
|
|
.expectedError = "badMarket",
|
|
.expectedErrorMessage = "badMarket"
|
|
}
|
|
};
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
RPCBookOffersHandler,
|
|
RPCBookOffersParameterTest,
|
|
testing::ValuesIn(generateParameterBookOffersTestBundles()),
|
|
tests::util::kNAME_GENERATOR
|
|
);
|
|
|
|
struct BookOffersNormalTestBundle {
|
|
std::string testName;
|
|
std::string inputJson;
|
|
std::map<ripple::uint256, std::optional<ripple::uint256>> mockedSuccessors;
|
|
std::map<ripple::uint256, Blob> mockedLedgerObjects;
|
|
uint32_t ledgerObjectCalls;
|
|
std::vector<ripple::STObject> mockedOffers;
|
|
std::string expectedJson;
|
|
uint32_t amendmentIsEnabledCalls = 0;
|
|
};
|
|
|
|
struct RPCBookOffersNormalPathTest : public RPCBookOffersHandlerTest,
|
|
public WithParamInterface<BookOffersNormalTestBundle> {};
|
|
|
|
TEST_P(RPCBookOffersNormalPathTest, CheckOutput)
|
|
{
|
|
auto const& bundle = GetParam();
|
|
auto const seq = 300;
|
|
|
|
EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1);
|
|
// return valid ledgerHeader
|
|
auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, seq);
|
|
ON_CALL(*backend_, fetchLedgerBySequence(seq, _)).WillByDefault(Return(ledgerHeader));
|
|
|
|
EXPECT_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::fixFrozenLPTokenTransfer, _))
|
|
.Times(bundle.amendmentIsEnabledCalls);
|
|
ON_CALL(*mockAmendmentCenterPtr_, isEnabled(_, Amendments::fixFrozenLPTokenTransfer, _))
|
|
.WillByDefault(Return(false));
|
|
|
|
// return valid book dir
|
|
EXPECT_CALL(*backend_, doFetchSuccessorKey).Times(bundle.mockedSuccessors.size());
|
|
for (auto const& [key, value] : bundle.mockedSuccessors) {
|
|
ON_CALL(*backend_, doFetchSuccessorKey(key, seq, _)).WillByDefault(Return(value));
|
|
}
|
|
|
|
EXPECT_CALL(*backend_, doFetchLedgerObject).Times(bundle.ledgerObjectCalls);
|
|
|
|
for (auto const& [key, value] : bundle.mockedLedgerObjects) {
|
|
ON_CALL(*backend_, doFetchLedgerObject(key, seq, _)).WillByDefault(Return(value));
|
|
}
|
|
|
|
std::vector<Blob> bbs;
|
|
std::ranges::transform(
|
|
bundle.mockedOffers,
|
|
|
|
std::back_inserter(bbs),
|
|
[](auto const& obj) { return obj.getSerializer().peekData(); }
|
|
);
|
|
ON_CALL(*backend_, doFetchLedgerObjects).WillByDefault(Return(bbs));
|
|
EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1);
|
|
|
|
auto const handler = AnyHandler{BookOffersHandler{backend_, mockAmendmentCenterPtr_}};
|
|
runSpawn([&](boost::asio::yield_context yield) {
|
|
auto const output = handler.process(json::parse(bundle.inputJson), Context{.yield = yield});
|
|
ASSERT_TRUE(output);
|
|
EXPECT_EQ(output.result.value(), json::parse(bundle.expectedJson));
|
|
});
|
|
}
|
|
|
|
static auto
|
|
generateNormalPathBookOffersTestBundles()
|
|
{
|
|
auto const account = getAccountIdWithString(kACCOUNT);
|
|
auto const account2 = getAccountIdWithString(kACCOUNT2);
|
|
|
|
auto const frozenTrustLine = createRippleStateLedgerObject(
|
|
"USD", kACCOUNT, -8, kACCOUNT2, 1000, kACCOUNT, 2000, kINDEX1, 2, ripple::lsfLowFreeze
|
|
);
|
|
|
|
auto const gets10USDPays20XRPOffer = createOfferLedgerObject(
|
|
kACCOUNT2,
|
|
10,
|
|
20,
|
|
ripple::to_string(ripple::to_currency("USD")),
|
|
ripple::to_string(ripple::xrpCurrency()),
|
|
kACCOUNT,
|
|
toBase58(ripple::xrpAccount()),
|
|
kPAYS20_XRP_GETS10_USD_BOOK_DIR
|
|
);
|
|
|
|
auto const gets10USDPays20XRPOwnerOffer = createOfferLedgerObject(
|
|
kACCOUNT,
|
|
10,
|
|
20,
|
|
ripple::to_string(ripple::to_currency("USD")),
|
|
ripple::to_string(ripple::xrpCurrency()),
|
|
kACCOUNT,
|
|
toBase58(ripple::xrpAccount()),
|
|
kPAYS20_XRP_GETS10_USD_BOOK_DIR
|
|
);
|
|
|
|
auto const gets10XRPPays20USDOffer = createOfferLedgerObject(
|
|
kACCOUNT2,
|
|
10,
|
|
20,
|
|
ripple::to_string(ripple::xrpCurrency()),
|
|
ripple::to_string(ripple::to_currency("USD")),
|
|
toBase58(ripple::xrpAccount()),
|
|
kACCOUNT,
|
|
kPAYS20_USD_GETS10_XRP_BOOK_DIR
|
|
);
|
|
|
|
auto const gets10XRPPays20USDOfferWithDomain = createOfferLedgerObject(
|
|
kACCOUNT2,
|
|
10,
|
|
20,
|
|
ripple::to_string(ripple::xrpCurrency()),
|
|
ripple::to_string(ripple::to_currency("USD")),
|
|
toBase58(ripple::xrpAccount()),
|
|
kACCOUNT,
|
|
kPAYS20_USD_GETS10_XRP_BOOK_DIR,
|
|
kDOMAIN
|
|
);
|
|
|
|
auto const getsXRPPaysUSDBook = getBookBase(
|
|
rpc::parseBook(ripple::to_currency("USD"), account, ripple::xrpCurrency(), ripple::xrpAccount(), std::nullopt)
|
|
.value()
|
|
);
|
|
auto const getsXRPPaysUSDBookWithDomain = getBookBase(
|
|
rpc::parseBook(ripple::to_currency("USD"), account, ripple::xrpCurrency(), ripple::xrpAccount(), kDOMAIN)
|
|
.value()
|
|
);
|
|
auto const getsUSDPaysXRPBook = getBookBase(
|
|
rpc::parseBook(ripple::xrpCurrency(), ripple::xrpAccount(), ripple::to_currency("USD"), account, std::nullopt)
|
|
.value()
|
|
);
|
|
|
|
auto const getsXRPPaysUSDInputJson = fmt::format(
|
|
R"JSON({{
|
|
"taker_gets": {{
|
|
"currency": "XRP"
|
|
}},
|
|
"taker_pays": {{
|
|
"currency": "USD",
|
|
"issuer": "{}"
|
|
}}
|
|
}})JSON",
|
|
kACCOUNT
|
|
);
|
|
|
|
auto const getsXRPPaysUSDInputJsonWithDomain = fmt::format(
|
|
R"JSON({{
|
|
"taker_gets": {{
|
|
"currency": "XRP"
|
|
}},
|
|
"taker_pays": {{
|
|
"currency": "USD",
|
|
"issuer": "{}"
|
|
}},
|
|
"domain": "{}"
|
|
}})JSON",
|
|
kACCOUNT,
|
|
kDOMAIN
|
|
);
|
|
|
|
auto const paysXRPGetsUSDInputJson = fmt::format(
|
|
R"JSON({{
|
|
"taker_pays": {{
|
|
"currency": "XRP"
|
|
}},
|
|
"taker_gets": {{
|
|
"currency": "USD",
|
|
"issuer": "{}"
|
|
}}
|
|
}})JSON",
|
|
kACCOUNT
|
|
);
|
|
|
|
auto const feeLedgerObject = createLegacyFeeSettingBlob(1, 2, 3, 4, 0);
|
|
|
|
auto const trustline30Balance =
|
|
createRippleStateLedgerObject("USD", kACCOUNT, -30, kACCOUNT2, 1000, kACCOUNT, 2000, kINDEX1, 2, 0);
|
|
|
|
auto const trustline8Balance =
|
|
createRippleStateLedgerObject("USD", kACCOUNT, -8, kACCOUNT2, 1000, kACCOUNT, 2000, kINDEX1, 2, 0);
|
|
|
|
return std::vector<BookOffersNormalTestBundle>{
|
|
BookOffersNormalTestBundle{
|
|
.testName = "PaysUSDGetsXRPNoFrozenOwnerFundEnough",
|
|
.inputJson = getsXRPPaysUSDInputJson,
|
|
// prepare offer dir index
|
|
.mockedSuccessors =
|
|
std::map<ripple::uint256, std::optional<ripple::uint256>>{
|
|
{getsXRPPaysUSDBook, ripple::uint256{kPAYS20_USD_GETS10_XRP_BOOK_DIR}},
|
|
{ripple::uint256{kPAYS20_USD_GETS10_XRP_BOOK_DIR}, std::optional<ripple::uint256>{}}
|
|
},
|
|
.mockedLedgerObjects =
|
|
std::map<ripple::uint256, ripple::Blob>{
|
|
// book dir object
|
|
{ripple::uint256{kPAYS20_USD_GETS10_XRP_BOOK_DIR},
|
|
createOwnerDirLedgerObject({ripple::uint256{kINDEX2}}, kINDEX1).getSerializer().peekData()},
|
|
// pays issuer account object
|
|
{ripple::keylet::account(account).key,
|
|
createAccountRootObject(kACCOUNT, 0, 2, 200, 2, kINDEX1, 2).getSerializer().peekData()},
|
|
// owner account object
|
|
{ripple::keylet::account(account2).key,
|
|
createAccountRootObject(kACCOUNT2, 0, 2, 200, 2, kINDEX1, 2).getSerializer().peekData()},
|
|
// fee settings: base ->3 inc->2, account2 has 2 objects ,total
|
|
// reserve ->7
|
|
// owner_funds should be 193
|
|
{ripple::keylet::fees().key, feeLedgerObject}
|
|
},
|
|
.ledgerObjectCalls = 5,
|
|
.mockedOffers = std::vector<ripple::STObject>{gets10XRPPays20USDOffer},
|
|
.expectedJson = fmt::format(
|
|
R"JSON({{
|
|
"ledger_hash": "{}",
|
|
"ledger_index": 300,
|
|
"offers": [
|
|
{{
|
|
"Account": "{}",
|
|
"BookDirectory": "43B83ADC452B85FCBADA6CAEAC5181C255A213630D58FFD455071AFD498D0000",
|
|
"BookNode": "0",
|
|
"Flags": 0,
|
|
"LedgerEntryType": "Offer",
|
|
"OwnerNode": "0",
|
|
"PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
"PreviousTxnLgrSeq": 0,
|
|
"Sequence": 0,
|
|
"TakerGets": "10",
|
|
"TakerPays": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "20"
|
|
}},
|
|
"index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321",
|
|
"owner_funds": "{}",
|
|
"quality": "{}"
|
|
}}
|
|
]
|
|
}})JSON",
|
|
kLEDGER_HASH,
|
|
kACCOUNT2,
|
|
193,
|
|
2
|
|
)
|
|
},
|
|
BookOffersNormalTestBundle{
|
|
.testName = "PaysUSDGetsXRPNoFrozenOwnerFundNotEnough",
|
|
.inputJson = getsXRPPaysUSDInputJson,
|
|
// prepare offer dir index
|
|
.mockedSuccessors =
|
|
std::map<ripple::uint256, std::optional<ripple::uint256>>{
|
|
{getsXRPPaysUSDBook, ripple::uint256{kPAYS20_USD_GETS10_XRP_BOOK_DIR}},
|
|
{ripple::uint256{kPAYS20_USD_GETS10_XRP_BOOK_DIR}, std::optional<ripple::uint256>{}}
|
|
},
|
|
.mockedLedgerObjects =
|
|
std::map<ripple::uint256, ripple::Blob>{
|
|
// book dir object
|
|
{ripple::uint256{kPAYS20_USD_GETS10_XRP_BOOK_DIR},
|
|
createOwnerDirLedgerObject({ripple::uint256{kINDEX2}}, kINDEX1).getSerializer().peekData()},
|
|
// pays issuer account object
|
|
{ripple::keylet::account(account).key,
|
|
createAccountRootObject(kACCOUNT, 0, 2, 200, 2, kINDEX1, 2).getSerializer().peekData()},
|
|
// owner account object, hold
|
|
{ripple::keylet::account(account2).key,
|
|
createAccountRootObject(kACCOUNT2, 0, 2, 5 + 7, 2, kINDEX1, 2).getSerializer().peekData()},
|
|
// fee settings: base ->3 inc->2, account2 has 2 objects
|
|
// ,total
|
|
// reserve ->7
|
|
{ripple::keylet::fees().key, feeLedgerObject}
|
|
},
|
|
.ledgerObjectCalls = 5,
|
|
.mockedOffers = std::vector<ripple::STObject>{gets10XRPPays20USDOffer},
|
|
.expectedJson = fmt::format(
|
|
R"JSON({{
|
|
"ledger_hash": "{}",
|
|
"ledger_index": 300,
|
|
"offers": [
|
|
{{
|
|
"Account": "{}",
|
|
"BookDirectory": "43B83ADC452B85FCBADA6CAEAC5181C255A213630D58FFD455071AFD498D0000",
|
|
"BookNode": "0",
|
|
"Flags": 0,
|
|
"LedgerEntryType": "Offer",
|
|
"OwnerNode": "0",
|
|
"PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
"PreviousTxnLgrSeq": 0,
|
|
"Sequence": 0,
|
|
"TakerGets": "10",
|
|
"TakerPays": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "20"
|
|
}},
|
|
"index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321",
|
|
"owner_funds": "{}",
|
|
"quality": "{}",
|
|
"taker_gets_funded": "5",
|
|
"taker_pays_funded": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "10"
|
|
}}
|
|
}}
|
|
]
|
|
}})JSON",
|
|
kLEDGER_HASH,
|
|
kACCOUNT2,
|
|
5,
|
|
2
|
|
)
|
|
},
|
|
BookOffersNormalTestBundle{
|
|
.testName = "PaysUSDGetsXRPFrozen",
|
|
.inputJson = getsXRPPaysUSDInputJson,
|
|
// prepare offer dir index
|
|
.mockedSuccessors =
|
|
std::map<ripple::uint256, std::optional<ripple::uint256>>{
|
|
{getsXRPPaysUSDBook, ripple::uint256{kPAYS20_USD_GETS10_XRP_BOOK_DIR}},
|
|
{ripple::uint256{kPAYS20_USD_GETS10_XRP_BOOK_DIR}, std::optional<ripple::uint256>{}}
|
|
},
|
|
.mockedLedgerObjects =
|
|
std::map<ripple::uint256, ripple::Blob>{
|
|
// book dir object
|
|
{ripple::uint256{kPAYS20_USD_GETS10_XRP_BOOK_DIR},
|
|
createOwnerDirLedgerObject({ripple::uint256{kINDEX2}}, kINDEX1).getSerializer().peekData()},
|
|
// pays issuer account object
|
|
{ripple::keylet::account(account).key,
|
|
createAccountRootObject(kACCOUNT, ripple::lsfGlobalFreeze, 2, 200, 2, kINDEX1, 2)
|
|
.getSerializer()
|
|
.peekData()}
|
|
},
|
|
.ledgerObjectCalls = 3,
|
|
.mockedOffers = std::vector<ripple::STObject>{gets10XRPPays20USDOffer},
|
|
.expectedJson = fmt::format(
|
|
R"JSON({{
|
|
"ledger_hash": "{}",
|
|
"ledger_index": 300,
|
|
"offers": [
|
|
{{
|
|
"Account": "{}",
|
|
"BookDirectory": "43B83ADC452B85FCBADA6CAEAC5181C255A213630D58FFD455071AFD498D0000",
|
|
"BookNode": "0",
|
|
"Flags": 0,
|
|
"LedgerEntryType": "Offer",
|
|
"OwnerNode": "0",
|
|
"PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
"PreviousTxnLgrSeq": 0,
|
|
"Sequence": 0,
|
|
"TakerGets": "10",
|
|
"TakerPays": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "20"
|
|
}},
|
|
"index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321",
|
|
"owner_funds": "{}",
|
|
"quality": "{}",
|
|
"taker_gets_funded": "0",
|
|
"taker_pays_funded": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "0"
|
|
}}
|
|
}}
|
|
]
|
|
}})JSON",
|
|
kLEDGER_HASH,
|
|
kACCOUNT2,
|
|
0,
|
|
2
|
|
)
|
|
},
|
|
BookOffersNormalTestBundle{
|
|
.testName = "PaysUSDGetsXRPFrozenWithDomain",
|
|
.inputJson = getsXRPPaysUSDInputJsonWithDomain,
|
|
// prepare offer dir index
|
|
.mockedSuccessors =
|
|
std::map<ripple::uint256, std::optional<ripple::uint256>>{
|
|
{getsXRPPaysUSDBookWithDomain, ripple::uint256{kPAYS20_USD_GETS10_XRP_BOOK_DIR}},
|
|
{ripple::uint256{kPAYS20_USD_GETS10_XRP_BOOK_DIR}, std::optional<ripple::uint256>{}}
|
|
},
|
|
.mockedLedgerObjects =
|
|
std::map<ripple::uint256, ripple::Blob>{
|
|
// book dir object
|
|
{ripple::uint256{kPAYS20_USD_GETS10_XRP_BOOK_DIR},
|
|
createOwnerDirLedgerObject({ripple::uint256{kINDEX2}}, kINDEX1).getSerializer().peekData()},
|
|
// pays issuer account object
|
|
{ripple::keylet::account(account).key,
|
|
createAccountRootObject(kACCOUNT, ripple::lsfGlobalFreeze, 2, 200, 2, kINDEX1, 2)
|
|
.getSerializer()
|
|
.peekData()}
|
|
},
|
|
.ledgerObjectCalls = 3,
|
|
.mockedOffers = std::vector<ripple::STObject>{gets10XRPPays20USDOfferWithDomain},
|
|
.expectedJson = fmt::format(
|
|
R"JSON({{
|
|
"ledger_hash": "{}",
|
|
"ledger_index": 300,
|
|
"offers": [
|
|
{{
|
|
"Account": "{}",
|
|
"BookDirectory": "43B83ADC452B85FCBADA6CAEAC5181C255A213630D58FFD455071AFD498D0000",
|
|
"BookNode": "0",
|
|
"DomainID": "F10D0CC9A0F9A3CBF585B80BE09A186483668FDBDD39AA7E3370F3649CE134E5",
|
|
"Flags": 0,
|
|
"LedgerEntryType": "Offer",
|
|
"OwnerNode": "0",
|
|
"PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
"PreviousTxnLgrSeq": 0,
|
|
"Sequence": 0,
|
|
"TakerGets": "10",
|
|
"TakerPays": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "20"
|
|
}},
|
|
"index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321",
|
|
"owner_funds": "{}",
|
|
"quality": "{}",
|
|
"taker_gets_funded": "0",
|
|
"taker_pays_funded": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "0"
|
|
}}
|
|
}}
|
|
]
|
|
}})JSON",
|
|
kLEDGER_HASH,
|
|
kACCOUNT2,
|
|
0,
|
|
2
|
|
)
|
|
},
|
|
BookOffersNormalTestBundle{
|
|
.testName = "GetsUSDPaysXRPFrozen",
|
|
.inputJson = paysXRPGetsUSDInputJson,
|
|
// prepare offer dir index
|
|
.mockedSuccessors =
|
|
std::map<ripple::uint256, std::optional<ripple::uint256>>{
|
|
{getsUSDPaysXRPBook, ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR}},
|
|
{ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR}, std::optional<ripple::uint256>{}}
|
|
},
|
|
.mockedLedgerObjects =
|
|
std::map<ripple::uint256, ripple::Blob>{
|
|
// book dir object
|
|
{ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR},
|
|
createOwnerDirLedgerObject({ripple::uint256{kINDEX2}}, kINDEX1).getSerializer().peekData()},
|
|
// gets issuer account object
|
|
{ripple::keylet::account(account).key,
|
|
createAccountRootObject(
|
|
kACCOUNT, ripple::lsfGlobalFreeze, 2, 200, 2, kINDEX1, 2, kTRANSFER_RATE_X2
|
|
)
|
|
.getSerializer()
|
|
.peekData()}
|
|
},
|
|
.ledgerObjectCalls = 3,
|
|
.mockedOffers = std::vector<ripple::STObject>{gets10USDPays20XRPOffer},
|
|
.expectedJson = fmt::format(
|
|
R"JSON({{
|
|
"ledger_hash": "{}",
|
|
"ledger_index": 300,
|
|
"offers": [
|
|
{{
|
|
"Account": "{}",
|
|
"BookDirectory": "{}",
|
|
"BookNode": "0",
|
|
"Flags": 0,
|
|
"LedgerEntryType": "Offer",
|
|
"OwnerNode": "0",
|
|
"PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
"PreviousTxnLgrSeq": 0,
|
|
"Sequence": 0,
|
|
"TakerPays": "20",
|
|
"TakerGets": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "10"
|
|
}},
|
|
"index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321",
|
|
"owner_funds": "{}",
|
|
"quality": "{}",
|
|
"taker_pays_funded": "0",
|
|
"taker_gets_funded": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "0"
|
|
}}
|
|
}}
|
|
]
|
|
}})JSON",
|
|
kLEDGER_HASH,
|
|
kACCOUNT2,
|
|
kPAYS20_XRP_GETS10_USD_BOOK_DIR,
|
|
0,
|
|
2
|
|
)
|
|
},
|
|
BookOffersNormalTestBundle{
|
|
.testName = "PaysXRPGetsUSDWithTransferFee",
|
|
.inputJson = paysXRPGetsUSDInputJson,
|
|
// prepare offer dir index
|
|
.mockedSuccessors =
|
|
std::map<ripple::uint256, std::optional<ripple::uint256>>{
|
|
{getsUSDPaysXRPBook, ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR}},
|
|
{ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR}, std::optional<ripple::uint256>{}}
|
|
},
|
|
.mockedLedgerObjects =
|
|
std::map<ripple::uint256, ripple::Blob>{
|
|
// book dir object
|
|
{ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR},
|
|
createOwnerDirLedgerObject({ripple::uint256{kINDEX2}}, kINDEX1).getSerializer().peekData()},
|
|
// gets issuer account object, rate is 1/2
|
|
{ripple::keylet::account(account).key,
|
|
createAccountRootObject(kACCOUNT, 0, 2, 200, 2, kINDEX1, 2, kTRANSFER_RATE_X2)
|
|
.getSerializer()
|
|
.peekData()},
|
|
// trust line between gets issuer and owner,owner has 8 USD
|
|
{ripple::keylet::line(account2, account, ripple::to_currency("USD")).key,
|
|
trustline8Balance.getSerializer().peekData()},
|
|
},
|
|
.ledgerObjectCalls = 6,
|
|
.mockedOffers = std::vector<ripple::STObject>{gets10USDPays20XRPOffer},
|
|
.expectedJson = fmt::format(
|
|
R"JSON({{
|
|
"ledger_hash": "{}",
|
|
"ledger_index": 300,
|
|
"offers": [
|
|
{{
|
|
"Account": "{}",
|
|
"BookDirectory": "{}",
|
|
"BookNode": "0",
|
|
"Flags": 0,
|
|
"LedgerEntryType": "Offer",
|
|
"OwnerNode": "0",
|
|
"PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
"PreviousTxnLgrSeq": 0,
|
|
"Sequence": 0,
|
|
"TakerPays": "20",
|
|
"TakerGets": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "10"
|
|
}},
|
|
"index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321",
|
|
"owner_funds": "{}",
|
|
"quality": "{}",
|
|
"taker_gets_funded": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "4"
|
|
}},
|
|
"taker_pays_funded": "8"
|
|
}}
|
|
]
|
|
}})JSON",
|
|
kLEDGER_HASH,
|
|
kACCOUNT2,
|
|
kPAYS20_XRP_GETS10_USD_BOOK_DIR,
|
|
8,
|
|
2
|
|
),
|
|
.amendmentIsEnabledCalls = 1,
|
|
},
|
|
BookOffersNormalTestBundle{
|
|
.testName = "PaysXRPGetsUSDWithMultipleOffers",
|
|
.inputJson = paysXRPGetsUSDInputJson,
|
|
// prepare offer dir index
|
|
.mockedSuccessors =
|
|
std::map<ripple::uint256, std::optional<ripple::uint256>>{
|
|
{getsUSDPaysXRPBook, ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR}},
|
|
{ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR}, std::optional<ripple::uint256>{}}
|
|
},
|
|
.mockedLedgerObjects =
|
|
std::map<ripple::uint256, ripple::Blob>{
|
|
// book dir object
|
|
{ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR},
|
|
createOwnerDirLedgerObject({ripple::uint256{kINDEX2}, ripple::uint256{kINDEX2}}, kINDEX1)
|
|
.getSerializer()
|
|
.peekData()},
|
|
// gets issuer account object
|
|
{ripple::keylet::account(account).key,
|
|
createAccountRootObject(kACCOUNT, 0, 2, 200, 2, kINDEX1, 2, kTRANSFER_RATE_X2)
|
|
.getSerializer()
|
|
.peekData()},
|
|
// trust line between gets issuer and owner,owner has 30 USD
|
|
{ripple::keylet::line(account2, account, ripple::to_currency("USD")).key,
|
|
trustline30Balance.getSerializer().peekData()},
|
|
},
|
|
.ledgerObjectCalls = 6,
|
|
.mockedOffers =
|
|
std::vector<ripple::STObject>{
|
|
// After offer1, balance is 30 - 2*10 = 10
|
|
gets10USDPays20XRPOffer,
|
|
// offer2 not fully funded, balance is 10, rate is 2, so only
|
|
// gets 5
|
|
gets10USDPays20XRPOffer
|
|
},
|
|
.expectedJson = fmt::format(
|
|
R"JSON({{
|
|
"ledger_hash": "{}",
|
|
"ledger_index": 300,
|
|
"offers": [
|
|
{{
|
|
"Account": "{}",
|
|
"BookDirectory": "{}",
|
|
"BookNode": "0",
|
|
"Flags": 0,
|
|
"LedgerEntryType": "Offer",
|
|
"OwnerNode": "0",
|
|
"PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
"PreviousTxnLgrSeq": 0,
|
|
"Sequence": 0,
|
|
"TakerPays": "20",
|
|
"TakerGets": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "10"
|
|
}},
|
|
"index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321",
|
|
"owner_funds": "{}",
|
|
"quality": "{}"
|
|
}},
|
|
{{
|
|
"Account": "{}",
|
|
"BookDirectory": "{}",
|
|
"BookNode": "0",
|
|
"Flags": 0,
|
|
"LedgerEntryType": "Offer",
|
|
"OwnerNode": "0",
|
|
"PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
"PreviousTxnLgrSeq": 0,
|
|
"Sequence": 0,
|
|
"TakerPays": "20",
|
|
"TakerGets": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "10"
|
|
}},
|
|
"index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321",
|
|
"taker_gets_funded": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "5"
|
|
}},
|
|
"taker_pays_funded": "10",
|
|
"quality": "{}"
|
|
}}
|
|
]
|
|
}})JSON",
|
|
kLEDGER_HASH,
|
|
kACCOUNT2,
|
|
kPAYS20_XRP_GETS10_USD_BOOK_DIR,
|
|
30,
|
|
2,
|
|
kACCOUNT2,
|
|
kPAYS20_XRP_GETS10_USD_BOOK_DIR,
|
|
2
|
|
),
|
|
.amendmentIsEnabledCalls = 1,
|
|
},
|
|
BookOffersNormalTestBundle{
|
|
.testName = "PaysXRPGetsUSDSellingOwnCurrency",
|
|
.inputJson = paysXRPGetsUSDInputJson,
|
|
// prepare offer dir index
|
|
.mockedSuccessors =
|
|
std::map<ripple::uint256, std::optional<ripple::uint256>>{
|
|
{getsUSDPaysXRPBook, ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR}},
|
|
{ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR}, std::optional<ripple::uint256>{}}
|
|
},
|
|
.mockedLedgerObjects =
|
|
std::map<ripple::uint256, ripple::Blob>{
|
|
// book dir object
|
|
{ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR},
|
|
createOwnerDirLedgerObject({ripple::uint256{kINDEX2}}, kINDEX1).getSerializer().peekData()},
|
|
// gets issuer account object, rate is 1/2
|
|
{ripple::keylet::account(account).key,
|
|
createAccountRootObject(kACCOUNT, 0, 2, 200, 2, kINDEX1, 2, kTRANSFER_RATE_X2)
|
|
.getSerializer()
|
|
.peekData()},
|
|
},
|
|
.ledgerObjectCalls = 3,
|
|
.mockedOffers = std::vector<ripple::STObject>{gets10USDPays20XRPOwnerOffer},
|
|
.expectedJson = fmt::format(
|
|
R"JSON({{
|
|
"ledger_hash": "{}",
|
|
"ledger_index": 300,
|
|
"offers": [
|
|
{{
|
|
"Account": "{}",
|
|
"BookDirectory": "{}",
|
|
"BookNode": "0",
|
|
"Flags": 0,
|
|
"LedgerEntryType": "Offer",
|
|
"OwnerNode": "0",
|
|
"PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
"PreviousTxnLgrSeq": 0,
|
|
"Sequence": 0,
|
|
"TakerPays": "20",
|
|
"TakerGets": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "10"
|
|
}},
|
|
"index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321",
|
|
"owner_funds": "{}",
|
|
"quality": "{}"
|
|
}}
|
|
]
|
|
}})JSON",
|
|
kLEDGER_HASH,
|
|
kACCOUNT,
|
|
kPAYS20_XRP_GETS10_USD_BOOK_DIR,
|
|
10,
|
|
2
|
|
)
|
|
},
|
|
BookOffersNormalTestBundle{
|
|
.testName = "PaysXRPGetsUSDTrustLineFrozen",
|
|
.inputJson = paysXRPGetsUSDInputJson,
|
|
// prepare offer dir index
|
|
.mockedSuccessors =
|
|
std::map<ripple::uint256, std::optional<ripple::uint256>>{
|
|
{getsUSDPaysXRPBook, ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR}},
|
|
{ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR}, std::optional<ripple::uint256>{}}
|
|
},
|
|
.mockedLedgerObjects =
|
|
std::map<ripple::uint256, ripple::Blob>{
|
|
// book dir object
|
|
{ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR},
|
|
createOwnerDirLedgerObject({ripple::uint256{kINDEX2}}, kINDEX1).getSerializer().peekData()},
|
|
// gets issuer account object, rate is 1/2
|
|
{ripple::keylet::account(account).key,
|
|
createAccountRootObject(kACCOUNT, 0, 2, 200, 2, kINDEX1, 2, kTRANSFER_RATE_X2)
|
|
.getSerializer()
|
|
.peekData()},
|
|
// trust line between gets issuer and owner,owner has 8 USD
|
|
{ripple::keylet::line(account2, account, ripple::to_currency("USD")).key,
|
|
frozenTrustLine.getSerializer().peekData()},
|
|
},
|
|
.ledgerObjectCalls = 6,
|
|
.mockedOffers = std::vector<ripple::STObject>{gets10USDPays20XRPOffer},
|
|
.expectedJson = fmt::format(
|
|
R"JSON({{
|
|
"ledger_hash": "{}",
|
|
"ledger_index": 300,
|
|
"offers": [
|
|
{{
|
|
"Account": "{}",
|
|
"BookDirectory": "{}",
|
|
"BookNode": "0",
|
|
"Flags": 0,
|
|
"LedgerEntryType": "Offer",
|
|
"OwnerNode": "0",
|
|
"PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
"PreviousTxnLgrSeq": 0,
|
|
"Sequence": 0,
|
|
"TakerPays": "20",
|
|
"TakerGets": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "10"
|
|
}},
|
|
"index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321",
|
|
"owner_funds": "{}",
|
|
"quality": "{}",
|
|
"taker_gets_funded": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "0"
|
|
}},
|
|
"taker_pays_funded": "0"
|
|
}}
|
|
]
|
|
}})JSON",
|
|
kLEDGER_HASH,
|
|
kACCOUNT2,
|
|
kPAYS20_XRP_GETS10_USD_BOOK_DIR,
|
|
0,
|
|
2
|
|
),
|
|
},
|
|
BookOffersNormalTestBundle{
|
|
.testName = "PaysXRPGetsUSDIsDeepFrozen",
|
|
.inputJson = paysXRPGetsUSDInputJson,
|
|
// prepare offer dir index
|
|
.mockedSuccessors =
|
|
std::map<ripple::uint256, std::optional<ripple::uint256>>{
|
|
{getsUSDPaysXRPBook, ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR}},
|
|
{ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR}, std::optional<ripple::uint256>{}}
|
|
},
|
|
.mockedLedgerObjects =
|
|
std::map<ripple::uint256, ripple::Blob>{
|
|
// book dir object
|
|
{ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR},
|
|
createOwnerDirLedgerObject({ripple::uint256{kINDEX2}}, kINDEX1).getSerializer().peekData()},
|
|
// gets issuer account object, is deep frozen so unfunded
|
|
{ripple::keylet::account(account).key,
|
|
createAccountRootObject(
|
|
kACCOUNT, ripple::lsfLowDeepFreeze, 2, 200, 2, kINDEX1, 2, kTRANSFER_RATE_X2
|
|
)
|
|
.getSerializer()
|
|
.peekData()},
|
|
},
|
|
.ledgerObjectCalls = 4,
|
|
.mockedOffers = std::vector<ripple::STObject>{gets10USDPays20XRPOffer},
|
|
.expectedJson = fmt::format(
|
|
R"JSON({{
|
|
"ledger_hash": "{}",
|
|
"ledger_index": 300,
|
|
"offers": [
|
|
{{
|
|
"Account": "{}",
|
|
"BookDirectory": "{}",
|
|
"BookNode": "0",
|
|
"Flags": 0,
|
|
"LedgerEntryType": "Offer",
|
|
"OwnerNode": "0",
|
|
"PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
"PreviousTxnLgrSeq": 0,
|
|
"Sequence": 0,
|
|
"TakerPays": "20",
|
|
"TakerGets": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "10"
|
|
}},
|
|
"index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321",
|
|
"owner_funds": "{}",
|
|
"quality": "{}",
|
|
"taker_gets_funded": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "0"
|
|
}},
|
|
"taker_pays_funded": "0"
|
|
}}
|
|
]
|
|
}})JSON",
|
|
kLEDGER_HASH,
|
|
kACCOUNT2,
|
|
kPAYS20_XRP_GETS10_USD_BOOK_DIR,
|
|
0,
|
|
2
|
|
)
|
|
},
|
|
BookOffersNormalTestBundle{
|
|
.testName = "PaysXRPGetsUSDTrustLineFrozenAndIsDeepFrozen",
|
|
.inputJson = paysXRPGetsUSDInputJson,
|
|
// prepare offer dir index
|
|
.mockedSuccessors =
|
|
std::map<ripple::uint256, std::optional<ripple::uint256>>{
|
|
{getsUSDPaysXRPBook, ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR}},
|
|
{ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR}, std::optional<ripple::uint256>{}}
|
|
},
|
|
.mockedLedgerObjects =
|
|
std::map<ripple::uint256, ripple::Blob>{
|
|
// book dir object
|
|
{ripple::uint256{kPAYS20_XRP_GETS10_USD_BOOK_DIR},
|
|
createOwnerDirLedgerObject({ripple::uint256{kINDEX2}}, kINDEX1).getSerializer().peekData()},
|
|
// gets issuer account object, is deep frozen so unfunded
|
|
{ripple::keylet::account(account).key,
|
|
createAccountRootObject(
|
|
kACCOUNT, ripple::lsfLowDeepFreeze, 2, 200, 2, kINDEX1, 2, kTRANSFER_RATE_X2
|
|
)
|
|
.getSerializer()
|
|
.peekData()},
|
|
{ripple::keylet::line(account2, account, ripple::to_currency("USD")).key,
|
|
frozenTrustLine.getSerializer().peekData()},
|
|
|
|
},
|
|
.ledgerObjectCalls = 6,
|
|
.mockedOffers = std::vector<ripple::STObject>{gets10USDPays20XRPOffer},
|
|
.expectedJson = fmt::format(
|
|
R"JSON({{
|
|
"ledger_hash": "{}",
|
|
"ledger_index": 300,
|
|
"offers": [
|
|
{{
|
|
"Account": "{}",
|
|
"BookDirectory": "{}",
|
|
"BookNode": "0",
|
|
"Flags": 0,
|
|
"LedgerEntryType": "Offer",
|
|
"OwnerNode": "0",
|
|
"PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000",
|
|
"PreviousTxnLgrSeq": 0,
|
|
"Sequence": 0,
|
|
"TakerPays": "20",
|
|
"TakerGets": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "10"
|
|
}},
|
|
"index": "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321",
|
|
"owner_funds": "{}",
|
|
"quality": "{}",
|
|
"taker_gets_funded": {{
|
|
"currency": "USD",
|
|
"issuer": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
|
"value": "0"
|
|
}},
|
|
"taker_pays_funded": "0"
|
|
}}
|
|
]
|
|
}})JSON",
|
|
kLEDGER_HASH,
|
|
kACCOUNT2,
|
|
kPAYS20_XRP_GETS10_USD_BOOK_DIR,
|
|
0,
|
|
2
|
|
)
|
|
}
|
|
};
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
RPCBookOffersHandler,
|
|
RPCBookOffersNormalPathTest,
|
|
testing::ValuesIn(generateNormalPathBookOffersTestBundles()),
|
|
tests::util::kNAME_GENERATOR
|
|
);
|
|
|
|
// ledger not exist
|
|
TEST_F(RPCBookOffersHandlerTest, LedgerNonExistViaIntSequence)
|
|
{
|
|
EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1);
|
|
// return empty ledgerHeader
|
|
ON_CALL(*backend_, fetchLedgerBySequence(30, _)).WillByDefault(Return(std::optional<ripple::LedgerHeader>{}));
|
|
|
|
static auto const kINPUT = json::parse(
|
|
fmt::format(
|
|
R"JSON({{
|
|
"ledger_index": 30,
|
|
"taker_gets": {{
|
|
"currency": "XRP"
|
|
}},
|
|
"taker_pays": {{
|
|
"currency": "USD",
|
|
"issuer": "{}"
|
|
}}
|
|
}})JSON",
|
|
kACCOUNT
|
|
)
|
|
);
|
|
auto const handler = AnyHandler{BookOffersHandler{backend_, mockAmendmentCenterPtr_}};
|
|
runSpawn([&](boost::asio::yield_context yield) {
|
|
auto const output = handler.process(kINPUT, Context{.yield = yield});
|
|
ASSERT_FALSE(output);
|
|
auto const err = rpc::makeError(output.result.error());
|
|
EXPECT_EQ(err.at("error").as_string(), "lgrNotFound");
|
|
EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound");
|
|
});
|
|
}
|
|
|
|
TEST_F(RPCBookOffersHandlerTest, LedgerNonExistViaSequence)
|
|
{
|
|
EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1);
|
|
// return empty ledgerHeader
|
|
ON_CALL(*backend_, fetchLedgerBySequence(30, _)).WillByDefault(Return(std::optional<ripple::LedgerHeader>{}));
|
|
|
|
static auto const kINPUT = json::parse(
|
|
fmt::format(
|
|
R"JSON({{
|
|
"ledger_index": "30",
|
|
"taker_gets": {{
|
|
"currency": "XRP"
|
|
}},
|
|
"taker_pays": {{
|
|
"currency": "USD",
|
|
"issuer": "{}"
|
|
}}
|
|
}})JSON",
|
|
kACCOUNT
|
|
)
|
|
);
|
|
auto const handler = AnyHandler{BookOffersHandler{backend_, mockAmendmentCenterPtr_}};
|
|
runSpawn([&](boost::asio::yield_context yield) {
|
|
auto const output = handler.process(kINPUT, Context{.yield = yield});
|
|
ASSERT_FALSE(output);
|
|
auto const err = rpc::makeError(output.result.error());
|
|
EXPECT_EQ(err.at("error").as_string(), "lgrNotFound");
|
|
EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound");
|
|
});
|
|
}
|
|
|
|
TEST_F(RPCBookOffersHandlerTest, LedgerNonExistViaHash)
|
|
{
|
|
EXPECT_CALL(*backend_, fetchLedgerByHash).Times(1);
|
|
// return empty ledgerHeader
|
|
ON_CALL(*backend_, fetchLedgerByHash(ripple::uint256{kLEDGER_HASH}, _))
|
|
.WillByDefault(Return(std::optional<ripple::LedgerHeader>{}));
|
|
|
|
static auto const kINPUT = json::parse(
|
|
fmt::format(
|
|
R"JSON({{
|
|
"ledger_hash": "{}",
|
|
"taker_gets": {{
|
|
"currency": "XRP"
|
|
}},
|
|
"taker_pays": {{
|
|
"currency": "USD",
|
|
"issuer": "{}"
|
|
}}
|
|
}})JSON",
|
|
kLEDGER_HASH,
|
|
kACCOUNT
|
|
)
|
|
);
|
|
auto const handler = AnyHandler{BookOffersHandler{backend_, mockAmendmentCenterPtr_}};
|
|
runSpawn([&](boost::asio::yield_context yield) {
|
|
auto const output = handler.process(kINPUT, Context{.yield = yield});
|
|
ASSERT_FALSE(output);
|
|
auto const err = rpc::makeError(output.result.error());
|
|
EXPECT_EQ(err.at("error").as_string(), "lgrNotFound");
|
|
EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound");
|
|
});
|
|
}
|
|
|
|
TEST_F(RPCBookOffersHandlerTest, Limit)
|
|
{
|
|
auto const seq = 300;
|
|
|
|
EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1);
|
|
// return valid ledgerHeader
|
|
auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, seq);
|
|
ON_CALL(*backend_, fetchLedgerBySequence(seq, _)).WillByDefault(Return(ledgerHeader));
|
|
|
|
auto const issuer = getAccountIdWithString(kACCOUNT);
|
|
// return valid book dir
|
|
EXPECT_CALL(*backend_, doFetchSuccessorKey).Times(1);
|
|
|
|
auto const getsXRPPaysUSDBook = getBookBase(
|
|
rpc::parseBook(ripple::to_currency("USD"), issuer, ripple::xrpCurrency(), ripple::xrpAccount(), std::nullopt)
|
|
.value()
|
|
);
|
|
ON_CALL(*backend_, doFetchSuccessorKey(getsXRPPaysUSDBook, seq, _))
|
|
.WillByDefault(Return(ripple::uint256{kPAYS20_USD_GETS10_XRP_BOOK_DIR}));
|
|
|
|
EXPECT_CALL(*backend_, doFetchLedgerObject).Times(5);
|
|
auto const indexes = std::vector<ripple::uint256>(10, ripple::uint256{kINDEX2});
|
|
|
|
ON_CALL(*backend_, doFetchLedgerObject(ripple::uint256{kPAYS20_USD_GETS10_XRP_BOOK_DIR}, seq, _))
|
|
.WillByDefault(Return(createOwnerDirLedgerObject(indexes, kINDEX1).getSerializer().peekData()));
|
|
ON_CALL(*backend_, doFetchLedgerObject(ripple::keylet::account(getAccountIdWithString(kACCOUNT2)).key, seq, _))
|
|
.WillByDefault(Return(createAccountRootObject(kACCOUNT2, 0, 2, 200, 2, kINDEX1, 2).getSerializer().peekData()));
|
|
|
|
ON_CALL(*backend_, doFetchLedgerObject(ripple::keylet::fees().key, seq, _))
|
|
.WillByDefault(Return(createLegacyFeeSettingBlob(1, 2, 3, 4, 0)));
|
|
|
|
ON_CALL(*backend_, doFetchLedgerObject(ripple::keylet::account(issuer).key, seq, _))
|
|
.WillByDefault(Return(
|
|
createAccountRootObject(kACCOUNT, 0, 2, 200, 2, kINDEX1, 2, kTRANSFER_RATE_X2).getSerializer().peekData()
|
|
));
|
|
|
|
auto const gets10XRPPays20USDOffer = createOfferLedgerObject(
|
|
kACCOUNT2,
|
|
10,
|
|
20,
|
|
ripple::to_string(ripple::xrpCurrency()),
|
|
ripple::to_string(ripple::to_currency("USD")),
|
|
toBase58(ripple::xrpAccount()),
|
|
kACCOUNT,
|
|
kPAYS20_USD_GETS10_XRP_BOOK_DIR
|
|
);
|
|
|
|
std::vector<Blob> const bbs(10, gets10XRPPays20USDOffer.getSerializer().peekData());
|
|
ON_CALL(*backend_, doFetchLedgerObjects).WillByDefault(Return(bbs));
|
|
EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1);
|
|
|
|
static auto const kINPUT = json::parse(
|
|
fmt::format(
|
|
R"JSON({{
|
|
"taker_gets": {{
|
|
"currency": "XRP"
|
|
}},
|
|
"taker_pays": {{
|
|
"currency": "USD",
|
|
"issuer": "{}"
|
|
}},
|
|
"limit": 5
|
|
}})JSON",
|
|
kACCOUNT
|
|
)
|
|
);
|
|
auto const handler = AnyHandler{BookOffersHandler{backend_, mockAmendmentCenterPtr_}};
|
|
runSpawn([&](boost::asio::yield_context yield) {
|
|
auto const output = handler.process(kINPUT, Context{.yield = yield});
|
|
ASSERT_TRUE(output);
|
|
EXPECT_EQ(output.result.value().as_object().at("offers").as_array().size(), 5);
|
|
});
|
|
}
|
|
|
|
TEST_F(RPCBookOffersHandlerTest, LimitMoreThanMax)
|
|
{
|
|
auto const seq = 300;
|
|
|
|
EXPECT_CALL(*backend_, fetchLedgerBySequence).Times(1);
|
|
// return valid ledgerHeader
|
|
auto const ledgerHeader = createLedgerHeader(kLEDGER_HASH, seq);
|
|
ON_CALL(*backend_, fetchLedgerBySequence(seq, _)).WillByDefault(Return(ledgerHeader));
|
|
|
|
auto const issuer = getAccountIdWithString(kACCOUNT);
|
|
// return valid book dir
|
|
EXPECT_CALL(*backend_, doFetchSuccessorKey).Times(1);
|
|
|
|
auto const getsXRPPaysUSDBook = getBookBase(
|
|
rpc::parseBook(ripple::to_currency("USD"), issuer, ripple::xrpCurrency(), ripple::xrpAccount(), std::nullopt)
|
|
.value()
|
|
);
|
|
ON_CALL(*backend_, doFetchSuccessorKey(getsXRPPaysUSDBook, seq, _))
|
|
.WillByDefault(Return(ripple::uint256{kPAYS20_USD_GETS10_XRP_BOOK_DIR}));
|
|
|
|
EXPECT_CALL(*backend_, doFetchLedgerObject).Times(5);
|
|
auto const indexes = std::vector<ripple::uint256>(BookOffersHandler::kLIMIT_MAX + 1, ripple::uint256{kINDEX2});
|
|
|
|
ON_CALL(*backend_, doFetchLedgerObject(ripple::uint256{kPAYS20_USD_GETS10_XRP_BOOK_DIR}, seq, _))
|
|
.WillByDefault(Return(createOwnerDirLedgerObject(indexes, kINDEX1).getSerializer().peekData()));
|
|
ON_CALL(*backend_, doFetchLedgerObject(ripple::keylet::account(getAccountIdWithString(kACCOUNT2)).key, seq, _))
|
|
.WillByDefault(Return(createAccountRootObject(kACCOUNT2, 0, 2, 200, 2, kINDEX1, 2).getSerializer().peekData()));
|
|
|
|
ON_CALL(*backend_, doFetchLedgerObject(ripple::keylet::fees().key, seq, _))
|
|
.WillByDefault(Return(createLegacyFeeSettingBlob(1, 2, 3, 4, 0)));
|
|
|
|
ON_CALL(*backend_, doFetchLedgerObject(ripple::keylet::account(issuer).key, seq, _))
|
|
.WillByDefault(Return(
|
|
createAccountRootObject(kACCOUNT, 0, 2, 200, 2, kINDEX1, 2, kTRANSFER_RATE_X2).getSerializer().peekData()
|
|
));
|
|
|
|
auto const gets10XRPPays20USDOffer = createOfferLedgerObject(
|
|
kACCOUNT2,
|
|
10,
|
|
20,
|
|
ripple::to_string(ripple::xrpCurrency()),
|
|
ripple::to_string(ripple::to_currency("USD")),
|
|
toBase58(ripple::xrpAccount()),
|
|
kACCOUNT,
|
|
kPAYS20_USD_GETS10_XRP_BOOK_DIR
|
|
);
|
|
|
|
std::vector<Blob> const bbs(BookOffersHandler::kLIMIT_MAX + 1, gets10XRPPays20USDOffer.getSerializer().peekData());
|
|
ON_CALL(*backend_, doFetchLedgerObjects).WillByDefault(Return(bbs));
|
|
EXPECT_CALL(*backend_, doFetchLedgerObjects).Times(1);
|
|
|
|
static auto const kINPUT = json::parse(
|
|
fmt::format(
|
|
R"JSON({{
|
|
"taker_gets": {{
|
|
"currency": "XRP"
|
|
}},
|
|
"taker_pays": {{
|
|
"currency": "USD",
|
|
"issuer": "{}"
|
|
}},
|
|
"limit": {}
|
|
}})JSON",
|
|
kACCOUNT,
|
|
BookOffersHandler::kLIMIT_MAX + 1
|
|
)
|
|
);
|
|
auto const handler = AnyHandler{BookOffersHandler{backend_, mockAmendmentCenterPtr_}};
|
|
runSpawn([&](boost::asio::yield_context yield) {
|
|
auto const output = handler.process(kINPUT, Context{.yield = yield});
|
|
ASSERT_TRUE(output);
|
|
EXPECT_EQ(output.result.value().as_object().at("offers").as_array().size(), BookOffersHandler::kLIMIT_MAX);
|
|
});
|
|
}
|