rippled
Book_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012, 2013 Ripple Labs Inc.
5  Permission to use, copy, modify, and/or distribute this software for any
6  purpose with or without fee is hereby granted, provided that the above
7  copyright notice and this permission notice appear in all copies.
8  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16 //==============================================================================
17 
18 #include <ripple/beast/unit_test.h>
19 #include <ripple/protocol/Indexes.h>
20 #include <ripple/protocol/jss.h>
21 #include <ripple/rpc/impl/Tuning.h>
22 #include <test/jtx.h>
23 #include <test/jtx/WSClient.h>
24 
25 namespace ripple {
26 namespace test {
27 
28 class Book_test : public beast::unit_test::suite
29 {
31  getBookDir(jtx::Env& env, Issue const& in, Issue const& out)
32  {
33  std::string dir;
34  auto uBookBase = getBookBase({in, out});
35  auto uBookEnd = getQualityNext(uBookBase);
36  auto view = env.closed();
37  auto key = view->succ(uBookBase, uBookEnd);
38  if (key)
39  {
40  auto sleOfferDir = view->read(keylet::page(key.value()));
41  uint256 offerIndex;
42  unsigned int bookEntry;
43  cdirFirst(
44  *view, sleOfferDir->key(), sleOfferDir, bookEntry, offerIndex);
45  auto sleOffer = view->read(keylet::offer(offerIndex));
46  dir = to_string(sleOffer->getFieldH256(sfBookDirectory));
47  }
48  return dir;
49  }
50 
51 public:
52  void
54  {
55  testcase("One Side Empty Book");
56  using namespace std::chrono_literals;
57  using namespace jtx;
58  Env env(*this);
59  env.fund(XRP(10000), "alice");
60  auto USD = Account("alice")["USD"];
61  auto wsc = makeWSClient(env.app().config());
62  Json::Value books;
63 
64  {
65  // RPC subscribe to books stream
66  books[jss::books] = Json::arrayValue;
67  {
68  auto& j = books[jss::books].append(Json::objectValue);
69  j[jss::snapshot] = true;
70  j[jss::taker_gets][jss::currency] = "XRP";
71  j[jss::taker_pays][jss::currency] = "USD";
72  j[jss::taker_pays][jss::issuer] = Account("alice").human();
73  }
74 
75  auto jv = wsc->invoke("subscribe", books);
76  if (wsc->version() == 2)
77  {
78  BEAST_EXPECT(
79  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
80  BEAST_EXPECT(
81  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
82  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
83  }
84  if (!BEAST_EXPECT(jv[jss::status] == "success"))
85  return;
86  BEAST_EXPECT(
87  jv[jss::result].isMember(jss::offers) &&
88  jv[jss::result][jss::offers].size() == 0);
89  BEAST_EXPECT(!jv[jss::result].isMember(jss::asks));
90  BEAST_EXPECT(!jv[jss::result].isMember(jss::bids));
91  }
92 
93  {
94  // Create an ask: TakerPays 700, TakerGets 100/USD
95  env(offer("alice", XRP(700), USD(100)),
96  require(owners("alice", 1)));
97  env.close();
98 
99  // Check stream update
100  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
101  auto const& t = jv[jss::transaction];
102  return t[jss::TransactionType] == jss::OfferCreate &&
103  t[jss::TakerGets] ==
104  USD(100).value().getJson(JsonOptions::none) &&
105  t[jss::TakerPays] ==
106  XRP(700).value().getJson(JsonOptions::none);
107  }));
108  }
109 
110  {
111  // Create a bid: TakerPays 100/USD, TakerGets 75
112  env(offer("alice", USD(100), XRP(75)), require(owners("alice", 2)));
113  env.close();
114  BEAST_EXPECT(!wsc->getMsg(10ms));
115  }
116 
117  // RPC unsubscribe
118  auto jv = wsc->invoke("unsubscribe", books);
119  BEAST_EXPECT(jv[jss::status] == "success");
120  if (wsc->version() == 2)
121  {
122  BEAST_EXPECT(
123  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
124  BEAST_EXPECT(
125  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
126  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
127  }
128  }
129 
130  void
132  {
133  testcase("One Side Offers In Book");
134  using namespace std::chrono_literals;
135  using namespace jtx;
136  Env env(*this);
137  env.fund(XRP(10000), "alice");
138  auto USD = Account("alice")["USD"];
139  auto wsc = makeWSClient(env.app().config());
140  Json::Value books;
141 
142  // Create an ask: TakerPays 500, TakerGets 100/USD
143  env(offer("alice", XRP(500), USD(100)), require(owners("alice", 1)));
144 
145  // Create a bid: TakerPays 100/USD, TakerGets 200
146  env(offer("alice", USD(100), XRP(200)), require(owners("alice", 2)));
147  env.close();
148 
149  {
150  // RPC subscribe to books stream
151  books[jss::books] = Json::arrayValue;
152  {
153  auto& j = books[jss::books].append(Json::objectValue);
154  j[jss::snapshot] = true;
155  j[jss::taker_gets][jss::currency] = "XRP";
156  j[jss::taker_pays][jss::currency] = "USD";
157  j[jss::taker_pays][jss::issuer] = Account("alice").human();
158  }
159 
160  auto jv = wsc->invoke("subscribe", books);
161  if (wsc->version() == 2)
162  {
163  BEAST_EXPECT(
164  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
165  BEAST_EXPECT(
166  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
167  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
168  }
169  if (!BEAST_EXPECT(jv[jss::status] == "success"))
170  return;
171  BEAST_EXPECT(
172  jv[jss::result].isMember(jss::offers) &&
173  jv[jss::result][jss::offers].size() == 1);
174  BEAST_EXPECT(
175  jv[jss::result][jss::offers][0u][jss::TakerGets] ==
176  XRP(200).value().getJson(JsonOptions::none));
177  BEAST_EXPECT(
178  jv[jss::result][jss::offers][0u][jss::TakerPays] ==
179  USD(100).value().getJson(JsonOptions::none));
180  BEAST_EXPECT(!jv[jss::result].isMember(jss::asks));
181  BEAST_EXPECT(!jv[jss::result].isMember(jss::bids));
182  }
183 
184  {
185  // Create an ask: TakerPays 700, TakerGets 100/USD
186  env(offer("alice", XRP(700), USD(100)),
187  require(owners("alice", 3)));
188  env.close();
189 
190  // Check stream update
191  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
192  auto const& t = jv[jss::transaction];
193  return t[jss::TransactionType] == jss::OfferCreate &&
194  t[jss::TakerGets] ==
195  USD(100).value().getJson(JsonOptions::none) &&
196  t[jss::TakerPays] ==
197  XRP(700).value().getJson(JsonOptions::none);
198  }));
199  }
200 
201  {
202  // Create a bid: TakerPays 100/USD, TakerGets 75
203  env(offer("alice", USD(100), XRP(75)), require(owners("alice", 4)));
204  env.close();
205  BEAST_EXPECT(!wsc->getMsg(10ms));
206  }
207 
208  // RPC unsubscribe
209  auto jv = wsc->invoke("unsubscribe", books);
210  BEAST_EXPECT(jv[jss::status] == "success");
211  if (wsc->version() == 2)
212  {
213  BEAST_EXPECT(
214  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
215  BEAST_EXPECT(
216  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
217  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
218  }
219  }
220 
221  void
223  {
224  testcase("Both Sides Empty Book");
225  using namespace std::chrono_literals;
226  using namespace jtx;
227  Env env(*this);
228  env.fund(XRP(10000), "alice");
229  auto USD = Account("alice")["USD"];
230  auto wsc = makeWSClient(env.app().config());
231  Json::Value books;
232 
233  {
234  // RPC subscribe to books stream
235  books[jss::books] = Json::arrayValue;
236  {
237  auto& j = books[jss::books].append(Json::objectValue);
238  j[jss::snapshot] = true;
239  j[jss::both] = true;
240  j[jss::taker_gets][jss::currency] = "XRP";
241  j[jss::taker_pays][jss::currency] = "USD";
242  j[jss::taker_pays][jss::issuer] = Account("alice").human();
243  }
244 
245  auto jv = wsc->invoke("subscribe", books);
246  if (wsc->version() == 2)
247  {
248  BEAST_EXPECT(
249  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
250  BEAST_EXPECT(
251  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
252  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
253  }
254  if (!BEAST_EXPECT(jv[jss::status] == "success"))
255  return;
256  BEAST_EXPECT(
257  jv[jss::result].isMember(jss::asks) &&
258  jv[jss::result][jss::asks].size() == 0);
259  BEAST_EXPECT(
260  jv[jss::result].isMember(jss::bids) &&
261  jv[jss::result][jss::bids].size() == 0);
262  BEAST_EXPECT(!jv[jss::result].isMember(jss::offers));
263  }
264 
265  {
266  // Create an ask: TakerPays 700, TakerGets 100/USD
267  env(offer("alice", XRP(700), USD(100)),
268  require(owners("alice", 1)));
269  env.close();
270 
271  // Check stream update
272  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
273  auto const& t = jv[jss::transaction];
274  return t[jss::TransactionType] == jss::OfferCreate &&
275  t[jss::TakerGets] ==
276  USD(100).value().getJson(JsonOptions::none) &&
277  t[jss::TakerPays] ==
278  XRP(700).value().getJson(JsonOptions::none);
279  }));
280  }
281 
282  {
283  // Create a bid: TakerPays 100/USD, TakerGets 75
284  env(offer("alice", USD(100), XRP(75)), require(owners("alice", 2)));
285  env.close();
286 
287  // Check stream update
288  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
289  auto const& t = jv[jss::transaction];
290  return t[jss::TransactionType] == jss::OfferCreate &&
291  t[jss::TakerGets] ==
292  XRP(75).value().getJson(JsonOptions::none) &&
293  t[jss::TakerPays] ==
294  USD(100).value().getJson(JsonOptions::none);
295  }));
296  }
297 
298  // RPC unsubscribe
299  auto jv = wsc->invoke("unsubscribe", books);
300  BEAST_EXPECT(jv[jss::status] == "success");
301  if (wsc->version() == 2)
302  {
303  BEAST_EXPECT(
304  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
305  BEAST_EXPECT(
306  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
307  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
308  }
309  }
310 
311  void
313  {
314  testcase("Both Sides Offers In Book");
315  using namespace std::chrono_literals;
316  using namespace jtx;
317  Env env(*this);
318  env.fund(XRP(10000), "alice");
319  auto USD = Account("alice")["USD"];
320  auto wsc = makeWSClient(env.app().config());
321  Json::Value books;
322 
323  // Create an ask: TakerPays 500, TakerGets 100/USD
324  env(offer("alice", XRP(500), USD(100)), require(owners("alice", 1)));
325 
326  // Create a bid: TakerPays 100/USD, TakerGets 200
327  env(offer("alice", USD(100), XRP(200)), require(owners("alice", 2)));
328  env.close();
329 
330  {
331  // RPC subscribe to books stream
332  books[jss::books] = Json::arrayValue;
333  {
334  auto& j = books[jss::books].append(Json::objectValue);
335  j[jss::snapshot] = true;
336  j[jss::both] = true;
337  j[jss::taker_gets][jss::currency] = "XRP";
338  j[jss::taker_pays][jss::currency] = "USD";
339  j[jss::taker_pays][jss::issuer] = Account("alice").human();
340  }
341 
342  auto jv = wsc->invoke("subscribe", books);
343  if (wsc->version() == 2)
344  {
345  BEAST_EXPECT(
346  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
347  BEAST_EXPECT(
348  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
349  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
350  }
351  if (!BEAST_EXPECT(jv[jss::status] == "success"))
352  return;
353  BEAST_EXPECT(
354  jv[jss::result].isMember(jss::asks) &&
355  jv[jss::result][jss::asks].size() == 1);
356  BEAST_EXPECT(
357  jv[jss::result].isMember(jss::bids) &&
358  jv[jss::result][jss::bids].size() == 1);
359  BEAST_EXPECT(
360  jv[jss::result][jss::asks][0u][jss::TakerGets] ==
361  USD(100).value().getJson(JsonOptions::none));
362  BEAST_EXPECT(
363  jv[jss::result][jss::asks][0u][jss::TakerPays] ==
364  XRP(500).value().getJson(JsonOptions::none));
365  BEAST_EXPECT(
366  jv[jss::result][jss::bids][0u][jss::TakerGets] ==
367  XRP(200).value().getJson(JsonOptions::none));
368  BEAST_EXPECT(
369  jv[jss::result][jss::bids][0u][jss::TakerPays] ==
370  USD(100).value().getJson(JsonOptions::none));
371  BEAST_EXPECT(!jv[jss::result].isMember(jss::offers));
372  }
373 
374  {
375  // Create an ask: TakerPays 700, TakerGets 100/USD
376  env(offer("alice", XRP(700), USD(100)),
377  require(owners("alice", 3)));
378  env.close();
379 
380  // Check stream update
381  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
382  auto const& t = jv[jss::transaction];
383  return t[jss::TransactionType] == jss::OfferCreate &&
384  t[jss::TakerGets] ==
385  USD(100).value().getJson(JsonOptions::none) &&
386  t[jss::TakerPays] ==
387  XRP(700).value().getJson(JsonOptions::none);
388  }));
389  }
390 
391  {
392  // Create a bid: TakerPays 100/USD, TakerGets 75
393  env(offer("alice", USD(100), XRP(75)), require(owners("alice", 4)));
394  env.close();
395 
396  // Check stream update
397  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
398  auto const& t = jv[jss::transaction];
399  return t[jss::TransactionType] == jss::OfferCreate &&
400  t[jss::TakerGets] ==
401  XRP(75).value().getJson(JsonOptions::none) &&
402  t[jss::TakerPays] ==
403  USD(100).value().getJson(JsonOptions::none);
404  }));
405  }
406 
407  // RPC unsubscribe
408  auto jv = wsc->invoke("unsubscribe", books);
409  BEAST_EXPECT(jv[jss::status] == "success");
410  if (wsc->version() == 2)
411  {
412  BEAST_EXPECT(
413  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
414  BEAST_EXPECT(
415  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
416  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
417  }
418  }
419 
420  void
422  {
423  testcase("Multiple Books, One Side Empty");
424  using namespace std::chrono_literals;
425  using namespace jtx;
426  Env env(*this);
427  env.fund(XRP(10000), "alice");
428  auto USD = Account("alice")["USD"];
429  auto CNY = Account("alice")["CNY"];
430  auto JPY = Account("alice")["JPY"];
431  auto wsc = makeWSClient(env.app().config());
432  Json::Value books;
433 
434  {
435  // RPC subscribe to books stream
436  books[jss::books] = Json::arrayValue;
437  {
438  auto& j = books[jss::books].append(Json::objectValue);
439  j[jss::snapshot] = true;
440  j[jss::taker_gets][jss::currency] = "XRP";
441  j[jss::taker_pays][jss::currency] = "USD";
442  j[jss::taker_pays][jss::issuer] = Account("alice").human();
443  }
444  {
445  auto& j = books[jss::books].append(Json::objectValue);
446  j[jss::snapshot] = true;
447  j[jss::taker_gets][jss::currency] = "CNY";
448  j[jss::taker_gets][jss::issuer] = Account("alice").human();
449  j[jss::taker_pays][jss::currency] = "JPY";
450  j[jss::taker_pays][jss::issuer] = Account("alice").human();
451  }
452 
453  auto jv = wsc->invoke("subscribe", books);
454  if (wsc->version() == 2)
455  {
456  BEAST_EXPECT(
457  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
458  BEAST_EXPECT(
459  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
460  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
461  }
462  if (!BEAST_EXPECT(jv[jss::status] == "success"))
463  return;
464  BEAST_EXPECT(
465  jv[jss::result].isMember(jss::offers) &&
466  jv[jss::result][jss::offers].size() == 0);
467  BEAST_EXPECT(!jv[jss::result].isMember(jss::asks));
468  BEAST_EXPECT(!jv[jss::result].isMember(jss::bids));
469  }
470 
471  {
472  // Create an ask: TakerPays 700, TakerGets 100/USD
473  env(offer("alice", XRP(700), USD(100)),
474  require(owners("alice", 1)));
475  env.close();
476 
477  // Check stream update
478  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
479  auto const& t = jv[jss::transaction];
480  return t[jss::TransactionType] == jss::OfferCreate &&
481  t[jss::TakerGets] ==
482  USD(100).value().getJson(JsonOptions::none) &&
483  t[jss::TakerPays] ==
484  XRP(700).value().getJson(JsonOptions::none);
485  }));
486  }
487 
488  {
489  // Create a bid: TakerPays 100/USD, TakerGets 75
490  env(offer("alice", USD(100), XRP(75)), require(owners("alice", 2)));
491  env.close();
492  BEAST_EXPECT(!wsc->getMsg(10ms));
493  }
494 
495  {
496  // Create an ask: TakerPays 700/CNY, TakerGets 100/JPY
497  env(offer("alice", CNY(700), JPY(100)),
498  require(owners("alice", 3)));
499  env.close();
500 
501  // Check stream update
502  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
503  auto const& t = jv[jss::transaction];
504  return t[jss::TransactionType] == jss::OfferCreate &&
505  t[jss::TakerGets] ==
506  JPY(100).value().getJson(JsonOptions::none) &&
507  t[jss::TakerPays] ==
508  CNY(700).value().getJson(JsonOptions::none);
509  }));
510  }
511 
512  {
513  // Create a bid: TakerPays 100/JPY, TakerGets 75/CNY
514  env(offer("alice", JPY(100), CNY(75)), require(owners("alice", 4)));
515  env.close();
516  BEAST_EXPECT(!wsc->getMsg(10ms));
517  }
518 
519  // RPC unsubscribe
520  auto jv = wsc->invoke("unsubscribe", books);
521  BEAST_EXPECT(jv[jss::status] == "success");
522  if (wsc->version() == 2)
523  {
524  BEAST_EXPECT(
525  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
526  BEAST_EXPECT(
527  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
528  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
529  }
530  }
531 
532  void
534  {
535  testcase("Multiple Books, One Side Offers In Book");
536  using namespace std::chrono_literals;
537  using namespace jtx;
538  Env env(*this);
539  env.fund(XRP(10000), "alice");
540  auto USD = Account("alice")["USD"];
541  auto CNY = Account("alice")["CNY"];
542  auto JPY = Account("alice")["JPY"];
543  auto wsc = makeWSClient(env.app().config());
544  Json::Value books;
545 
546  // Create an ask: TakerPays 500, TakerGets 100/USD
547  env(offer("alice", XRP(500), USD(100)), require(owners("alice", 1)));
548 
549  // Create an ask: TakerPays 500/CNY, TakerGets 100/JPY
550  env(offer("alice", CNY(500), JPY(100)), require(owners("alice", 2)));
551 
552  // Create a bid: TakerPays 100/USD, TakerGets 200
553  env(offer("alice", USD(100), XRP(200)), require(owners("alice", 3)));
554 
555  // Create a bid: TakerPays 100/JPY, TakerGets 200/CNY
556  env(offer("alice", JPY(100), CNY(200)), require(owners("alice", 4)));
557  env.close();
558 
559  {
560  // RPC subscribe to books stream
561  books[jss::books] = Json::arrayValue;
562  {
563  auto& j = books[jss::books].append(Json::objectValue);
564  j[jss::snapshot] = true;
565  j[jss::taker_gets][jss::currency] = "XRP";
566  j[jss::taker_pays][jss::currency] = "USD";
567  j[jss::taker_pays][jss::issuer] = Account("alice").human();
568  }
569  {
570  auto& j = books[jss::books].append(Json::objectValue);
571  j[jss::snapshot] = true;
572  j[jss::taker_gets][jss::currency] = "CNY";
573  j[jss::taker_gets][jss::issuer] = Account("alice").human();
574  j[jss::taker_pays][jss::currency] = "JPY";
575  j[jss::taker_pays][jss::issuer] = Account("alice").human();
576  }
577 
578  auto jv = wsc->invoke("subscribe", books);
579  if (wsc->version() == 2)
580  {
581  BEAST_EXPECT(
582  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
583  BEAST_EXPECT(
584  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
585  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
586  }
587  if (!BEAST_EXPECT(jv[jss::status] == "success"))
588  return;
589  BEAST_EXPECT(
590  jv[jss::result].isMember(jss::offers) &&
591  jv[jss::result][jss::offers].size() == 2);
592  BEAST_EXPECT(
593  jv[jss::result][jss::offers][0u][jss::TakerGets] ==
594  XRP(200).value().getJson(JsonOptions::none));
595  BEAST_EXPECT(
596  jv[jss::result][jss::offers][0u][jss::TakerPays] ==
597  USD(100).value().getJson(JsonOptions::none));
598  BEAST_EXPECT(
599  jv[jss::result][jss::offers][1u][jss::TakerGets] ==
600  CNY(200).value().getJson(JsonOptions::none));
601  BEAST_EXPECT(
602  jv[jss::result][jss::offers][1u][jss::TakerPays] ==
603  JPY(100).value().getJson(JsonOptions::none));
604  BEAST_EXPECT(!jv[jss::result].isMember(jss::asks));
605  BEAST_EXPECT(!jv[jss::result].isMember(jss::bids));
606  }
607 
608  {
609  // Create an ask: TakerPays 700, TakerGets 100/USD
610  env(offer("alice", XRP(700), USD(100)),
611  require(owners("alice", 5)));
612  env.close();
613 
614  // Check stream update
615  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
616  auto const& t = jv[jss::transaction];
617  return t[jss::TransactionType] == jss::OfferCreate &&
618  t[jss::TakerGets] ==
619  USD(100).value().getJson(JsonOptions::none) &&
620  t[jss::TakerPays] ==
621  XRP(700).value().getJson(JsonOptions::none);
622  }));
623  }
624 
625  {
626  // Create a bid: TakerPays 100/USD, TakerGets 75
627  env(offer("alice", USD(100), XRP(75)), require(owners("alice", 6)));
628  env.close();
629  BEAST_EXPECT(!wsc->getMsg(10ms));
630  }
631 
632  {
633  // Create an ask: TakerPays 700/CNY, TakerGets 100/JPY
634  env(offer("alice", CNY(700), JPY(100)),
635  require(owners("alice", 7)));
636  env.close();
637 
638  // Check stream update
639  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
640  auto const& t = jv[jss::transaction];
641  return t[jss::TransactionType] == jss::OfferCreate &&
642  t[jss::TakerGets] ==
643  JPY(100).value().getJson(JsonOptions::none) &&
644  t[jss::TakerPays] ==
645  CNY(700).value().getJson(JsonOptions::none);
646  }));
647  }
648 
649  {
650  // Create a bid: TakerPays 100/JPY, TakerGets 75/CNY
651  env(offer("alice", JPY(100), CNY(75)), require(owners("alice", 8)));
652  env.close();
653  BEAST_EXPECT(!wsc->getMsg(10ms));
654  }
655 
656  // RPC unsubscribe
657  auto jv = wsc->invoke("unsubscribe", books);
658  BEAST_EXPECT(jv[jss::status] == "success");
659  if (wsc->version() == 2)
660  {
661  BEAST_EXPECT(
662  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
663  BEAST_EXPECT(
664  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
665  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
666  }
667  }
668 
669  void
671  {
672  testcase("Multiple Books, Both Sides Empty Book");
673  using namespace std::chrono_literals;
674  using namespace jtx;
675  Env env(*this);
676  env.fund(XRP(10000), "alice");
677  auto USD = Account("alice")["USD"];
678  auto CNY = Account("alice")["CNY"];
679  auto JPY = Account("alice")["JPY"];
680  auto wsc = makeWSClient(env.app().config());
681  Json::Value books;
682 
683  {
684  // RPC subscribe to books stream
685  books[jss::books] = Json::arrayValue;
686  {
687  auto& j = books[jss::books].append(Json::objectValue);
688  j[jss::snapshot] = true;
689  j[jss::both] = true;
690  j[jss::taker_gets][jss::currency] = "XRP";
691  j[jss::taker_pays][jss::currency] = "USD";
692  j[jss::taker_pays][jss::issuer] = Account("alice").human();
693  }
694  {
695  auto& j = books[jss::books].append(Json::objectValue);
696  j[jss::snapshot] = true;
697  j[jss::both] = true;
698  j[jss::taker_gets][jss::currency] = "CNY";
699  j[jss::taker_gets][jss::issuer] = Account("alice").human();
700  j[jss::taker_pays][jss::currency] = "JPY";
701  j[jss::taker_pays][jss::issuer] = Account("alice").human();
702  }
703 
704  auto jv = wsc->invoke("subscribe", books);
705  if (wsc->version() == 2)
706  {
707  BEAST_EXPECT(
708  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
709  BEAST_EXPECT(
710  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
711  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
712  }
713  if (!BEAST_EXPECT(jv[jss::status] == "success"))
714  return;
715  BEAST_EXPECT(
716  jv[jss::result].isMember(jss::asks) &&
717  jv[jss::result][jss::asks].size() == 0);
718  BEAST_EXPECT(
719  jv[jss::result].isMember(jss::bids) &&
720  jv[jss::result][jss::bids].size() == 0);
721  BEAST_EXPECT(!jv[jss::result].isMember(jss::offers));
722  }
723 
724  {
725  // Create an ask: TakerPays 700, TakerGets 100/USD
726  env(offer("alice", XRP(700), USD(100)),
727  require(owners("alice", 1)));
728  env.close();
729 
730  // Check stream update
731  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
732  auto const& t = jv[jss::transaction];
733  return t[jss::TransactionType] == jss::OfferCreate &&
734  t[jss::TakerGets] ==
735  USD(100).value().getJson(JsonOptions::none) &&
736  t[jss::TakerPays] ==
737  XRP(700).value().getJson(JsonOptions::none);
738  }));
739  }
740 
741  {
742  // Create a bid: TakerPays 100/USD, TakerGets 75
743  env(offer("alice", USD(100), XRP(75)), require(owners("alice", 2)));
744  env.close();
745 
746  // Check stream update
747  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
748  auto const& t = jv[jss::transaction];
749  return t[jss::TransactionType] == jss::OfferCreate &&
750  t[jss::TakerGets] ==
751  XRP(75).value().getJson(JsonOptions::none) &&
752  t[jss::TakerPays] ==
753  USD(100).value().getJson(JsonOptions::none);
754  }));
755  }
756 
757  {
758  // Create an ask: TakerPays 700/CNY, TakerGets 100/JPY
759  env(offer("alice", CNY(700), JPY(100)),
760  require(owners("alice", 3)));
761  env.close();
762 
763  // Check stream update
764  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
765  auto const& t = jv[jss::transaction];
766  return t[jss::TransactionType] == jss::OfferCreate &&
767  t[jss::TakerGets] ==
768  JPY(100).value().getJson(JsonOptions::none) &&
769  t[jss::TakerPays] ==
770  CNY(700).value().getJson(JsonOptions::none);
771  }));
772  }
773 
774  {
775  // Create a bid: TakerPays 100/JPY, TakerGets 75/CNY
776  env(offer("alice", JPY(100), CNY(75)), require(owners("alice", 4)));
777  env.close();
778 
779  // Check stream update
780  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
781  auto const& t = jv[jss::transaction];
782  return t[jss::TransactionType] == jss::OfferCreate &&
783  t[jss::TakerGets] ==
784  CNY(75).value().getJson(JsonOptions::none) &&
785  t[jss::TakerPays] ==
786  JPY(100).value().getJson(JsonOptions::none);
787  }));
788  }
789 
790  // RPC unsubscribe
791  auto jv = wsc->invoke("unsubscribe", books);
792  BEAST_EXPECT(jv[jss::status] == "success");
793  if (wsc->version() == 2)
794  {
795  BEAST_EXPECT(
796  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
797  BEAST_EXPECT(
798  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
799  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
800  }
801  }
802 
803  void
805  {
806  testcase("Multiple Books, Both Sides Offers In Book");
807  using namespace std::chrono_literals;
808  using namespace jtx;
809  Env env(*this);
810  env.fund(XRP(10000), "alice");
811  auto USD = Account("alice")["USD"];
812  auto CNY = Account("alice")["CNY"];
813  auto JPY = Account("alice")["JPY"];
814  auto wsc = makeWSClient(env.app().config());
815  Json::Value books;
816 
817  // Create an ask: TakerPays 500, TakerGets 100/USD
818  env(offer("alice", XRP(500), USD(100)), require(owners("alice", 1)));
819 
820  // Create an ask: TakerPays 500/CNY, TakerGets 100/JPY
821  env(offer("alice", CNY(500), JPY(100)), require(owners("alice", 2)));
822 
823  // Create a bid: TakerPays 100/USD, TakerGets 200
824  env(offer("alice", USD(100), XRP(200)), require(owners("alice", 3)));
825 
826  // Create a bid: TakerPays 100/JPY, TakerGets 200/CNY
827  env(offer("alice", JPY(100), CNY(200)), require(owners("alice", 4)));
828  env.close();
829 
830  {
831  // RPC subscribe to books stream
832  books[jss::books] = Json::arrayValue;
833  {
834  auto& j = books[jss::books].append(Json::objectValue);
835  j[jss::snapshot] = true;
836  j[jss::both] = true;
837  j[jss::taker_gets][jss::currency] = "XRP";
838  j[jss::taker_pays][jss::currency] = "USD";
839  j[jss::taker_pays][jss::issuer] = Account("alice").human();
840  }
841  // RPC subscribe to books stream
842  {
843  auto& j = books[jss::books].append(Json::objectValue);
844  j[jss::snapshot] = true;
845  j[jss::both] = true;
846  j[jss::taker_gets][jss::currency] = "CNY";
847  j[jss::taker_gets][jss::issuer] = Account("alice").human();
848  j[jss::taker_pays][jss::currency] = "JPY";
849  j[jss::taker_pays][jss::issuer] = Account("alice").human();
850  }
851 
852  auto jv = wsc->invoke("subscribe", books);
853  if (wsc->version() == 2)
854  {
855  BEAST_EXPECT(
856  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
857  BEAST_EXPECT(
858  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
859  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
860  }
861  if (!BEAST_EXPECT(jv[jss::status] == "success"))
862  return;
863  BEAST_EXPECT(
864  jv[jss::result].isMember(jss::asks) &&
865  jv[jss::result][jss::asks].size() == 2);
866  BEAST_EXPECT(
867  jv[jss::result].isMember(jss::bids) &&
868  jv[jss::result][jss::bids].size() == 2);
869  BEAST_EXPECT(
870  jv[jss::result][jss::asks][0u][jss::TakerGets] ==
871  USD(100).value().getJson(JsonOptions::none));
872  BEAST_EXPECT(
873  jv[jss::result][jss::asks][0u][jss::TakerPays] ==
874  XRP(500).value().getJson(JsonOptions::none));
875  BEAST_EXPECT(
876  jv[jss::result][jss::asks][1u][jss::TakerGets] ==
877  JPY(100).value().getJson(JsonOptions::none));
878  BEAST_EXPECT(
879  jv[jss::result][jss::asks][1u][jss::TakerPays] ==
880  CNY(500).value().getJson(JsonOptions::none));
881  BEAST_EXPECT(
882  jv[jss::result][jss::bids][0u][jss::TakerGets] ==
883  XRP(200).value().getJson(JsonOptions::none));
884  BEAST_EXPECT(
885  jv[jss::result][jss::bids][0u][jss::TakerPays] ==
886  USD(100).value().getJson(JsonOptions::none));
887  BEAST_EXPECT(
888  jv[jss::result][jss::bids][1u][jss::TakerGets] ==
889  CNY(200).value().getJson(JsonOptions::none));
890  BEAST_EXPECT(
891  jv[jss::result][jss::bids][1u][jss::TakerPays] ==
892  JPY(100).value().getJson(JsonOptions::none));
893  BEAST_EXPECT(!jv[jss::result].isMember(jss::offers));
894  }
895 
896  {
897  // Create an ask: TakerPays 700, TakerGets 100/USD
898  env(offer("alice", XRP(700), USD(100)),
899  require(owners("alice", 5)));
900  env.close();
901 
902  // Check stream update
903  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
904  auto const& t = jv[jss::transaction];
905  return t[jss::TransactionType] == jss::OfferCreate &&
906  t[jss::TakerGets] ==
907  USD(100).value().getJson(JsonOptions::none) &&
908  t[jss::TakerPays] ==
909  XRP(700).value().getJson(JsonOptions::none);
910  }));
911  }
912 
913  {
914  // Create a bid: TakerPays 100/USD, TakerGets 75
915  env(offer("alice", USD(100), XRP(75)), require(owners("alice", 6)));
916  env.close();
917 
918  // Check stream update
919  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
920  auto const& t = jv[jss::transaction];
921  return t[jss::TransactionType] == jss::OfferCreate &&
922  t[jss::TakerGets] ==
923  XRP(75).value().getJson(JsonOptions::none) &&
924  t[jss::TakerPays] ==
925  USD(100).value().getJson(JsonOptions::none);
926  }));
927  }
928 
929  {
930  // Create an ask: TakerPays 700/CNY, TakerGets 100/JPY
931  env(offer("alice", CNY(700), JPY(100)),
932  require(owners("alice", 7)));
933  env.close();
934 
935  // Check stream update
936  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
937  auto const& t = jv[jss::transaction];
938  return t[jss::TransactionType] == jss::OfferCreate &&
939  t[jss::TakerGets] ==
940  JPY(100).value().getJson(JsonOptions::none) &&
941  t[jss::TakerPays] ==
942  CNY(700).value().getJson(JsonOptions::none);
943  }));
944  }
945 
946  {
947  // Create a bid: TakerPays 100/JPY, TakerGets 75/CNY
948  env(offer("alice", JPY(100), CNY(75)), require(owners("alice", 8)));
949  env.close();
950 
951  // Check stream update
952  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
953  auto const& t = jv[jss::transaction];
954  return t[jss::TransactionType] == jss::OfferCreate &&
955  t[jss::TakerGets] ==
956  CNY(75).value().getJson(JsonOptions::none) &&
957  t[jss::TakerPays] ==
958  JPY(100).value().getJson(JsonOptions::none);
959  }));
960  }
961 
962  // RPC unsubscribe
963  auto jv = wsc->invoke("unsubscribe", books);
964  BEAST_EXPECT(jv[jss::status] == "success");
965  if (wsc->version() == 2)
966  {
967  BEAST_EXPECT(
968  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
969  BEAST_EXPECT(
970  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
971  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
972  }
973  }
974 
975  void
977  {
978  testcase("TrackOffers");
979  using namespace jtx;
980  Env env(*this);
981  Account gw{"gw"};
982  Account alice{"alice"};
983  Account bob{"bob"};
984  auto wsc = makeWSClient(env.app().config());
985  env.fund(XRP(20000), alice, bob, gw);
986  env.close();
987  auto USD = gw["USD"];
988 
989  Json::Value books;
990  {
991  books[jss::books] = Json::arrayValue;
992  {
993  auto& j = books[jss::books].append(Json::objectValue);
994  j[jss::snapshot] = true;
995  j[jss::taker_gets][jss::currency] = "XRP";
996  j[jss::taker_pays][jss::currency] = "USD";
997  j[jss::taker_pays][jss::issuer] = gw.human();
998  }
999 
1000  auto jv = wsc->invoke("subscribe", books);
1001  if (wsc->version() == 2)
1002  {
1003  BEAST_EXPECT(
1004  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
1005  BEAST_EXPECT(
1006  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
1007  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
1008  }
1009  if (!BEAST_EXPECT(jv[jss::status] == "success"))
1010  return;
1011  BEAST_EXPECT(
1012  jv[jss::result].isMember(jss::offers) &&
1013  jv[jss::result][jss::offers].size() == 0);
1014  BEAST_EXPECT(!jv[jss::result].isMember(jss::asks));
1015  BEAST_EXPECT(!jv[jss::result].isMember(jss::bids));
1016  }
1017 
1018  env(rate(gw, 1.1));
1019  env.close();
1020  env.trust(USD(1000), alice);
1021  env.trust(USD(1000), bob);
1022  env(pay(gw, alice, USD(100)));
1023  env(pay(gw, bob, USD(50)));
1024  env(offer(alice, XRP(4000), USD(10)));
1025  env.close();
1026 
1027  Json::Value jvParams;
1028  jvParams[jss::taker] = env.master.human();
1029  jvParams[jss::taker_pays][jss::currency] = "XRP";
1030  jvParams[jss::ledger_index] = "validated";
1031  jvParams[jss::taker_gets][jss::currency] = "USD";
1032  jvParams[jss::taker_gets][jss::issuer] = gw.human();
1033 
1034  auto jv = wsc->invoke("book_offers", jvParams);
1035  if (wsc->version() == 2)
1036  {
1037  BEAST_EXPECT(
1038  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
1039  BEAST_EXPECT(
1040  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
1041  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
1042  }
1043  auto jrr = jv[jss::result];
1044 
1045  BEAST_EXPECT(jrr[jss::offers].isArray());
1046  BEAST_EXPECT(jrr[jss::offers].size() == 1);
1047  auto const jrOffer = jrr[jss::offers][0u];
1048  BEAST_EXPECT(jrOffer[sfAccount.fieldName] == alice.human());
1049  BEAST_EXPECT(
1050  jrOffer[sfBookDirectory.fieldName] ==
1051  getBookDir(env, XRP, USD.issue()));
1052  BEAST_EXPECT(jrOffer[sfBookNode.fieldName] == "0");
1053  BEAST_EXPECT(jrOffer[jss::Flags] == 0);
1054  BEAST_EXPECT(jrOffer[sfLedgerEntryType.fieldName] == jss::Offer);
1055  BEAST_EXPECT(jrOffer[sfOwnerNode.fieldName] == "0");
1056  BEAST_EXPECT(jrOffer[sfSequence.fieldName] == 5);
1057  BEAST_EXPECT(
1058  jrOffer[jss::TakerGets] ==
1059  USD(10).value().getJson(JsonOptions::none));
1060  BEAST_EXPECT(
1061  jrOffer[jss::TakerPays] ==
1062  XRP(4000).value().getJson(JsonOptions::none));
1063  BEAST_EXPECT(jrOffer[jss::owner_funds] == "100");
1064  BEAST_EXPECT(jrOffer[jss::quality] == "400000000");
1065 
1066  using namespace std::chrono_literals;
1067  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jval) {
1068  auto const& t = jval[jss::transaction];
1069  return t[jss::TransactionType] == jss::OfferCreate &&
1070  t[jss::TakerGets] ==
1071  USD(10).value().getJson(JsonOptions::none) &&
1072  t[jss::owner_funds] == "100" &&
1073  t[jss::TakerPays] ==
1074  XRP(4000).value().getJson(JsonOptions::none);
1075  }));
1076 
1077  env(offer(bob, XRP(2000), USD(5)));
1078  env.close();
1079 
1080  BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jval) {
1081  auto const& t = jval[jss::transaction];
1082  return t[jss::TransactionType] == jss::OfferCreate &&
1083  t[jss::TakerGets] ==
1084  USD(5).value().getJson(JsonOptions::none) &&
1085  t[jss::owner_funds] == "50" &&
1086  t[jss::TakerPays] ==
1087  XRP(2000).value().getJson(JsonOptions::none);
1088  }));
1089 
1090  jv = wsc->invoke("book_offers", jvParams);
1091  if (wsc->version() == 2)
1092  {
1093  BEAST_EXPECT(
1094  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
1095  BEAST_EXPECT(
1096  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
1097  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
1098  }
1099  jrr = jv[jss::result];
1100 
1101  BEAST_EXPECT(jrr[jss::offers].isArray());
1102  BEAST_EXPECT(jrr[jss::offers].size() == 2);
1103  auto const jrNextOffer = jrr[jss::offers][1u];
1104  BEAST_EXPECT(jrNextOffer[sfAccount.fieldName] == bob.human());
1105  BEAST_EXPECT(
1106  jrNextOffer[sfBookDirectory.fieldName] ==
1107  getBookDir(env, XRP, USD.issue()));
1108  BEAST_EXPECT(jrNextOffer[sfBookNode.fieldName] == "0");
1109  BEAST_EXPECT(jrNextOffer[jss::Flags] == 0);
1110  BEAST_EXPECT(jrNextOffer[sfLedgerEntryType.fieldName] == jss::Offer);
1111  BEAST_EXPECT(jrNextOffer[sfOwnerNode.fieldName] == "0");
1112  BEAST_EXPECT(jrNextOffer[sfSequence.fieldName] == 5);
1113  BEAST_EXPECT(
1114  jrNextOffer[jss::TakerGets] ==
1115  USD(5).value().getJson(JsonOptions::none));
1116  BEAST_EXPECT(
1117  jrNextOffer[jss::TakerPays] ==
1118  XRP(2000).value().getJson(JsonOptions::none));
1119  BEAST_EXPECT(jrNextOffer[jss::owner_funds] == "50");
1120  BEAST_EXPECT(jrNextOffer[jss::quality] == "400000000");
1121 
1122  jv = wsc->invoke("unsubscribe", books);
1123  if (wsc->version() == 2)
1124  {
1125  BEAST_EXPECT(
1126  jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
1127  BEAST_EXPECT(
1128  jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
1129  BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
1130  }
1131  BEAST_EXPECT(jv[jss::status] == "success");
1132  }
1133 
1134  // Check that a stream only sees the given OfferCreate once
1135  static bool
1137  std::unique_ptr<WSClient> const& wsc,
1138  std::chrono::milliseconds const& timeout,
1139  jtx::PrettyAmount const& takerGets,
1140  jtx::PrettyAmount const& takerPays)
1141  {
1142  auto maybeJv = wsc->getMsg(timeout);
1143  // No message
1144  if (!maybeJv)
1145  return false;
1146  // wrong message
1147  if (!(*maybeJv).isMember(jss::transaction))
1148  return false;
1149  auto const& t = (*maybeJv)[jss::transaction];
1150  if (t[jss::TransactionType] != jss::OfferCreate ||
1151  t[jss::TakerGets] != takerGets.value().getJson(JsonOptions::none) ||
1152  t[jss::TakerPays] != takerPays.value().getJson(JsonOptions::none))
1153  return false;
1154  // Make sure no other message is waiting
1155  return wsc->getMsg(timeout) == std::nullopt;
1156  }
1157 
1158  void
1160  {
1161  testcase("Crossing single book offer");
1162 
1163  // This was added to check that an OfferCreate transaction is only
1164  // published once in a stream, even if it updates multiple offer
1165  // ledger entries
1166 
1167  using namespace jtx;
1168  Env env(*this);
1169 
1170  // Scenario is:
1171  // - Alice and Bob place identical offers for USD -> XRP
1172  // - Charlie places a crossing order that takes both Alice and Bob's
1173 
1174  auto const gw = Account("gateway");
1175  auto const alice = Account("alice");
1176  auto const bob = Account("bob");
1177  auto const charlie = Account("charlie");
1178  auto const USD = gw["USD"];
1179 
1180  env.fund(XRP(1000000), gw, alice, bob, charlie);
1181  env.close();
1182 
1183  env(trust(alice, USD(500)));
1184  env(trust(bob, USD(500)));
1185  env.close();
1186 
1187  env(pay(gw, alice, USD(500)));
1188  env(pay(gw, bob, USD(500)));
1189  env.close();
1190 
1191  // Alice and Bob offer $500 for 500 XRP
1192  env(offer(alice, XRP(500), USD(500)));
1193  env(offer(bob, XRP(500), USD(500)));
1194  env.close();
1195 
1196  auto wsc = makeWSClient(env.app().config());
1197  Json::Value books;
1198  {
1199  // RPC subscribe to books stream
1200  books[jss::books] = Json::arrayValue;
1201  {
1202  auto& j = books[jss::books].append(Json::objectValue);
1203  j[jss::snapshot] = false;
1204  j[jss::taker_gets][jss::currency] = "XRP";
1205  j[jss::taker_pays][jss::currency] = "USD";
1206  j[jss::taker_pays][jss::issuer] = gw.human();
1207  }
1208 
1209  auto jv = wsc->invoke("subscribe", books);
1210  if (!BEAST_EXPECT(jv[jss::status] == "success"))
1211  return;
1212  }
1213 
1214  // Charlie places an offer that crosses Alice and Charlie's offers
1215  env(offer(charlie, USD(1000), XRP(1000)));
1216  env.close();
1217  env.require(offers(alice, 0), offers(bob, 0), offers(charlie, 0));
1218  using namespace std::chrono_literals;
1219  BEAST_EXPECT(offerOnlyOnceInStream(wsc, 1s, XRP(1000), USD(1000)));
1220 
1221  // RPC unsubscribe
1222  auto jv = wsc->invoke("unsubscribe", books);
1223  BEAST_EXPECT(jv[jss::status] == "success");
1224  }
1225 
1226  void
1228  {
1229  testcase("Crossing multi-book offer");
1230 
1231  // This was added to check that an OfferCreate transaction is only
1232  // published once in a stream, even if it auto-bridges across several
1233  // books that are under subscription
1234 
1235  using namespace jtx;
1236  Env env(*this);
1237 
1238  // Scenario is:
1239  // - Alice has 1 USD and wants 100 XRP
1240  // - Bob has 100 XRP and wants 1 EUR
1241  // - Charlie has 1 EUR and wants 1 USD and should auto-bridge through
1242  // Alice and Bob
1243 
1244  auto const gw = Account("gateway");
1245  auto const alice = Account("alice");
1246  auto const bob = Account("bob");
1247  auto const charlie = Account("charlie");
1248  auto const USD = gw["USD"];
1249  auto const EUR = gw["EUR"];
1250 
1251  env.fund(XRP(1000000), gw, alice, bob, charlie);
1252  env.close();
1253 
1254  for (auto const& account : {alice, bob, charlie})
1255  {
1256  for (auto const& iou : {USD, EUR})
1257  {
1258  env(trust(account, iou(1)));
1259  }
1260  }
1261  env.close();
1262 
1263  env(pay(gw, alice, USD(1)));
1264  env(pay(gw, charlie, EUR(1)));
1265  env.close();
1266 
1267  env(offer(alice, XRP(100), USD(1)));
1268  env(offer(bob, EUR(1), XRP(100)));
1269  env.close();
1270 
1271  auto wsc = makeWSClient(env.app().config());
1272  Json::Value books;
1273 
1274  {
1275  // RPC subscribe to multiple book streams
1276  books[jss::books] = Json::arrayValue;
1277  {
1278  auto& j = books[jss::books].append(Json::objectValue);
1279  j[jss::snapshot] = false;
1280  j[jss::taker_gets][jss::currency] = "XRP";
1281  j[jss::taker_pays][jss::currency] = "USD";
1282  j[jss::taker_pays][jss::issuer] = gw.human();
1283  }
1284 
1285  {
1286  auto& j = books[jss::books].append(Json::objectValue);
1287  j[jss::snapshot] = false;
1288  j[jss::taker_gets][jss::currency] = "EUR";
1289  j[jss::taker_gets][jss::issuer] = gw.human();
1290  j[jss::taker_pays][jss::currency] = "XRP";
1291  }
1292 
1293  auto jv = wsc->invoke("subscribe", books);
1294  if (!BEAST_EXPECT(jv[jss::status] == "success"))
1295  return;
1296  }
1297 
1298  // Charlies places an on offer for EUR -> USD that should auto-bridge
1299  env(offer(charlie, USD(1), EUR(1)));
1300  env.close();
1301  using namespace std::chrono_literals;
1302  BEAST_EXPECT(offerOnlyOnceInStream(wsc, 1s, EUR(1), USD(1)));
1303 
1304  // RPC unsubscribe
1305  auto jv = wsc->invoke("unsubscribe", books);
1306  BEAST_EXPECT(jv[jss::status] == "success");
1307  }
1308 
1309  void
1311  {
1312  testcase("BookOffersRPC Errors");
1313  using namespace jtx;
1314  Env env(*this);
1315  Account gw{"gw"};
1316  Account alice{"alice"};
1317  env.fund(XRP(10000), alice, gw);
1318  env.close();
1319  auto USD = gw["USD"];
1320 
1321  {
1322  Json::Value jvParams;
1323  jvParams[jss::ledger_index] = 10u;
1324  auto const jrr = env.rpc(
1325  "json", "book_offers", to_string(jvParams))[jss::result];
1326  BEAST_EXPECT(jrr[jss::error] == "lgrNotFound");
1327  BEAST_EXPECT(jrr[jss::error_message] == "ledgerNotFound");
1328  }
1329 
1330  {
1331  Json::Value jvParams;
1332  jvParams[jss::ledger_index] = "validated";
1333  auto const jrr = env.rpc(
1334  "json", "book_offers", to_string(jvParams))[jss::result];
1335  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
1336  BEAST_EXPECT(
1337  jrr[jss::error_message] == "Missing field 'taker_pays'.");
1338  }
1339 
1340  {
1341  Json::Value jvParams;
1342  jvParams[jss::ledger_index] = "validated";
1343  jvParams[jss::taker_pays] = Json::objectValue;
1344  auto const jrr = env.rpc(
1345  "json", "book_offers", to_string(jvParams))[jss::result];
1346  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
1347  BEAST_EXPECT(
1348  jrr[jss::error_message] == "Missing field 'taker_gets'.");
1349  }
1350 
1351  {
1352  Json::Value jvParams;
1353  jvParams[jss::ledger_index] = "validated";
1354  jvParams[jss::taker_pays] = "not an object";
1355  jvParams[jss::taker_gets] = Json::objectValue;
1356  auto const jrr = env.rpc(
1357  "json", "book_offers", to_string(jvParams))[jss::result];
1358  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
1359  BEAST_EXPECT(
1360  jrr[jss::error_message] ==
1361  "Invalid field 'taker_pays', not object.");
1362  }
1363 
1364  {
1365  Json::Value jvParams;
1366  jvParams[jss::ledger_index] = "validated";
1367  jvParams[jss::taker_pays] = Json::objectValue;
1368  jvParams[jss::taker_gets] = "not an object";
1369  auto const jrr = env.rpc(
1370  "json", "book_offers", to_string(jvParams))[jss::result];
1371  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
1372  BEAST_EXPECT(
1373  jrr[jss::error_message] ==
1374  "Invalid field 'taker_gets', not object.");
1375  }
1376 
1377  {
1378  Json::Value jvParams;
1379  jvParams[jss::ledger_index] = "validated";
1380  jvParams[jss::taker_pays] = Json::objectValue;
1381  jvParams[jss::taker_gets] = Json::objectValue;
1382  auto const jrr = env.rpc(
1383  "json", "book_offers", to_string(jvParams))[jss::result];
1384  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
1385  BEAST_EXPECT(
1386  jrr[jss::error_message] ==
1387  "Missing field 'taker_pays.currency'.");
1388  }
1389 
1390  {
1391  Json::Value jvParams;
1392  jvParams[jss::ledger_index] = "validated";
1393  jvParams[jss::taker_pays][jss::currency] = 1;
1394  jvParams[jss::taker_gets] = Json::objectValue;
1395  auto const jrr = env.rpc(
1396  "json", "book_offers", to_string(jvParams))[jss::result];
1397  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
1398  BEAST_EXPECT(
1399  jrr[jss::error_message] ==
1400  "Invalid field 'taker_pays.currency', not string.");
1401  }
1402 
1403  {
1404  Json::Value jvParams;
1405  jvParams[jss::ledger_index] = "validated";
1406  jvParams[jss::taker_pays][jss::currency] = "XRP";
1407  jvParams[jss::taker_gets] = Json::objectValue;
1408  auto const jrr = env.rpc(
1409  "json", "book_offers", to_string(jvParams))[jss::result];
1410  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
1411  BEAST_EXPECT(
1412  jrr[jss::error_message] ==
1413  "Missing field 'taker_gets.currency'.");
1414  }
1415 
1416  {
1417  Json::Value jvParams;
1418  jvParams[jss::ledger_index] = "validated";
1419  jvParams[jss::taker_pays][jss::currency] = "XRP";
1420  jvParams[jss::taker_gets][jss::currency] = 1;
1421  auto const jrr = env.rpc(
1422  "json", "book_offers", to_string(jvParams))[jss::result];
1423  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
1424  BEAST_EXPECT(
1425  jrr[jss::error_message] ==
1426  "Invalid field 'taker_gets.currency', not string.");
1427  }
1428 
1429  {
1430  Json::Value jvParams;
1431  jvParams[jss::ledger_index] = "validated";
1432  jvParams[jss::taker_pays][jss::currency] = "NOT_VALID";
1433  jvParams[jss::taker_gets][jss::currency] = "XRP";
1434  auto const jrr = env.rpc(
1435  "json", "book_offers", to_string(jvParams))[jss::result];
1436  BEAST_EXPECT(jrr[jss::error] == "srcCurMalformed");
1437  BEAST_EXPECT(
1438  jrr[jss::error_message] ==
1439  "Invalid field 'taker_pays.currency', bad currency.");
1440  }
1441 
1442  {
1443  Json::Value jvParams;
1444  jvParams[jss::ledger_index] = "validated";
1445  jvParams[jss::taker_pays][jss::currency] = "XRP";
1446  jvParams[jss::taker_gets][jss::currency] = "NOT_VALID";
1447  auto const jrr = env.rpc(
1448  "json", "book_offers", to_string(jvParams))[jss::result];
1449  BEAST_EXPECT(jrr[jss::error] == "dstAmtMalformed");
1450  BEAST_EXPECT(
1451  jrr[jss::error_message] ==
1452  "Invalid field 'taker_gets.currency', bad currency.");
1453  }
1454 
1455  {
1456  Json::Value jvParams;
1457  jvParams[jss::ledger_index] = "validated";
1458  jvParams[jss::taker_pays][jss::currency] = "XRP";
1459  jvParams[jss::taker_gets][jss::currency] = "USD";
1460  jvParams[jss::taker_gets][jss::issuer] = 1;
1461  auto const jrr = env.rpc(
1462  "json", "book_offers", to_string(jvParams))[jss::result];
1463  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
1464  BEAST_EXPECT(
1465  jrr[jss::error_message] ==
1466  "Invalid field 'taker_gets.issuer', not string.");
1467  }
1468 
1469  {
1470  Json::Value jvParams;
1471  jvParams[jss::ledger_index] = "validated";
1472  jvParams[jss::taker_pays][jss::currency] = "XRP";
1473  jvParams[jss::taker_pays][jss::issuer] = 1;
1474  jvParams[jss::taker_gets][jss::currency] = "USD";
1475  auto const jrr = env.rpc(
1476  "json", "book_offers", to_string(jvParams))[jss::result];
1477  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
1478  BEAST_EXPECT(
1479  jrr[jss::error_message] ==
1480  "Invalid field 'taker_pays.issuer', not string.");
1481  }
1482 
1483  {
1484  Json::Value jvParams;
1485  jvParams[jss::ledger_index] = "validated";
1486  jvParams[jss::taker_pays][jss::currency] = "XRP";
1487  jvParams[jss::taker_pays][jss::issuer] = gw.human() + "DEAD";
1488  jvParams[jss::taker_gets][jss::currency] = "USD";
1489  auto const jrr = env.rpc(
1490  "json", "book_offers", to_string(jvParams))[jss::result];
1491  BEAST_EXPECT(jrr[jss::error] == "srcIsrMalformed");
1492  BEAST_EXPECT(
1493  jrr[jss::error_message] ==
1494  "Invalid field 'taker_pays.issuer', bad issuer.");
1495  }
1496 
1497  {
1498  Json::Value jvParams;
1499  jvParams[jss::ledger_index] = "validated";
1500  jvParams[jss::taker_pays][jss::currency] = "XRP";
1501  jvParams[jss::taker_pays][jss::issuer] = toBase58(noAccount());
1502  jvParams[jss::taker_gets][jss::currency] = "USD";
1503  auto const jrr = env.rpc(
1504  "json", "book_offers", to_string(jvParams))[jss::result];
1505  BEAST_EXPECT(jrr[jss::error] == "srcIsrMalformed");
1506  BEAST_EXPECT(
1507  jrr[jss::error_message] ==
1508  "Invalid field 'taker_pays.issuer', bad issuer account one.");
1509  }
1510 
1511  {
1512  Json::Value jvParams;
1513  jvParams[jss::ledger_index] = "validated";
1514  jvParams[jss::taker_pays][jss::currency] = "XRP";
1515  jvParams[jss::taker_gets][jss::currency] = "USD";
1516  jvParams[jss::taker_gets][jss::issuer] = gw.human() + "DEAD";
1517  auto const jrr = env.rpc(
1518  "json", "book_offers", to_string(jvParams))[jss::result];
1519  BEAST_EXPECT(jrr[jss::error] == "dstIsrMalformed");
1520  BEAST_EXPECT(
1521  jrr[jss::error_message] ==
1522  "Invalid field 'taker_gets.issuer', bad issuer.");
1523  }
1524 
1525  {
1526  Json::Value jvParams;
1527  jvParams[jss::ledger_index] = "validated";
1528  jvParams[jss::taker_pays][jss::currency] = "XRP";
1529  jvParams[jss::taker_gets][jss::currency] = "USD";
1530  jvParams[jss::taker_gets][jss::issuer] = toBase58(noAccount());
1531  auto const jrr = env.rpc(
1532  "json", "book_offers", to_string(jvParams))[jss::result];
1533  BEAST_EXPECT(jrr[jss::error] == "dstIsrMalformed");
1534  BEAST_EXPECT(
1535  jrr[jss::error_message] ==
1536  "Invalid field 'taker_gets.issuer', bad issuer account one.");
1537  }
1538 
1539  {
1540  Json::Value jvParams;
1541  jvParams[jss::ledger_index] = "validated";
1542  jvParams[jss::taker_pays][jss::currency] = "XRP";
1543  jvParams[jss::taker_pays][jss::issuer] = alice.human();
1544  jvParams[jss::taker_gets][jss::currency] = "USD";
1545  jvParams[jss::taker_gets][jss::issuer] = gw.human();
1546  auto const jrr = env.rpc(
1547  "json", "book_offers", to_string(jvParams))[jss::result];
1548  BEAST_EXPECT(jrr[jss::error] == "srcIsrMalformed");
1549  BEAST_EXPECT(
1550  jrr[jss::error_message] ==
1551  "Unneeded field 'taker_pays.issuer' "
1552  "for XRP currency specification.");
1553  }
1554 
1555  {
1556  Json::Value jvParams;
1557  jvParams[jss::ledger_index] = "validated";
1558  jvParams[jss::taker_pays][jss::currency] = "USD";
1559  jvParams[jss::taker_pays][jss::issuer] = toBase58(xrpAccount());
1560  jvParams[jss::taker_gets][jss::currency] = "USD";
1561  jvParams[jss::taker_gets][jss::issuer] = gw.human();
1562  auto const jrr = env.rpc(
1563  "json", "book_offers", to_string(jvParams))[jss::result];
1564  BEAST_EXPECT(jrr[jss::error] == "srcIsrMalformed");
1565  BEAST_EXPECT(
1566  jrr[jss::error_message] ==
1567  "Invalid field 'taker_pays.issuer', expected non-XRP issuer.");
1568  }
1569 
1570  {
1571  Json::Value jvParams;
1572  jvParams[jss::ledger_index] = "validated";
1573  jvParams[jss::taker] = 1;
1574  jvParams[jss::taker_pays][jss::currency] = "XRP";
1575  jvParams[jss::taker_gets][jss::currency] = "USD";
1576  jvParams[jss::taker_gets][jss::issuer] = gw.human();
1577  auto const jrr = env.rpc(
1578  "json", "book_offers", to_string(jvParams))[jss::result];
1579  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
1580  BEAST_EXPECT(
1581  jrr[jss::error_message] ==
1582  "Invalid field 'taker', not string.");
1583  }
1584 
1585  {
1586  Json::Value jvParams;
1587  jvParams[jss::ledger_index] = "validated";
1588  jvParams[jss::taker] = env.master.human() + "DEAD";
1589  jvParams[jss::taker_pays][jss::currency] = "XRP";
1590  jvParams[jss::taker_gets][jss::currency] = "USD";
1591  jvParams[jss::taker_gets][jss::issuer] = gw.human();
1592  auto const jrr = env.rpc(
1593  "json", "book_offers", to_string(jvParams))[jss::result];
1594  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
1595  BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'taker'.");
1596  }
1597 
1598  {
1599  Json::Value jvParams;
1600  jvParams[jss::ledger_index] = "validated";
1601  jvParams[jss::taker] = env.master.human();
1602  jvParams[jss::taker_pays][jss::currency] = "USD";
1603  jvParams[jss::taker_pays][jss::issuer] = gw.human();
1604  jvParams[jss::taker_gets][jss::currency] = "USD";
1605  jvParams[jss::taker_gets][jss::issuer] = gw.human();
1606  auto const jrr = env.rpc(
1607  "json", "book_offers", to_string(jvParams))[jss::result];
1608  BEAST_EXPECT(jrr[jss::error] == "badMarket");
1609  BEAST_EXPECT(jrr[jss::error_message] == "No such market.");
1610  }
1611 
1612  {
1613  Json::Value jvParams;
1614  jvParams[jss::ledger_index] = "validated";
1615  jvParams[jss::taker] = env.master.human();
1616  jvParams[jss::limit] = "0"; // NOT an integer
1617  jvParams[jss::taker_pays][jss::currency] = "XRP";
1618  jvParams[jss::taker_gets][jss::currency] = "USD";
1619  jvParams[jss::taker_gets][jss::issuer] = gw.human();
1620  auto const jrr = env.rpc(
1621  "json", "book_offers", to_string(jvParams))[jss::result];
1622  BEAST_EXPECT(jrr[jss::error] == "invalidParams");
1623  BEAST_EXPECT(
1624  jrr[jss::error_message] ==
1625  "Invalid field 'limit', not unsigned integer.");
1626  }
1627 
1628  {
1629  Json::Value jvParams;
1630  jvParams[jss::ledger_index] = "validated";
1631  jvParams[jss::taker_pays][jss::currency] = "USD";
1632  jvParams[jss::taker_pays][jss::issuer] = gw.human();
1633  jvParams[jss::taker_gets][jss::currency] = "USD";
1634  auto const jrr = env.rpc(
1635  "json", "book_offers", to_string(jvParams))[jss::result];
1636  BEAST_EXPECT(jrr[jss::error] == "dstIsrMalformed");
1637  BEAST_EXPECT(
1638  jrr[jss::error_message] ==
1639  "Invalid field 'taker_gets.issuer', "
1640  "expected non-XRP issuer.");
1641  }
1642 
1643  {
1644  Json::Value jvParams;
1645  jvParams[jss::ledger_index] = "validated";
1646  jvParams[jss::taker_pays][jss::currency] = "USD";
1647  jvParams[jss::taker_pays][jss::issuer] = gw.human();
1648  jvParams[jss::taker_gets][jss::currency] = "XRP";
1649  jvParams[jss::taker_gets][jss::issuer] = gw.human();
1650  auto const jrr = env.rpc(
1651  "json", "book_offers", to_string(jvParams))[jss::result];
1652  BEAST_EXPECT(jrr[jss::error] == "dstIsrMalformed");
1653  BEAST_EXPECT(
1654  jrr[jss::error_message] ==
1655  "Unneeded field 'taker_gets.issuer' "
1656  "for XRP currency specification.");
1657  }
1658  }
1659 
1660  void
1661  testBookOfferLimits(bool asAdmin)
1662  {
1663  testcase("BookOffer Limits");
1664  using namespace jtx;
1665  Env env{*this, asAdmin ? envconfig() : envconfig(no_admin)};
1666  Account gw{"gw"};
1667  env.fund(XRP(200000), gw);
1668  // Note that calls to env.close() fail without admin permission.
1669  if (asAdmin)
1670  env.close();
1671 
1672  auto USD = gw["USD"];
1673 
1674  for (auto i = 0; i <= RPC::Tuning::bookOffers.rmax; i++)
1675  env(offer(gw, XRP(50 + 1 * i), USD(1.0 + 0.1 * i)));
1676 
1677  if (asAdmin)
1678  env.close();
1679 
1680  Json::Value jvParams;
1681  jvParams[jss::limit] = 1;
1682  jvParams[jss::ledger_index] = "validated";
1683  jvParams[jss::taker_pays][jss::currency] = "XRP";
1684  jvParams[jss::taker_gets][jss::currency] = "USD";
1685  jvParams[jss::taker_gets][jss::issuer] = gw.human();
1686  auto jrr =
1687  env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
1688  BEAST_EXPECT(jrr[jss::offers].isArray());
1689  BEAST_EXPECT(jrr[jss::offers].size() == (asAdmin ? 1u : 0u));
1690  // NOTE - a marker field is not returned for this method
1691 
1692  jvParams[jss::limit] = 0u;
1693  jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
1694  BEAST_EXPECT(jrr[jss::offers].isArray());
1695  BEAST_EXPECT(jrr[jss::offers].size() == 0u);
1696 
1697  jvParams[jss::limit] = RPC::Tuning::bookOffers.rmax + 1;
1698  jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
1699  BEAST_EXPECT(jrr[jss::offers].isArray());
1700  BEAST_EXPECT(
1701  jrr[jss::offers].size() ==
1702  (asAdmin ? RPC::Tuning::bookOffers.rmax + 1 : 0u));
1703 
1704  jvParams[jss::limit] = Json::nullValue;
1705  jrr = env.rpc("json", "book_offers", to_string(jvParams))[jss::result];
1706  BEAST_EXPECT(jrr[jss::offers].isArray());
1707  BEAST_EXPECT(
1708  jrr[jss::offers].size() ==
1709  (asAdmin ? RPC::Tuning::bookOffers.rdefault : 0u));
1710  }
1711 
1712  void
1713  run() override
1714  {
1723  testTrackOffers();
1727  testBookOfferLimits(true);
1728  testBookOfferLimits(false);
1729  }
1730 };
1731 
1733 
1734 } // namespace test
1735 } // namespace ripple
ripple::test::Book_test
Definition: Book_test.cpp:28
ripple::test::Book_test::offerOnlyOnceInStream
static bool offerOnlyOnceInStream(std::unique_ptr< WSClient > const &wsc, std::chrono::milliseconds const &timeout, jtx::PrettyAmount const &takerGets, jtx::PrettyAmount const &takerPays)
Definition: Book_test.cpp:1136
ripple::test::jtx::XRP
const XRP_t XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
ripple::Issue
A currency issued by an account.
Definition: Issue.h:34
std::string
STL class.
ripple::sfOwnerNode
const SF_UINT64 sfOwnerNode
ripple::test::jtx::owners
Match the number of items in the account's owner directory.
Definition: owners.h:69
ripple::test::jtx::Env::require
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:466
ripple::test::jtx::Env::closed
std::shared_ptr< ReadView const > closed()
Returns the last closed ledger.
Definition: Env.cpp:115
Json::arrayValue
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
ripple::sfSequence
const SF_UINT32 sfSequence
ripple::getBookBase
uint256 getBookBase(Book const &book)
Definition: Indexes.cpp:82
ripple::sfBookDirectory
const SF_UINT256 sfBookDirectory
ripple::test::Book_test::testMultipleBooksBothSidesOffersInBook
void testMultipleBooksBothSidesOffersInBook()
Definition: Book_test.cpp:804
ripple::test::jtx::trust
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition: trust.cpp:30
ripple::SField::fieldName
const std::string fieldName
Definition: SField.h:132
ripple::test::Book_test::testMultipleBooksOneSideEmptyBook
void testMultipleBooksOneSideEmptyBook()
Definition: Book_test.cpp:421
ripple::STAmount::getJson
Json::Value getJson(JsonOptions) const override
Definition: STAmount.cpp:655
std::chrono::milliseconds
ripple::keylet::offer
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition: Indexes.cpp:222
ripple::test::jtx::require
Check a set of conditions.
Definition: require.h:63
ripple::toBase58
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:104
ripple::test::Book_test::testBothSidesEmptyBook
void testBothSidesEmptyBook()
Definition: Book_test.cpp:222
ripple::test::jtx::Account::human
std::string const & human() const
Returns the human readable public key.
Definition: Account.h:113
ripple::test::Book_test::getBookDir
std::string getBookDir(jtx::Env &env, Issue const &in, Issue const &out)
Definition: Book_test.cpp:31
ripple::QualityDirection::in
@ in
ripple::test::jtx::Env::app
Application & app()
Definition: Env.h:241
ripple::getQualityNext
uint256 getQualityNext(uint256 const &uBase)
Definition: Indexes.cpp:100
ripple::test::jtx::envconfig
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:49
ripple::RPC::Tuning::bookOffers
static constexpr LimitRange bookOffers
Limits for the book_offers command.
Definition: rpc/impl/Tuning.h:49
ripple::test::Book_test::testCrossingSingleBookOffer
void testCrossingSingleBookOffer()
Definition: Book_test.cpp:1159
ripple::test::Book_test::testOneSideOffersInBook
void testOneSideOffersInBook()
Definition: Book_test.cpp:131
ripple::test::jtx::Env::trust
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:259
ripple::test::BEAST_DEFINE_TESTSUITE_PRIO
BEAST_DEFINE_TESTSUITE_PRIO(AccountDelete, app, ripple, 2)
ripple::base_uint< 256 >
ripple::test::Book_test::testBookOfferErrors
void testBookOfferErrors()
Definition: Book_test.cpp:1310
ripple::test::jtx::no_admin
std::unique_ptr< Config > no_admin(std::unique_ptr< Config >)
adjust config so no admin ports are enabled
Definition: envconfig.cpp:79
Json::Value::append
Value & append(const Value &value)
Append value to array at the end.
Definition: json_value.cpp:882
ripple::QualityDirection::out
@ out
Json::objectValue
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:43
ripple::test::Book_test::testCrossingMultiBookOffer
void testCrossingMultiBookOffer()
Definition: Book_test.cpp:1227
ripple::test::Book_test::testMultipleBooksBothSidesEmptyBook
void testMultipleBooksBothSidesEmptyBook()
Definition: Book_test.cpp:670
ripple::JsonOptions::none
@ none
ripple::Application::config
virtual Config & config()=0
ripple::keylet::page
Keylet page(uint256 const &key, std::uint64_t index) noexcept
A page in a directory.
Definition: Indexes.cpp:309
ripple::sfBookNode
const SF_UINT64 sfBookNode
ripple::test::jtx::Env::close
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:121
ripple::xrpAccount
AccountID const & xrpAccount()
Compute AccountID from public key.
Definition: AccountID.cpp:168
ripple::test::Book_test::testBookOfferLimits
void testBookOfferLimits(bool asAdmin)
Definition: Book_test.cpp:1661
ripple::RPC::Tuning::LimitRange::rmax
unsigned int rmax
Definition: rpc/impl/Tuning.h:33
ripple::test::Book_test::testOneSideEmptyBook
void testOneSideEmptyBook()
Definition: Book_test.cpp:53
ripple::getJson
Json::Value getJson(LedgerFill const &fill)
Return a new Json::Value representing the ledger with given options.
Definition: LedgerToJson.cpp:296
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::sfLedgerEntryType
const SF_UINT16 sfLedgerEntryType
ripple::test::Book_test::testMultipleBooksOneSideOffersInBook
void testMultipleBooksOneSideOffersInBook()
Definition: Book_test.cpp:533
ripple::test::jtx::pay
Json::Value pay(Account const &account, Account const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:29
ripple::test::jtx::Env::fund
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:228
ripple::test::jtx::Env::master
Account const & master
Definition: Env.h:121
Json::nullValue
@ nullValue
'null' value
Definition: json_value.h:36
ripple::test::makeWSClient
std::unique_ptr< WSClient > makeWSClient(Config const &cfg, bool v2, unsigned rpc_version, std::unordered_map< std::string, std::string > const &headers)
Returns a client operating through WebSockets/S.
Definition: WSClient.cpp:300
ripple::test::Book_test::testTrackOffers
void testTrackOffers()
Definition: Book_test.cpp:976
ripple::test::jtx::PrettyAmount::value
STAmount const & value() const
Definition: amount.h:124
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:41
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::Book
Specifies an order book.
Definition: Book.h:33
ripple::sfAccount
const SF_ACCOUNT sfAccount
ripple::test::Book_test::run
void run() override
Definition: Book_test.cpp:1713
std::unique_ptr
STL class.
ripple::cdirFirst
bool cdirFirst(ReadView const &view, uint256 const &root, std::shared_ptr< SLE const > &page, unsigned int &index, uint256 &entry)
Returns the first entry in the directory, advancing the index.
Definition: View.cpp:134
ripple::test::Book_test::testBothSidesOffersInBook
void testBothSidesOffersInBook()
Definition: Book_test.cpp:312
ripple::noAccount
AccountID const & noAccount()
A placeholder for empty accounts.
Definition: AccountID.cpp:175
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:116
ripple::test::jtx::Env::rpc
Json::Value rpc(std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition: Env.h:687
Json::Value
Represents a JSON value.
Definition: json_value.h:145
ripple::test::jtx::PrettyAmount
Represents an XRP or IOU quantity This customizes the string conversion and supports XRP conversions ...
Definition: amount.h:73
ripple::test::jtx::owner_count
Definition: owners.h:49
ripple::test::jtx::rate
Json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
Definition: rate.cpp:30