Add book_offers RPC tests (RIPD-1283):

Migrate orderbook-test.js to cpp tests. Provide
coverage for error conditions in book_offers
RPC method.
This commit is contained in:
Mike Ellery
2016-09-22 16:08:52 -07:00
committed by Vinnie Falco
parent bb0b97f46b
commit 05e7373086
6 changed files with 568 additions and 37 deletions

View File

@@ -277,9 +277,9 @@ public:
// Book functions.
//
void getBookPage (bool bUnlimited, std::shared_ptr<ReadView const>& lpLedger,
void getBookPage (std::shared_ptr<ReadView const>& lpLedger,
Book const&, AccountID const& uTakerID, const bool bProof,
const unsigned int iLimit,
unsigned int iLimit,
Json::Value const& jvMarker, Json::Value& jvResult)
override;
@@ -2832,14 +2832,12 @@ InfoSub::pointer NetworkOPsImp::addRpcSub (
// NIKB FIXME this should be looked at. There's no reason why this shouldn't
// work, but it demonstrated poor performance.
//
// FIXME : support iLimit.
void NetworkOPsImp::getBookPage (
bool bUnlimited,
std::shared_ptr<ReadView const>& lpLedger,
Book const& book,
AccountID const& uTakerID,
bool const bProof,
const unsigned int iLimit,
unsigned int iLimit,
Json::Value const& jvMarker,
Json::Value& jvResult)
{ // CAUTION: This is the old get book page logic
@@ -2876,11 +2874,7 @@ void NetworkOPsImp::getBookPage (
auto const rate = transferRate(view, book.out.account);
auto viewJ = app_.journal ("View");
unsigned int left (iLimit == 0 ? 300 : iLimit);
if (! bUnlimited && left > 300)
left = 300;
while (!bDone && left-- > 0)
while (! bDone && iLimit-- > 0)
{
if (bDirectAdvance)
{
@@ -3048,14 +3042,12 @@ void NetworkOPsImp::getBookPage (
// This is the new code that uses the book iterators
// It has temporarily been disabled
// FIXME : support iLimit.
void NetworkOPsImp::getBookPage (
bool bUnlimited,
std::shared_ptr<ReadView const> lpLedger,
Book const& book,
AccountID const& uTakerID,
bool const bProof,
const unsigned int iLimit,
unsigned int iLimit,
Json::Value const& jvMarker,
Json::Value& jvResult)
{
@@ -3071,11 +3063,7 @@ void NetworkOPsImp::getBookPage (
const bool bGlobalFreeze = lesActive.isGlobalFrozen (book.out.account) ||
lesActive.isGlobalFrozen (book.in.account);
unsigned int left (iLimit == 0 ? 300 : iLimit);
if (! bUnlimited && left > 300)
left = 300;
while (left-- > 0 && obIterator.nextOffer ())
while (iLimit-- > 0 && obIterator.nextOffer ())
{
SLE::pointer sleOffer = obIterator.getCurrentOffer();

View File

@@ -139,12 +139,11 @@ public:
//
virtual void getBookPage (
bool bUnlimited,
std::shared_ptr<ReadView const>& lpLedger,
Book const& book,
AccountID const& uTakerID,
bool const bProof,
const unsigned int iLimit,
unsigned int iLimit,
Json::Value const& jvMarker,
Json::Value& jvResult) = 0;

View File

@@ -182,7 +182,6 @@ Json::Value doBookOffers (RPC::Context& context)
: Json::Value (Json::nullValue));
context.netOps.getBookPage (
isUnlimited (context.role),
lpLedger,
{{pay_currency, pay_issuer}, {get_currency, get_issuer}},
takerID ? *takerID : zero, bProof, limit, jvMarker, jvResult);

View File

@@ -287,10 +287,12 @@ Json::Value doSubscribe (RPC::Context& context)
auto add = [&](Json::StaticString field)
{
context.netOps.getBookPage (isUnlimited (context.role),
lpLedger, field == jss::asks ? reversed (book) : book,
takerID ? *takerID : noAccount(), false, 0, jvMarker,
jvOffers);
context.netOps.getBookPage (
lpLedger,
field == jss::asks ? reversed (book) : book,
takerID ? *takerID : noAccount(),
false, RPC::Tuning::bookOffers.rdefault,
jvMarker, jvOffers);
if (jvResult.isMember (field))
{

View File

@@ -45,7 +45,7 @@ static LimitRange const accountObjects = {10, 200, 400};
static LimitRange const accountOffers = {10, 200, 400};
/** Limits for the book_offers command. */
static LimitRange const bookOffers = {0, 0, 400};
static LimitRange const bookOffers = {0, 300, 400};
/** Limits for the no_ripple_check command. */
static LimitRange const noRippleCheck = {10, 300, 400};

View File

@@ -17,19 +17,41 @@
#include <BeastConfig.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/test/WSClient.h>
#include <ripple/test/jtx.h>
#include <ripple/beast/unit_test.h>
#include <ripple/rpc/impl/Tuning.h>
namespace ripple {
namespace test {
class Book_test : public beast::unit_test::suite
{
std::string getBookDir(jtx::Env & env, Issue const& in, Issue const& out)
{
std::string dir;
auto uBookBase = getBookBase({in, out});
auto uBookEnd = getQualityNext(uBookBase);
auto view = env.closed();
auto key = view->succ(uBookBase, uBookEnd);
if (key)
{
auto sleOfferDir = view->read(keylet::page(key.value()));
uint256 offerIndex;
unsigned int bookEntry;
cdirFirst(*view, sleOfferDir->key(), sleOfferDir, bookEntry, offerIndex, env.journal);
auto sleOffer = view->read(keylet::offer(offerIndex));
dir = to_string(sleOffer->getFieldH256(sfBookDirectory));
}
return dir;
}
public:
void
testOneSideEmptyBook()
{
testcase("One Side Empty Book");
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
@@ -50,7 +72,8 @@ public:
}
auto jv = wsc->invoke("subscribe", books);
BEAST_EXPECT(jv[jss::status] == "success");
if(! BEAST_EXPECT(jv[jss::status] == "success"))
return;
BEAST_EXPECT(jv[jss::result].isMember(jss::offers) &&
jv[jss::result][jss::offers].size() == 0);
BEAST_EXPECT(! jv[jss::result].isMember(jss::asks));
@@ -90,6 +113,7 @@ public:
void
testOneSideOffersInBook()
{
testcase("One Side Offers In Book");
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
@@ -119,7 +143,8 @@ public:
}
auto jv = wsc->invoke("subscribe", books);
BEAST_EXPECT(jv[jss::status] == "success");
if(! BEAST_EXPECT(jv[jss::status] == "success"))
return;
BEAST_EXPECT(jv[jss::result].isMember(jss::offers) &&
jv[jss::result][jss::offers].size() == 1);
BEAST_EXPECT(jv[jss::result][jss::offers][0u][jss::TakerGets] ==
@@ -163,6 +188,7 @@ public:
void
testBothSidesEmptyBook()
{
testcase("Both Sides Empty Book");
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
@@ -184,7 +210,8 @@ public:
}
auto jv = wsc->invoke("subscribe", books);
BEAST_EXPECT(jv[jss::status] == "success");
if(! BEAST_EXPECT(jv[jss::status] == "success"))
return;
BEAST_EXPECT(jv[jss::result].isMember(jss::asks) &&
jv[jss::result][jss::asks].size() == 0);
BEAST_EXPECT(jv[jss::result].isMember(jss::bids) &&
@@ -234,6 +261,7 @@ public:
void
testBothSidesOffersInBook()
{
testcase("Both Sides Offers In Book");
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
@@ -264,7 +292,8 @@ public:
}
auto jv = wsc->invoke("subscribe", books);
BEAST_EXPECT(jv[jss::status] == "success");
if(! BEAST_EXPECT(jv[jss::status] == "success"))
return;
BEAST_EXPECT(jv[jss::result].isMember(jss::asks) &&
jv[jss::result][jss::asks].size() == 1);
BEAST_EXPECT(jv[jss::result].isMember(jss::bids) &&
@@ -322,6 +351,7 @@ public:
void
testMultipleBooksOneSideEmptyBook()
{
testcase("Multiple Books, One Side Empty");
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
@@ -352,7 +382,8 @@ public:
}
auto jv = wsc->invoke("subscribe", books);
BEAST_EXPECT(jv[jss::status] == "success");
if(! BEAST_EXPECT(jv[jss::status] == "success"))
return;
BEAST_EXPECT(jv[jss::result].isMember(jss::offers) &&
jv[jss::result][jss::offers].size() == 0);
BEAST_EXPECT(! jv[jss::result].isMember(jss::asks));
@@ -417,6 +448,7 @@ public:
void
testMultipleBooksOneSideOffersInBook()
{
testcase("Multiple Books, One Side Offers In Book");
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
@@ -464,7 +496,8 @@ public:
}
auto jv = wsc->invoke("subscribe", books);
BEAST_EXPECT(jv[jss::status] == "success");
if(! BEAST_EXPECT(jv[jss::status] == "success"))
return;
BEAST_EXPECT(jv[jss::result].isMember(jss::offers) &&
jv[jss::result][jss::offers].size() == 2);
BEAST_EXPECT(jv[jss::result][jss::offers][0u][jss::TakerGets] ==
@@ -537,6 +570,7 @@ public:
void
testMultipleBooksBothSidesEmptyBook()
{
testcase("Multiple Books, Both Sides Empty Book");
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
@@ -569,7 +603,8 @@ public:
}
auto jv = wsc->invoke("subscribe", books);
BEAST_EXPECT(jv[jss::status] == "success");
if(! BEAST_EXPECT(jv[jss::status] == "success"))
return;
BEAST_EXPECT(jv[jss::result].isMember(jss::asks) &&
jv[jss::result][jss::asks].size() == 0);
BEAST_EXPECT(jv[jss::result].isMember(jss::bids) &&
@@ -653,6 +688,7 @@ public:
void
testMultipleBooksBothSidesOffersInBook()
{
testcase("Multiple Books, Both Sides Offers In Book");
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
@@ -703,7 +739,8 @@ public:
}
auto jv = wsc->invoke("subscribe", books);
BEAST_EXPECT(jv[jss::status] == "success");
if(! BEAST_EXPECT(jv[jss::status] == "success"))
return;
BEAST_EXPECT(jv[jss::result].isMember(jss::asks) &&
jv[jss::result][jss::asks].size() == 2);
BEAST_EXPECT(jv[jss::result].isMember(jss::bids) &&
@@ -800,20 +837,525 @@ public:
books)[jss::status] == "success");
}
void
testTrackOffers()
{
testcase("TrackOffers");
using namespace jtx;
Env env(*this);
Account gw {"gw"};
Account alice {"alice"};
Account bob {"bob"};
auto wsc = makeWSClient(env.app().config());
env.fund(XRP(20000), alice, bob, gw);
env.close();
auto USD = gw["USD"];
Json::Value books;
{
books[jss::books] = Json::arrayValue;
{
auto &j = books[jss::books].append(Json::objectValue);
j[jss::snapshot] = true;
j[jss::taker_gets][jss::currency] = "XRP";
j[jss::taker_pays][jss::currency] = "USD";
j[jss::taker_pays][jss::issuer] = gw.human();
}
auto jv = wsc->invoke("subscribe", books);
if(! BEAST_EXPECT(jv[jss::status] == "success"))
return;
BEAST_EXPECT(jv[jss::result].isMember(jss::offers) &&
jv[jss::result][jss::offers].size() == 0);
BEAST_EXPECT(! jv[jss::result].isMember(jss::asks));
BEAST_EXPECT(! jv[jss::result].isMember(jss::bids));
}
env(rate(gw, 1.1));
env.close();
env.trust(USD(1000), alice);
env.trust(USD(1000), bob);
env(pay(gw, alice, USD(100)));
env(pay(gw, bob, USD(50)));
env(offer(alice, XRP(4000), USD(10)));
env.close();
Json::Value jvParams;
jvParams[jss::taker] = env.master.human();
jvParams[jss::taker_pays][jss::currency] = "XRP";
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_gets][jss::currency] = "USD";
jvParams[jss::taker_gets][jss::issuer] = gw.human();
auto jrr = wsc->invoke("book_offers", jvParams)[jss::result];
BEAST_EXPECT(jrr[jss::offers].isArray());
BEAST_EXPECT(jrr[jss::offers].size() == 1);
auto const jrOffer = jrr[jss::offers][0u];
BEAST_EXPECT(jrOffer[sfAccount.fieldName] == alice.human());
BEAST_EXPECT(jrOffer[sfBookDirectory.fieldName] ==
getBookDir(env, XRP, USD.issue()));
BEAST_EXPECT(jrOffer[sfBookNode.fieldName] == "0000000000000000");
BEAST_EXPECT(jrOffer[jss::Flags] == 0);
BEAST_EXPECT(jrOffer[sfLedgerEntryType.fieldName] == "Offer");
BEAST_EXPECT(jrOffer[sfOwnerNode.fieldName] == "0000000000000000");
BEAST_EXPECT(jrOffer[sfSequence.fieldName] == 3);
BEAST_EXPECT(jrOffer[jss::TakerGets] == USD(10).value().getJson(0));
BEAST_EXPECT(jrOffer[jss::TakerPays] == XRP(4000).value().getJson(0));
BEAST_EXPECT(jrOffer[jss::owner_funds] == "100");
BEAST_EXPECT(jrOffer[jss::quality] == "400000000");
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == USD(10).value().getJson(0) &&
t[jss::owner_funds] == "100" &&
t[jss::TakerPays] == XRP(4000).value().getJson(0);
}));
env(offer(bob, XRP(2000), USD(5)));
env.close();
BEAST_EXPECT(wsc->findMsg(5s,
[&](auto const& jv)
{
auto const& t = jv[jss::transaction];
return t[jss::TransactionType] == "OfferCreate" &&
t[jss::TakerGets] == USD(5).value().getJson(0) &&
t[jss::owner_funds] == "50" &&
t[jss::TakerPays] == XRP(2000).value().getJson(0);
}));
jrr = wsc->invoke("book_offers", jvParams)[jss::result];
BEAST_EXPECT(jrr[jss::offers].isArray());
BEAST_EXPECT(jrr[jss::offers].size() == 2);
auto const jrNextOffer = jrr[jss::offers][1u];
BEAST_EXPECT(jrNextOffer[sfAccount.fieldName] == bob.human());
BEAST_EXPECT(jrNextOffer[sfBookDirectory.fieldName] ==
getBookDir(env, XRP, USD.issue()));
BEAST_EXPECT(jrNextOffer[sfBookNode.fieldName] == "0000000000000000");
BEAST_EXPECT(jrNextOffer[jss::Flags] == 0);
BEAST_EXPECT(jrNextOffer[sfLedgerEntryType.fieldName] == "Offer");
BEAST_EXPECT(jrNextOffer[sfOwnerNode.fieldName] == "0000000000000000");
BEAST_EXPECT(jrNextOffer[sfSequence.fieldName] == 3);
BEAST_EXPECT(jrNextOffer[jss::TakerGets] == USD(5).value().getJson(0));
BEAST_EXPECT(jrNextOffer[jss::TakerPays] ==
XRP(2000).value().getJson(0));
BEAST_EXPECT(jrNextOffer[jss::owner_funds] == "50");
BEAST_EXPECT(jrNextOffer[jss::quality] == "400000000");
BEAST_EXPECT(wsc->invoke("unsubscribe",
books)[jss::status] == "success");
}
void
testBookOfferErrors()
{
testcase("BookOffersRPC Errors");
using namespace jtx;
Env env(*this);
Account gw {"gw"};
Account alice {"alice"};
env.fund(XRP(10000), alice, gw);
env.close();
auto USD = gw["USD"];
{
Json::Value jvParams;
jvParams[jss::ledger_index] = 10u;
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] ==
"Missing field 'taker_pays'.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays] = Json::objectValue;
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] ==
"Missing field 'taker_gets'.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays] = "not an object";
jvParams[jss::taker_gets] = Json::objectValue;
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] ==
"Invalid field 'taker_pays', not object.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays] = Json::objectValue;
jvParams[jss::taker_gets] = "not an object";
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] ==
"Invalid field 'taker_gets', not object.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays] = Json::objectValue;
jvParams[jss::taker_gets] = Json::objectValue;
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] ==
"Missing field 'taker_pays.currency'.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays][jss::currency] = 1;
jvParams[jss::taker_gets] = Json::objectValue;
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] ==
"Invalid field 'taker_pays.currency', not string.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays][jss::currency] = "XRP";
jvParams[jss::taker_gets] = Json::objectValue;
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] ==
"Missing field 'taker_gets.currency'.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays][jss::currency] = "XRP";
jvParams[jss::taker_gets][jss::currency] = 1;
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] ==
"Invalid field 'taker_gets.currency', not string.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays][jss::currency] = "NOT_VALID";
jvParams[jss::taker_gets][jss::currency] = "XRP";
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "srcCurMalformed");
BEAST_EXPECT(jrr[jss::error_message] ==
"Invalid field 'taker_pays.currency', bad currency.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays][jss::currency] = "XRP";
jvParams[jss::taker_gets][jss::currency] = "NOT_VALID";
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "dstAmtMalformed");
BEAST_EXPECT(jrr[jss::error_message] ==
"Invalid field 'taker_gets.currency', bad currency.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays][jss::currency] = "XRP";
jvParams[jss::taker_gets][jss::currency] = "USD";
jvParams[jss::taker_gets][jss::issuer] = 1;
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] ==
"Invalid field 'taker_gets.issuer', not string.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays][jss::currency] = "XRP";
jvParams[jss::taker_pays][jss::issuer] = 1;
jvParams[jss::taker_gets][jss::currency] = "USD";
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] ==
"Invalid field 'taker_pays.issuer', not string.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays][jss::currency] = "XRP";
jvParams[jss::taker_pays][jss::issuer] = gw.human() + "DEAD";
jvParams[jss::taker_gets][jss::currency] = "USD";
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "srcIsrMalformed");
BEAST_EXPECT(jrr[jss::error_message] ==
"Invalid field 'taker_pays.issuer', bad issuer.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays][jss::currency] = "XRP";
jvParams[jss::taker_pays][jss::issuer] = toBase58(noAccount());
jvParams[jss::taker_gets][jss::currency] = "USD";
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "srcIsrMalformed");
BEAST_EXPECT(jrr[jss::error_message] ==
"Invalid field 'taker_pays.issuer', bad issuer account one.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays][jss::currency] = "XRP";
jvParams[jss::taker_gets][jss::currency] = "USD";
jvParams[jss::taker_gets][jss::issuer] = gw.human() + "DEAD";
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "dstIsrMalformed");
BEAST_EXPECT(jrr[jss::error_message] ==
"Invalid field 'taker_gets.issuer', bad issuer.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays][jss::currency] = "XRP";
jvParams[jss::taker_gets][jss::currency] = "USD";
jvParams[jss::taker_gets][jss::issuer] = toBase58(noAccount());
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "dstIsrMalformed");
BEAST_EXPECT(jrr[jss::error_message] ==
"Invalid field 'taker_gets.issuer', bad issuer account one.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays][jss::currency] = "XRP";
jvParams[jss::taker_pays][jss::issuer] = alice.human();
jvParams[jss::taker_gets][jss::currency] = "USD";
jvParams[jss::taker_gets][jss::issuer] = gw.human();
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "srcIsrMalformed");
BEAST_EXPECT(jrr[jss::error_message] ==
"Unneeded field 'taker_pays.issuer' "
"for XRP currency specification.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays][jss::currency] = "USD";
jvParams[jss::taker_pays][jss::issuer] = toBase58(xrpAccount());
jvParams[jss::taker_gets][jss::currency] = "USD";
jvParams[jss::taker_gets][jss::issuer] = gw.human();
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "srcIsrMalformed");
BEAST_EXPECT(jrr[jss::error_message] ==
"Invalid field 'taker_pays.issuer', expected non-XRP issuer.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker] = 1;
jvParams[jss::taker_pays][jss::currency] = "XRP";
jvParams[jss::taker_gets][jss::currency] = "USD";
jvParams[jss::taker_gets][jss::issuer] = gw.human();
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] ==
"Invalid field 'taker', not string.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker] = env.master.human() + "DEAD";
jvParams[jss::taker_pays][jss::currency] = "XRP";
jvParams[jss::taker_gets][jss::currency] = "USD";
jvParams[jss::taker_gets][jss::issuer] = gw.human();
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] ==
"Invalid field 'taker'.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker] = env.master.human();
jvParams[jss::taker_pays][jss::currency] = "USD";
jvParams[jss::taker_pays][jss::issuer] = gw.human();
jvParams[jss::taker_gets][jss::currency] = "USD";
jvParams[jss::taker_gets][jss::issuer] = gw.human();
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "badMarket");
BEAST_EXPECT(jrr[jss::error_message] == "No such market.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker] = env.master.human();
jvParams[jss::limit] = "0"; // NOT an integer
jvParams[jss::taker_pays][jss::currency] = "XRP";
jvParams[jss::taker_gets][jss::currency] = "USD";
jvParams[jss::taker_gets][jss::issuer] = gw.human();
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "invalidParams");
BEAST_EXPECT(jrr[jss::error_message] ==
"Invalid field 'limit', not unsigned integer.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays][jss::currency] = "USD";
jvParams[jss::taker_pays][jss::issuer] = gw.human();
jvParams[jss::taker_gets][jss::currency] = "USD";
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "dstIsrMalformed");
BEAST_EXPECT(jrr[jss::error_message] ==
"Invalid field 'taker_gets.issuer', "
"expected non-XRP issuer.");
}
{
Json::Value jvParams;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays][jss::currency] = "USD";
jvParams[jss::taker_pays][jss::issuer] = gw.human();
jvParams[jss::taker_gets][jss::currency] = "XRP";
jvParams[jss::taker_gets][jss::issuer] = gw.human();
auto const jrr = env.rpc(
"json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::error] == "dstIsrMalformed");
BEAST_EXPECT(jrr[jss::error_message] ==
"Unneeded field 'taker_gets.issuer' "
"for XRP currency specification.");
}
}
void
testBookOfferLimits(bool asAdmin)
{
testcase("BookOffer Limits");
using namespace jtx;
Env env(*this, [asAdmin]() {
auto p = std::make_unique<Config>();
setupConfigForUnitTests(*p);
if(! asAdmin)
{
(*p)["port_rpc"].set("admin","");
(*p)["port_ws"].set("admin","");
}
return p;
}());
Account gw {"gw"};
env.fund(XRP(200000), gw);
env.close();
auto USD = gw["USD"];
for(auto i = 0; i <= RPC::Tuning::bookOffers.rmax; i++)
env(offer(gw, XRP(50 + 1*i), USD(1.0 + 0.1*i)));
env.close();
Json::Value jvParams;
jvParams[jss::limit] = 1;
jvParams[jss::ledger_index] = "validated";
jvParams[jss::taker_pays][jss::currency] = "XRP";
jvParams[jss::taker_gets][jss::currency] = "USD";
jvParams[jss::taker_gets][jss::issuer] = gw.human();
auto jrr =
env.rpc("json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::offers].isArray());
BEAST_EXPECT(jrr[jss::offers].size() == (asAdmin ? 1u : 0u));
// NOTE - a marker field is not returned for this method
jvParams[jss::limit] = 0u;
jrr =
env.rpc("json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::offers].isArray());
BEAST_EXPECT(jrr[jss::offers].size() == 0u);
jvParams[jss::limit] = RPC::Tuning::bookOffers.rmax + 1;
jrr =
env.rpc("json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::offers].isArray());
BEAST_EXPECT(jrr[jss::offers].size() ==
(asAdmin ? RPC::Tuning::bookOffers.rmax + 1 : 0u));
jvParams[jss::limit] = Json::nullValue;
jrr =
env.rpc("json", "book_offers", to_string(jvParams)) [jss::result];
BEAST_EXPECT(jrr[jss::offers].isArray());
BEAST_EXPECT(jrr[jss::offers].size() ==
(asAdmin ? RPC::Tuning::bookOffers.rdefault : 0u));
}
void
run() override
{
testOneSideEmptyBook();
testOneSideOffersInBook();
testBothSidesEmptyBook();
testBothSidesOffersInBook();
testMultipleBooksOneSideEmptyBook();
testMultipleBooksOneSideOffersInBook();
testMultipleBooksBothSidesEmptyBook();
testMultipleBooksBothSidesOffersInBook();
testTrackOffers();
testBookOfferErrors();
testBookOfferLimits(true);
testBookOfferLimits(false);
}
};
@@ -821,3 +1363,4 @@ BEAST_DEFINE_TESTSUITE(Book,app,ripple);
} // test
} // ripple