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