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