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