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