rippled
Loading...
Searching...
No Matches
Path_test.cpp
1#include <test/jtx.h>
2#include <test/jtx/AMM.h>
3#include <test/jtx/AMMTest.h>
4#include <test/jtx/envconfig.h>
5#include <test/jtx/permissioned_dex.h>
6
7#include <xrpld/core/JobQueue.h>
8#include <xrpld/rpc/RPCHandler.h>
9#include <xrpld/rpc/detail/Tuning.h>
10
11#include <xrpl/beast/unit_test.h>
12#include <xrpl/json/json_reader.h>
13#include <xrpl/protocol/ApiVersion.h>
14#include <xrpl/protocol/STParsedJSON.h>
15#include <xrpl/protocol/TxFlags.h>
16#include <xrpl/protocol/jss.h>
17#include <xrpl/resource/Fees.h>
18
19#include <chrono>
20#include <condition_variable>
21#include <mutex>
22#include <optional>
23#include <string>
24#include <thread>
25
26namespace ripple {
27namespace test {
28
29//------------------------------------------------------------------------------
30
32rpf(jtx::Account const& src, jtx::Account const& dst, std::uint32_t num_src)
33{
35 jv[jss::command] = "ripple_path_find";
36 jv[jss::source_account] = toBase58(src);
37
38 if (num_src > 0)
39 {
40 auto& sc = (jv[jss::source_currencies] = Json::arrayValue);
42 while (num_src--)
43 {
44 j[jss::currency] = std::to_string(num_src + 100);
45 sc.append(j);
46 }
47 }
48
49 auto const d = toBase58(dst);
50 jv[jss::destination_account] = d;
51
52 Json::Value& j = (jv[jss::destination_amount] = Json::objectValue);
53 j[jss::currency] = "USD";
54 j[jss::value] = "0.01";
55 j[jss::issuer] = d;
56
57 return jv;
58}
59
60//------------------------------------------------------------------------------
61
63{
66 {
67 // These tests were originally written with search parameters that are
68 // different from the current defaults. This function creates an env
69 // with the search parameters that the tests were written for.
70 using namespace jtx;
71 return Env(*this, envconfig([](std::unique_ptr<Config> cfg) {
72 cfg->PATH_SEARCH_OLD = 7;
73 cfg->PATH_SEARCH = 7;
74 cfg->PATH_SEARCH_MAX = 10;
75 return cfg;
76 }));
77 }
78
79public:
80 class gate
81 {
82 private:
85 bool signaled_ = false;
86
87 public:
88 // Thread safe, blocks until signaled or period expires.
89 // Returns `true` if signaled.
90 template <class Rep, class Period>
91 bool
93 {
95 auto b = cv_.wait_for(lk, rel_time, [this] { return signaled_; });
96 signaled_ = false;
97 return b;
98 }
99
100 void
102 {
104 signaled_ = true;
105 cv_.notify_all();
106 }
107 };
108
109 auto
111 jtx::Env& env,
112 jtx::Account const& src,
113 jtx::Account const& dst,
114 STAmount const& saDstAmount,
115 std::optional<STAmount> const& saSendMax = std::nullopt,
116 std::optional<Currency> const& saSrcCurrency = std::nullopt,
118 {
119 using namespace jtx;
120
121 auto& app = env.app();
124
125 RPC::JsonContext context{
126 {env.journal,
127 app,
128 loadType,
129 app.getOPs(),
130 app.getLedgerMaster(),
131 c,
133 {},
134 {},
136 {},
137 {}};
138
140 params[jss::command] = "ripple_path_find";
141 params[jss::source_account] = toBase58(src);
142 params[jss::destination_account] = toBase58(dst);
143 params[jss::destination_amount] =
144 saDstAmount.getJson(JsonOptions::none);
145 if (saSendMax)
146 params[jss::send_max] = saSendMax->getJson(JsonOptions::none);
147 if (saSrcCurrency)
148 {
149 auto& sc = params[jss::source_currencies] = Json::arrayValue;
151 j[jss::currency] = to_string(saSrcCurrency.value());
152 sc.append(j);
153 }
154 if (domain)
155 params[jss::domain] = to_string(*domain);
156
157 Json::Value result;
158 gate g;
159 app.getJobQueue().postCoro(
160 jtCLIENT, "RPC-Client", [&](auto const& coro) {
161 context.params = std::move(params);
162 context.coro = coro;
163 RPC::doCommand(context, result);
164 g.signal();
165 });
166
167 using namespace std::chrono_literals;
168 BEAST_EXPECT(g.wait_for(5s));
169 BEAST_EXPECT(!result.isMember(jss::error));
170 return result;
171 }
172
175 jtx::Env& env,
176 jtx::Account const& src,
177 jtx::Account const& dst,
178 STAmount const& saDstAmount,
179 std::optional<STAmount> const& saSendMax = std::nullopt,
180 std::optional<Currency> const& saSrcCurrency = std::nullopt,
182 {
184 env, src, dst, saDstAmount, saSendMax, saSrcCurrency, domain);
185 BEAST_EXPECT(!result.isMember(jss::error));
186
187 STAmount da;
188 if (result.isMember(jss::destination_amount))
189 da = amountFromJson(sfGeneric, result[jss::destination_amount]);
190
191 STAmount sa;
193 if (result.isMember(jss::alternatives))
194 {
195 auto const& alts = result[jss::alternatives];
196 if (alts.size() > 0)
197 {
198 auto const& path = alts[0u];
199
200 if (path.isMember(jss::source_amount))
201 sa = amountFromJson(sfGeneric, path[jss::source_amount]);
202
203 if (path.isMember(jss::destination_amount))
204 da = amountFromJson(
205 sfGeneric, path[jss::destination_amount]);
206
207 if (path.isMember(jss::paths_computed))
208 {
209 Json::Value p;
210 p["Paths"] = path[jss::paths_computed];
211 STParsedJSONObject po("generic", p);
212 paths = po.object->getFieldPathSet(sfPaths);
213 }
214 }
215 }
216
217 return std::make_tuple(std::move(paths), std::move(sa), std::move(da));
218 }
219
220 void
222 {
223 testcase("source currency limits");
224 using namespace std::chrono_literals;
225 using namespace jtx;
226 Env env = pathTestEnv();
227 auto const gw = Account("gateway");
228 env.fund(XRP(10000), "alice", "bob", gw);
229 env.close();
230 env.trust(gw["USD"](100), "alice", "bob");
231 env.close();
232
233 auto& app = env.app();
236
237 RPC::JsonContext context{
238 {env.journal,
239 app,
240 loadType,
241 app.getOPs(),
242 app.getLedgerMaster(),
243 c,
245 {},
246 {},
248 {},
249 {}};
250 Json::Value result;
251 gate g;
252 // Test RPC::Tuning::max_src_cur source currencies.
253 app.getJobQueue().postCoro(
254 jtCLIENT, "RPC-Client", [&](auto const& coro) {
255 context.params = rpf(
256 Account("alice"), Account("bob"), RPC::Tuning::max_src_cur);
257 context.coro = coro;
258 RPC::doCommand(context, result);
259 g.signal();
260 });
261 BEAST_EXPECT(g.wait_for(5s));
262 BEAST_EXPECT(!result.isMember(jss::error));
263
264 // Test more than RPC::Tuning::max_src_cur source currencies.
265 app.getJobQueue().postCoro(
266 jtCLIENT, "RPC-Client", [&](auto const& coro) {
267 context.params =
268 rpf(Account("alice"),
269 Account("bob"),
271 context.coro = coro;
272 RPC::doCommand(context, result);
273 g.signal();
274 });
275 BEAST_EXPECT(g.wait_for(5s));
276 BEAST_EXPECT(result.isMember(jss::error));
277
278 // Test RPC::Tuning::max_auto_src_cur source currencies.
279 for (auto i = 0; i < (RPC::Tuning::max_auto_src_cur - 1); ++i)
280 env.trust(Account("alice")[std::to_string(i + 100)](100), "bob");
281 app.getJobQueue().postCoro(
282 jtCLIENT, "RPC-Client", [&](auto const& coro) {
283 context.params = rpf(Account("alice"), Account("bob"), 0);
284 context.coro = coro;
285 RPC::doCommand(context, result);
286 g.signal();
287 });
288 BEAST_EXPECT(g.wait_for(5s));
289 BEAST_EXPECT(!result.isMember(jss::error));
290
291 // Test more than RPC::Tuning::max_auto_src_cur source currencies.
292 env.trust(Account("alice")["AUD"](100), "bob");
293 app.getJobQueue().postCoro(
294 jtCLIENT, "RPC-Client", [&](auto const& coro) {
295 context.params = rpf(Account("alice"), Account("bob"), 0);
296 context.coro = coro;
297 RPC::doCommand(context, result);
298 g.signal();
299 });
300 BEAST_EXPECT(g.wait_for(5s));
301 BEAST_EXPECT(result.isMember(jss::error));
302 }
303
304 void
306 {
307 testcase("no direct path no intermediary no alternatives");
308 using namespace jtx;
309 Env env = pathTestEnv();
310 env.fund(XRP(10000), "alice", "bob");
311 env.close();
312
313 auto const result =
314 find_paths(env, "alice", "bob", Account("bob")["USD"](5));
315 BEAST_EXPECT(std::get<0>(result).empty());
316 }
317
318 void
320 {
321 testcase("direct path no intermediary");
322 using namespace jtx;
323 Env env = pathTestEnv();
324 env.fund(XRP(10000), "alice", "bob");
325 env.close();
326 env.trust(Account("alice")["USD"](700), "bob");
327
328 STPathSet st;
329 STAmount sa;
330 std::tie(st, sa, std::ignore) =
331 find_paths(env, "alice", "bob", Account("bob")["USD"](5));
332 BEAST_EXPECT(st.empty());
333 BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
334 }
335
336 void
338 {
339 testcase("payment auto path find");
340 using namespace jtx;
341 Env env = pathTestEnv();
342 auto const gw = Account("gateway");
343 auto const USD = gw["USD"];
344 env.fund(XRP(10000), "alice", "bob", gw);
345 env.close();
346 env.trust(USD(600), "alice");
347 env.trust(USD(700), "bob");
348 env(pay(gw, "alice", USD(70)));
349 env(pay("alice", "bob", USD(24)));
350 env.require(balance("alice", USD(46)));
351 env.require(balance(gw, Account("alice")["USD"](-46)));
352 env.require(balance("bob", USD(24)));
353 env.require(balance(gw, Account("bob")["USD"](-24)));
354 }
355
356 void
357 path_find(bool const domainEnabled)
358 {
359 testcase(
360 std::string("path find") + (domainEnabled ? " w/ " : " w/o ") +
361 "domain");
362 using namespace jtx;
363 Env env = pathTestEnv();
364 auto const gw = Account("gateway");
365 auto const USD = gw["USD"];
366 env.fund(XRP(10000), "alice", "bob", gw);
367 env.close();
368 env.trust(USD(600), "alice");
369 env.trust(USD(700), "bob");
370 env(pay(gw, "alice", USD(70)));
371 env(pay(gw, "bob", USD(50)));
372
373 std::optional<uint256> domainID;
374 if (domainEnabled)
375 domainID = setupDomain(env, {"alice", "bob", gw});
376
377 STPathSet st;
378 STAmount sa;
379 std::tie(st, sa, std::ignore) = find_paths(
380 env,
381 "alice",
382 "bob",
383 Account("bob")["USD"](5),
386 domainID);
387 BEAST_EXPECT(same(st, stpath("gateway")));
388 BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
389 }
390
391 void
392 xrp_to_xrp(bool const domainEnabled)
393 {
394 using namespace jtx;
395 testcase(
396 std::string("XRP to XRP") + (domainEnabled ? " w/ " : " w/o ") +
397 "domain");
398 Env env = pathTestEnv();
399 env.fund(XRP(10000), "alice", "bob");
400 env.close();
401
402 std::optional<uint256> domainID;
403 if (domainEnabled)
404 domainID = setupDomain(env, {"alice", "bob"});
405
406 auto const result = find_paths(
407 env, "alice", "bob", XRP(5), std::nullopt, std::nullopt, domainID);
408 BEAST_EXPECT(std::get<0>(result).empty());
409 }
410
411 void
412 path_find_consume_all(bool const domainEnabled)
413 {
414 testcase(
415 std::string("path find consume all") +
416 (domainEnabled ? " w/ " : " w/o ") + "domain");
417 using namespace jtx;
418
419 {
420 Env env = pathTestEnv();
421 env.fund(XRP(10000), "alice", "bob", "carol", "dan", "edward");
422 env.close();
423 env.trust(Account("alice")["USD"](10), "bob");
424 env.trust(Account("bob")["USD"](10), "carol");
425 env.trust(Account("carol")["USD"](10), "edward");
426 env.trust(Account("alice")["USD"](100), "dan");
427 env.trust(Account("dan")["USD"](100), "edward");
428
429 std::optional<uint256> domainID;
430 if (domainEnabled)
431 domainID = setupDomain(
432 env, {"alice", "bob", "carol", "dan", "edward"});
433
434 STPathSet st;
435 STAmount sa;
436 STAmount da;
437 std::tie(st, sa, da) = find_paths(
438 env,
439 "alice",
440 "edward",
441 Account("edward")["USD"](-1),
444 domainID);
445 BEAST_EXPECT(same(st, stpath("dan"), stpath("bob", "carol")));
446 BEAST_EXPECT(equal(sa, Account("alice")["USD"](110)));
447 BEAST_EXPECT(equal(da, Account("edward")["USD"](110)));
448 }
449
450 {
451 Env env = pathTestEnv();
452 auto const gw = Account("gateway");
453 auto const USD = gw["USD"];
454 env.fund(XRP(10000), "alice", "bob", "carol", gw);
455 env.close();
456 env.trust(USD(100), "bob", "carol");
457 env.close();
458 env(pay(gw, "carol", USD(100)));
459 env.close();
460
461 std::optional<uint256> domainID;
462 if (domainEnabled)
463 {
464 domainID =
465 setupDomain(env, {"alice", "bob", "carol", "gateway"});
466 env(offer("carol", XRP(100), USD(100)), domain(*domainID));
467 }
468 else
469 {
470 env(offer("carol", XRP(100), USD(100)));
471 }
472 env.close();
473
474 STPathSet st;
475 STAmount sa;
476 STAmount da;
477 std::tie(st, sa, da) = find_paths(
478 env,
479 "alice",
480 "bob",
481 Account("bob")["AUD"](-1),
484 domainID);
485 BEAST_EXPECT(st.empty());
486 std::tie(st, sa, da) = find_paths(
487 env,
488 "alice",
489 "bob",
490 Account("bob")["USD"](-1),
493 domainID);
494 BEAST_EXPECT(sa == XRP(100));
495 BEAST_EXPECT(equal(da, Account("bob")["USD"](100)));
496
497 // if domain is used, finding path in the open offerbook will return
498 // empty result
499 if (domainEnabled)
500 {
501 std::tie(st, sa, da) = find_paths(
502 env,
503 "alice",
504 "bob",
505 Account("bob")["USD"](-1),
508 std::nullopt); // not specifying a domain
509 BEAST_EXPECT(st.empty());
510 }
511 }
512 }
513
514 void
515 alternative_path_consume_both(bool const domainEnabled)
516 {
517 testcase(
518 std::string("alternative path consume both") +
519 (domainEnabled ? " w/ " : " w/o ") + "domain");
520 using namespace jtx;
521 Env env = pathTestEnv();
522 auto const gw = Account("gateway");
523 auto const USD = gw["USD"];
524 auto const gw2 = Account("gateway2");
525 auto const gw2_USD = gw2["USD"];
526 env.fund(XRP(10000), "alice", "bob", gw, gw2);
527 env.close();
528 env.trust(USD(600), "alice");
529 env.trust(gw2_USD(800), "alice");
530 env.trust(USD(700), "bob");
531 env.trust(gw2_USD(900), "bob");
532
533 std::optional<uint256> domainID;
534 if (domainEnabled)
535 {
536 domainID =
537 setupDomain(env, {"alice", "bob", "gateway", "gateway2"});
538 env(pay(gw, "alice", USD(70)), domain(*domainID));
539 env(pay(gw2, "alice", gw2_USD(70)), domain(*domainID));
540 env(pay("alice", "bob", Account("bob")["USD"](140)),
541 paths(Account("alice")["USD"]),
542 domain(*domainID));
543 }
544 else
545 {
546 env(pay(gw, "alice", USD(70)));
547 env(pay(gw2, "alice", gw2_USD(70)));
548 env(pay("alice", "bob", Account("bob")["USD"](140)),
549 paths(Account("alice")["USD"]));
550 }
551
552 env.require(balance("alice", USD(0)));
553 env.require(balance("alice", gw2_USD(0)));
554 env.require(balance("bob", USD(70)));
555 env.require(balance("bob", gw2_USD(70)));
556 env.require(balance(gw, Account("alice")["USD"](0)));
557 env.require(balance(gw, Account("bob")["USD"](-70)));
558 env.require(balance(gw2, Account("alice")["USD"](0)));
559 env.require(balance(gw2, Account("bob")["USD"](-70)));
560 }
561
562 void
564 {
565 testcase(
566 std::string("alternative paths consume best transfer") +
567 (domainEnabled ? " w/ " : " w/o ") + "domain");
568 using namespace jtx;
569 Env env = pathTestEnv();
570 auto const gw = Account("gateway");
571 auto const USD = gw["USD"];
572 auto const gw2 = Account("gateway2");
573 auto const gw2_USD = gw2["USD"];
574 env.fund(XRP(10000), "alice", "bob", gw, gw2);
575 env.close();
576 env(rate(gw2, 1.1));
577 env.trust(USD(600), "alice");
578 env.trust(gw2_USD(800), "alice");
579 env.trust(USD(700), "bob");
580 env.trust(gw2_USD(900), "bob");
581
582 std::optional<uint256> domainID;
583 if (domainEnabled)
584 {
585 domainID =
586 setupDomain(env, {"alice", "bob", "gateway", "gateway2"});
587 env(pay(gw, "alice", USD(70)), domain(*domainID));
588 env(pay(gw2, "alice", gw2_USD(70)), domain(*domainID));
589 env(pay("alice", "bob", USD(70)), domain(*domainID));
590 }
591 else
592 {
593 env(pay(gw, "alice", USD(70)));
594 env(pay(gw2, "alice", gw2_USD(70)));
595 env(pay("alice", "bob", USD(70)));
596 }
597 env.require(balance("alice", USD(0)));
598 env.require(balance("alice", gw2_USD(70)));
599 env.require(balance("bob", USD(70)));
600 env.require(balance("bob", gw2_USD(0)));
601 env.require(balance(gw, Account("alice")["USD"](0)));
602 env.require(balance(gw, Account("bob")["USD"](-70)));
603 env.require(balance(gw2, Account("alice")["USD"](-70)));
604 env.require(balance(gw2, Account("bob")["USD"](0)));
605 }
606
607 void
609 {
610 testcase("alternative paths - consume best transfer first");
611 using namespace jtx;
612 Env env = pathTestEnv();
613 auto const gw = Account("gateway");
614 auto const USD = gw["USD"];
615 auto const gw2 = Account("gateway2");
616 auto const gw2_USD = gw2["USD"];
617 env.fund(XRP(10000), "alice", "bob", gw, gw2);
618 env.close();
619 env(rate(gw2, 1.1));
620 env.trust(USD(600), "alice");
621 env.trust(gw2_USD(800), "alice");
622 env.trust(USD(700), "bob");
623 env.trust(gw2_USD(900), "bob");
624 env(pay(gw, "alice", USD(70)));
625 env(pay(gw2, "alice", gw2_USD(70)));
626 env(pay("alice", "bob", Account("bob")["USD"](77)),
627 sendmax(Account("alice")["USD"](100)),
628 paths(Account("alice")["USD"]));
629 env.require(balance("alice", USD(0)));
630 env.require(balance("alice", gw2_USD(62.3)));
631 env.require(balance("bob", USD(70)));
632 env.require(balance("bob", gw2_USD(7)));
633 env.require(balance(gw, Account("alice")["USD"](0)));
634 env.require(balance(gw, Account("bob")["USD"](-70)));
635 env.require(balance(gw2, Account("alice")["USD"](-62.3)));
636 env.require(balance(gw2, Account("bob")["USD"](-7)));
637 }
638
639 void
641 bool const domainEnabled)
642 {
643 testcase(
645 "alternative paths - limit returned paths to best quality") +
646 (domainEnabled ? " w/ " : " w/o ") + "domain");
647 using namespace jtx;
648 Env env = pathTestEnv();
649 auto const gw = Account("gateway");
650 auto const USD = gw["USD"];
651 auto const gw2 = Account("gateway2");
652 auto const gw2_USD = gw2["USD"];
653 env.fund(XRP(10000), "alice", "bob", "carol", "dan", gw, gw2);
654 env.close();
655 env(rate("carol", 1.1));
656 env.trust(Account("carol")["USD"](800), "alice", "bob");
657 env.trust(Account("dan")["USD"](800), "alice", "bob");
658 env.trust(USD(800), "alice", "bob");
659 env.trust(gw2_USD(800), "alice", "bob");
660 env.trust(Account("alice")["USD"](800), "dan");
661 env.trust(Account("bob")["USD"](800), "dan");
662 env.close();
663 env(pay(gw2, "alice", gw2_USD(100)));
664 env.close();
665 env(pay("carol", "alice", Account("carol")["USD"](100)));
666 env.close();
667 env(pay(gw, "alice", USD(100)));
668 env.close();
669
670 std::optional<uint256> domainID;
671 if (domainEnabled)
672 {
673 domainID =
674 setupDomain(env, {"alice", "bob", "carol", "dan", gw, gw2});
675 }
676
677 STPathSet st;
678 STAmount sa;
679 std::tie(st, sa, std::ignore) = find_paths(
680 env,
681 "alice",
682 "bob",
683 Account("bob")["USD"](5),
686 domainID);
687 BEAST_EXPECT(same(
688 st,
689 stpath("gateway"),
690 stpath("gateway2"),
691 stpath("dan"),
692 stpath("carol")));
693 BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
694 }
695
696 void
697 issues_path_negative_issue(bool const domainEnabled)
698 {
699 testcase(
700 std::string("path negative: Issue #5") +
701 (domainEnabled ? " w/ " : " w/o ") + "domain");
702 using namespace jtx;
703 Env env = pathTestEnv();
704 env.fund(XRP(10000), "alice", "bob", "carol", "dan");
705 env.close();
706 env.trust(Account("bob")["USD"](100), "alice", "carol", "dan");
707 env.trust(Account("alice")["USD"](100), "dan");
708 env.trust(Account("carol")["USD"](100), "dan");
709 env(pay("bob", "carol", Account("bob")["USD"](75)));
710 env.require(balance("bob", Account("carol")["USD"](-75)));
711 env.require(balance("carol", Account("bob")["USD"](75)));
712 env.close();
713
714 std::optional<uint256> domainID;
715 if (domainEnabled)
716 {
717 domainID = setupDomain(env, {"alice", "bob", "carol", "dan"});
718 }
719
720 auto result = find_paths(
721 env,
722 "alice",
723 "bob",
724 Account("bob")["USD"](25),
727 domainID);
728 BEAST_EXPECT(std::get<0>(result).empty());
729
730 env(pay("alice", "bob", Account("alice")["USD"](25)), ter(tecPATH_DRY));
731 env.close();
732
733 result = find_paths(
734 env,
735 "alice",
736 "bob",
737 Account("alice")["USD"](25),
740 domainID);
741 BEAST_EXPECT(std::get<0>(result).empty());
742
743 env.require(balance("alice", Account("bob")["USD"](0)));
744 env.require(balance("alice", Account("dan")["USD"](0)));
745 env.require(balance("bob", Account("alice")["USD"](0)));
746 env.require(balance("bob", Account("carol")["USD"](-75)));
747 env.require(balance("bob", Account("dan")["USD"](0)));
748 env.require(balance("carol", Account("bob")["USD"](75)));
749 env.require(balance("carol", Account("dan")["USD"](0)));
750 env.require(balance("dan", Account("alice")["USD"](0)));
751 env.require(balance("dan", Account("bob")["USD"](0)));
752 env.require(balance("dan", Account("carol")["USD"](0)));
753 }
754
755 // alice -- limit 40 --> bob
756 // alice --> carol --> dan --> bob
757 // Balance of 100 USD Bob - Balance of 37 USD -> Rod
758 void
760 {
761 testcase("path negative: ripple-client issue #23: smaller");
762 using namespace jtx;
763 Env env = pathTestEnv();
764 env.fund(XRP(10000), "alice", "bob", "carol", "dan");
765 env.close();
766 env.trust(Account("alice")["USD"](40), "bob");
767 env.trust(Account("dan")["USD"](20), "bob");
768 env.trust(Account("alice")["USD"](20), "carol");
769 env.trust(Account("carol")["USD"](20), "dan");
770 env(pay("alice", "bob", Account("bob")["USD"](55)),
771 paths(Account("alice")["USD"]));
772 env.require(balance("bob", Account("alice")["USD"](40)));
773 env.require(balance("bob", Account("dan")["USD"](15)));
774 }
775
776 // alice -120 USD-> edward -25 USD-> bob
777 // alice -25 USD-> carol -75 USD -> dan -100 USD-> bob
778 void
780 {
781 testcase("path negative: ripple-client issue #23: larger");
782 using namespace jtx;
783 Env env = pathTestEnv();
784 env.fund(XRP(10000), "alice", "bob", "carol", "dan", "edward");
785 env.close();
786 env.trust(Account("alice")["USD"](120), "edward");
787 env.trust(Account("edward")["USD"](25), "bob");
788 env.trust(Account("dan")["USD"](100), "bob");
789 env.trust(Account("alice")["USD"](25), "carol");
790 env.trust(Account("carol")["USD"](75), "dan");
791 env(pay("alice", "bob", Account("bob")["USD"](50)),
792 paths(Account("alice")["USD"]));
793 env.require(balance("alice", Account("edward")["USD"](-25)));
794 env.require(balance("alice", Account("carol")["USD"](-25)));
795 env.require(balance("bob", Account("edward")["USD"](25)));
796 env.require(balance("bob", Account("dan")["USD"](25)));
797 env.require(balance("carol", Account("alice")["USD"](25)));
798 env.require(balance("carol", Account("dan")["USD"](-25)));
799 env.require(balance("dan", Account("carol")["USD"](25)));
800 env.require(balance("dan", Account("bob")["USD"](-25)));
801 }
802
803 // carol holds gateway AUD, sells gateway AUD for XRP
804 // bob will hold gateway AUD
805 // alice pays bob gateway AUD using XRP
806 void
807 via_offers_via_gateway(bool const domainEnabled)
808 {
809 testcase(
810 std::string("via gateway") + (domainEnabled ? " w/ " : " w/o ") +
811 "domain");
812 using namespace jtx;
813 Env env = pathTestEnv();
814 auto const gw = Account("gateway");
815 auto const AUD = gw["AUD"];
816 env.fund(XRP(10000), "alice", "bob", "carol", gw);
817 env.close();
818 env(rate(gw, 1.1));
819 env.close();
820 env.trust(AUD(100), "bob", "carol");
821 env.close();
822 env(pay(gw, "carol", AUD(50)));
823 env.close();
824
825 std::optional<uint256> domainID;
826 if (domainEnabled)
827 {
828 domainID = setupDomain(env, {"alice", "bob", "carol", gw});
829 env(offer("carol", XRP(50), AUD(50)), domain(*domainID));
830 env.close();
831 env(pay("alice", "bob", AUD(10)),
832 sendmax(XRP(100)),
833 paths(XRP),
834 domain(*domainID));
835 env.close();
836 }
837 else
838 {
839 env(offer("carol", XRP(50), AUD(50)));
840 env.close();
841 env(pay("alice", "bob", AUD(10)), sendmax(XRP(100)), paths(XRP));
842 env.close();
843 }
844
845 env.require(balance("bob", AUD(10)));
846 env.require(balance("carol", AUD(39)));
847
848 auto const result = find_paths(
849 env,
850 "alice",
851 "bob",
852 Account("bob")["USD"](25),
855 domainID);
856 BEAST_EXPECT(std::get<0>(result).empty());
857 }
858
859 void
861 {
862 testcase("path find");
863 using namespace jtx;
864 Env env = pathTestEnv();
865 env.fund(XRP(10000), "alice", "bob", "carol");
866 env.close();
867 env.trust(Account("alice")["USD"](1000), "bob");
868 env.trust(Account("bob")["USD"](1000), "carol");
869
870 STPathSet st;
871 STAmount sa;
872 std::tie(st, sa, std::ignore) =
873 find_paths(env, "alice", "carol", Account("carol")["USD"](5));
874 BEAST_EXPECT(same(st, stpath("bob")));
875 BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
876 }
877
878 void
880 {
881 testcase("quality set and test");
882 using namespace jtx;
883 Env env = pathTestEnv();
884 env.fund(XRP(10000), "alice", "bob");
885 env.close();
886 env(trust("bob", Account("alice")["USD"](1000)),
887 json("{\"" + sfQualityIn.fieldName + "\": 2000}"),
888 json("{\"" + sfQualityOut.fieldName + "\": 1400000000}"));
889
890 Json::Value jv;
892 R"({
893 "Balance" : {
894 "currency" : "USD",
895 "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
896 "value" : "0"
897 },
898 "Flags" : 131072,
899 "HighLimit" : {
900 "currency" : "USD",
901 "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
902 "value" : "1000"
903 },
904 "HighNode" : "0",
905 "HighQualityIn" : 2000,
906 "HighQualityOut" : 1400000000,
907 "LedgerEntryType" : "RippleState",
908 "LowLimit" : {
909 "currency" : "USD",
910 "issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
911 "value" : "0"
912 },
913 "LowNode" : "0"
914 })",
915 jv);
916
917 auto const jv_l =
918 env.le(keylet::line(
919 Account("bob").id(), Account("alice")["USD"].issue()))
920 ->getJson(JsonOptions::none);
921 for (auto it = jv.begin(); it != jv.end(); ++it)
922 BEAST_EXPECT(*it == jv_l[it.memberName()]);
923 }
924
925 void
927 {
928 testcase("trust normal clear");
929 using namespace jtx;
930 Env env = pathTestEnv();
931 env.fund(XRP(10000), "alice", "bob");
932 env.close();
933 env.trust(Account("bob")["USD"](1000), "alice");
934 env.trust(Account("alice")["USD"](1000), "bob");
936 Json::Value jv;
938 R"({
939 "Balance" : {
940 "currency" : "USD",
941 "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
942 "value" : "0"
943 },
944 "Flags" : 196608,
945 "HighLimit" : {
946 "currency" : "USD",
947 "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
948 "value" : "1000"
949 },
950 "HighNode" : "0",
951 "LedgerEntryType" : "RippleState",
952 "LowLimit" : {
953 "currency" : "USD",
954 "issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
955 "value" : "1000"
956 },
957 "LowNode" : "0"
958 })",
959 jv);
960
961 auto const jv_l =
962 env.le(keylet::line(
963 Account("bob").id(), Account("alice")["USD"].issue()))
964 ->getJson(JsonOptions::none);
965 for (auto it = jv.begin(); it != jv.end(); ++it)
966 BEAST_EXPECT(*it == jv_l[it.memberName()]);
967
968 env.trust(Account("bob")["USD"](0), "alice");
969 env.trust(Account("alice")["USD"](0), "bob");
970 BEAST_EXPECT(
971 env.le(keylet::line(
972 Account("bob").id(), Account("alice")["USD"].issue())) ==
973 nullptr);
974 }
975
976 void
978 {
979 testcase("trust auto clear");
980 using namespace jtx;
981 Env env = pathTestEnv();
982 env.fund(XRP(10000), "alice", "bob");
983 env.close();
984 env.trust(Account("bob")["USD"](1000), "alice");
985 env(pay("bob", "alice", Account("bob")["USD"](50)));
986 env.trust(Account("bob")["USD"](0), "alice");
987
988 Json::Value jv;
990 R"({
991 "Balance" :
992 {
993 "currency" : "USD",
994 "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
995 "value" : "50"
996 },
997 "Flags" : 65536,
998 "HighLimit" :
999 {
1000 "currency" : "USD",
1001 "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
1002 "value" : "0"
1003 },
1004 "HighNode" : "0",
1005 "LedgerEntryType" : "RippleState",
1006 "LowLimit" :
1007 {
1008 "currency" : "USD",
1009 "issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
1010 "value" : "0"
1011 },
1012 "LowNode" : "0"
1013 })",
1014 jv);
1015
1016 auto const jv_l =
1017 env.le(keylet::line(
1018 Account("alice").id(), Account("bob")["USD"].issue()))
1019 ->getJson(JsonOptions::none);
1020 for (auto it = jv.begin(); it != jv.end(); ++it)
1021 BEAST_EXPECT(*it == jv_l[it.memberName()]);
1022
1023 env(pay("alice", "bob", Account("alice")["USD"](50)));
1024 BEAST_EXPECT(
1025 env.le(keylet::line(
1026 Account("alice").id(), Account("bob")["USD"].issue())) ==
1027 nullptr);
1028 }
1029
1030 void
1031 path_find_01(bool const domainEnabled)
1032 {
1033 testcase(
1034 std::string("Path Find: XRP -> XRP and XRP -> IOU") +
1035 (domainEnabled ? " w/ " : " w/o ") + "domain");
1036 using namespace jtx;
1037 Env env = pathTestEnv();
1038 Account A1{"A1"};
1039 Account A2{"A2"};
1040 Account A3{"A3"};
1041 Account G1{"G1"};
1042 Account G2{"G2"};
1043 Account G3{"G3"};
1044 Account M1{"M1"};
1045
1046 env.fund(XRP(100000), A1);
1047 env.fund(XRP(10000), A2);
1048 env.fund(XRP(1000), A3, G1, G2, G3, M1);
1049 env.close();
1050
1051 env.trust(G1["XYZ"](5000), A1);
1052 env.trust(G3["ABC"](5000), A1);
1053 env.trust(G2["XYZ"](5000), A2);
1054 env.trust(G3["ABC"](5000), A2);
1055 env.trust(A2["ABC"](1000), A3);
1056 env.trust(G1["XYZ"](100000), M1);
1057 env.trust(G2["XYZ"](100000), M1);
1058 env.trust(G3["ABC"](100000), M1);
1059 env.close();
1060
1061 env(pay(G1, A1, G1["XYZ"](3500)));
1062 env(pay(G3, A1, G3["ABC"](1200)));
1063 env(pay(G2, M1, G2["XYZ"](25000)));
1064 env(pay(G3, M1, G3["ABC"](25000)));
1065 env.close();
1066
1067 std::optional<uint256> domainID;
1068 if (domainEnabled)
1069 {
1070 domainID = setupDomain(env, {A1, A2, A3, G1, G2, G3, M1});
1071 env(offer(M1, G1["XYZ"](1000), G2["XYZ"](1000)), domain(*domainID));
1072 env(offer(M1, XRP(10000), G3["ABC"](1000)), domain(*domainID));
1073 env.close();
1074 }
1075 else
1076 {
1077 env(offer(M1, G1["XYZ"](1000), G2["XYZ"](1000)));
1078 env(offer(M1, XRP(10000), G3["ABC"](1000)));
1079 env.close();
1080 }
1081
1082 STPathSet st;
1083 STAmount sa, da;
1084
1085 {
1086 auto const& send_amt = XRP(10);
1087 std::tie(st, sa, da) = find_paths(
1088 env, A1, A2, send_amt, std::nullopt, xrpCurrency(), domainID);
1089 BEAST_EXPECT(equal(da, send_amt));
1090 BEAST_EXPECT(st.empty());
1091 }
1092
1093 {
1094 // no path should exist for this since dest account
1095 // does not exist.
1096 auto const& send_amt = XRP(200);
1097 std::tie(st, sa, da) = find_paths(
1098 env,
1099 A1,
1100 Account{"A0"},
1101 send_amt,
1103 xrpCurrency(),
1104 domainID);
1105 BEAST_EXPECT(equal(da, send_amt));
1106 BEAST_EXPECT(st.empty());
1107 }
1108
1109 {
1110 auto const& send_amt = G3["ABC"](10);
1111 std::tie(st, sa, da) = find_paths(
1112 env, A2, G3, send_amt, std::nullopt, xrpCurrency(), domainID);
1113 BEAST_EXPECT(equal(da, send_amt));
1114 BEAST_EXPECT(equal(sa, XRP(100)));
1115 BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]))));
1116 }
1117
1118 {
1119 auto const& send_amt = A2["ABC"](1);
1120 std::tie(st, sa, da) = find_paths(
1121 env, A1, A2, send_amt, std::nullopt, xrpCurrency(), domainID);
1122 BEAST_EXPECT(equal(da, send_amt));
1123 BEAST_EXPECT(equal(sa, XRP(10)));
1124 BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]), G3)));
1125 }
1126
1127 {
1128 auto const& send_amt = A3["ABC"](1);
1129 std::tie(st, sa, da) = find_paths(
1130 env, A1, A3, send_amt, std::nullopt, xrpCurrency(), domainID);
1131 BEAST_EXPECT(equal(da, send_amt));
1132 BEAST_EXPECT(equal(sa, XRP(10)));
1133 BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]), G3, A2)));
1134 }
1135 }
1136
1137 void
1138 path_find_02(bool const domainEnabled)
1140 testcase(
1141 std::string("Path Find: non-XRP -> XRP") +
1142 (domainEnabled ? " w/ " : " w/o ") + "domain");
1143 using namespace jtx;
1144 Env env = pathTestEnv();
1145 Account A1{"A1"};
1146 Account A2{"A2"};
1147 Account G3{"G3"};
1148 Account M1{"M1"};
1149
1150 env.fund(XRP(1000), A1, A2, G3);
1151 env.fund(XRP(11000), M1);
1152 env.close();
1153
1154 env.trust(G3["ABC"](1000), A1, A2);
1155 env.trust(G3["ABC"](100000), M1);
1156 env.close();
1157
1158 env(pay(G3, A1, G3["ABC"](1000)));
1159 env(pay(G3, A2, G3["ABC"](1000)));
1160 env(pay(G3, M1, G3["ABC"](1200)));
1161 env.close();
1162
1163 std::optional<uint256> domainID;
1164 if (domainEnabled)
1165 {
1166 domainID = setupDomain(env, {A1, A2, G3, M1});
1167 env(offer(M1, G3["ABC"](1000), XRP(10000)), domain(*domainID));
1168 }
1169 else
1170 {
1171 env(offer(M1, G3["ABC"](1000), XRP(10000)));
1172 }
1173
1174 STPathSet st;
1175 STAmount sa, da;
1176 auto const& send_amt = XRP(10);
1177
1178 {
1179 std::tie(st, sa, da) = find_paths(
1180 env,
1181 A1,
1182 A2,
1183 send_amt,
1185 A2["ABC"].currency,
1186 domainID);
1187 BEAST_EXPECT(equal(da, send_amt));
1188 BEAST_EXPECT(equal(sa, A1["ABC"](1)));
1189 BEAST_EXPECT(same(st, stpath(G3, IPE(xrpIssue()))));
1190 }
1191
1192 // domain offer will not be considered in pathfinding for non-domain
1193 // paths
1194 if (domainEnabled)
1195 {
1196 std::tie(st, sa, da) = find_paths(
1197 env, A1, A2, send_amt, std::nullopt, A2["ABC"].currency);
1198 BEAST_EXPECT(equal(da, send_amt));
1199 BEAST_EXPECT(st.empty());
1200 }
1201 }
1202
1203 void
1204 path_find_04(bool const domainEnabled)
1205 {
1206 testcase(
1208 "Path Find: Bitstamp and SnapSwap, liquidity with no offers") +
1209 (domainEnabled ? " w/ " : " w/o ") + "domain");
1210 using namespace jtx;
1211 Env env = pathTestEnv();
1212 Account A1{"A1"};
1213 Account A2{"A2"};
1214 Account G1BS{"G1BS"};
1215 Account G2SW{"G2SW"};
1216 Account M1{"M1"};
1217
1218 env.fund(XRP(1000), G1BS, G2SW, A1, A2);
1219 env.fund(XRP(11000), M1);
1220 env.close();
1221
1222 env.trust(G1BS["HKD"](2000), A1);
1223 env.trust(G2SW["HKD"](2000), A2);
1224 env.trust(G1BS["HKD"](100000), M1);
1225 env.trust(G2SW["HKD"](100000), M1);
1226 env.close();
1227
1228 env(pay(G1BS, A1, G1BS["HKD"](1000)));
1229 env(pay(G2SW, A2, G2SW["HKD"](1000)));
1230 // SnapSwap wants to be able to set trust line quality settings so they
1231 // can charge a fee when transactions ripple across. Liquidity
1232 // provider, via trusting/holding both accounts
1233 env(pay(G1BS, M1, G1BS["HKD"](1200)));
1234 env(pay(G2SW, M1, G2SW["HKD"](5000)));
1235 env.close();
1236
1237 std::optional<uint256> domainID;
1238 if (domainEnabled)
1239 domainID = setupDomain(env, {A1, A2, G1BS, G2SW, M1});
1240
1241 STPathSet st;
1242 STAmount sa, da;
1243
1244 {
1245 auto const& send_amt = A2["HKD"](10);
1246 std::tie(st, sa, da) = find_paths(
1247 env,
1248 A1,
1249 A2,
1250 send_amt,
1252 A2["HKD"].currency,
1253 domainID);
1254 BEAST_EXPECT(equal(da, send_amt));
1255 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1256 BEAST_EXPECT(same(st, stpath(G1BS, M1, G2SW)));
1257 }
1258
1259 {
1260 auto const& send_amt = A1["HKD"](10);
1261 std::tie(st, sa, da) = find_paths(
1262 env,
1263 A2,
1264 A1,
1265 send_amt,
1267 A1["HKD"].currency,
1268 domainID);
1269 BEAST_EXPECT(equal(da, send_amt));
1270 BEAST_EXPECT(equal(sa, A2["HKD"](10)));
1271 BEAST_EXPECT(same(st, stpath(G2SW, M1, G1BS)));
1272 }
1273
1274 {
1275 auto const& send_amt = A2["HKD"](10);
1276 std::tie(st, sa, da) = find_paths(
1277 env,
1278 G1BS,
1279 A2,
1280 send_amt,
1282 A1["HKD"].currency,
1283 domainID);
1284 BEAST_EXPECT(equal(da, send_amt));
1285 BEAST_EXPECT(equal(sa, G1BS["HKD"](10)));
1286 BEAST_EXPECT(same(st, stpath(M1, G2SW)));
1287 }
1288
1289 {
1290 auto const& send_amt = M1["HKD"](10);
1291 std::tie(st, sa, da) = find_paths(
1292 env,
1293 M1,
1294 G1BS,
1295 send_amt,
1297 A1["HKD"].currency,
1298 domainID);
1299 BEAST_EXPECT(equal(da, send_amt));
1300 BEAST_EXPECT(equal(sa, M1["HKD"](10)));
1301 BEAST_EXPECT(st.empty());
1302 }
1303
1304 {
1305 auto const& send_amt = A1["HKD"](10);
1306 std::tie(st, sa, da) = find_paths(
1307 env,
1308 G2SW,
1309 A1,
1310 send_amt,
1312 A1["HKD"].currency,
1313 domainID);
1314 BEAST_EXPECT(equal(da, send_amt));
1315 BEAST_EXPECT(equal(sa, G2SW["HKD"](10)));
1316 BEAST_EXPECT(same(st, stpath(M1, G1BS)));
1317 }
1318 }
1319
1320 void
1321 path_find_05(bool const domainEnabled)
1322 {
1323 testcase(
1324 std::string("Path Find: non-XRP -> non-XRP, same currency") +
1325 (domainEnabled ? " w/ " : " w/o ") + "domain");
1326 using namespace jtx;
1327 Env env = pathTestEnv();
1328 Account A1{"A1"};
1329 Account A2{"A2"};
1330 Account A3{"A3"};
1331 Account A4{"A4"};
1332 Account G1{"G1"};
1333 Account G2{"G2"};
1334 Account G3{"G3"};
1335 Account G4{"G4"};
1336 Account M1{"M1"};
1337 Account M2{"M2"};
1338
1339 env.fund(XRP(1000), A1, A2, A3, G1, G2, G3, G4);
1340 env.fund(XRP(10000), A4);
1341 env.fund(XRP(11000), M1, M2);
1342 env.close();
1343
1344 env.trust(G1["HKD"](2000), A1);
1345 env.trust(G2["HKD"](2000), A2);
1346 env.trust(G1["HKD"](2000), A3);
1347 env.trust(G1["HKD"](100000), M1);
1348 env.trust(G2["HKD"](100000), M1);
1349 env.trust(G1["HKD"](100000), M2);
1350 env.trust(G2["HKD"](100000), M2);
1351 env.close();
1352
1353 env(pay(G1, A1, G1["HKD"](1000)));
1354 env(pay(G2, A2, G2["HKD"](1000)));
1355 env(pay(G1, A3, G1["HKD"](1000)));
1356 env(pay(G1, M1, G1["HKD"](1200)));
1357 env(pay(G2, M1, G2["HKD"](5000)));
1358 env(pay(G1, M2, G1["HKD"](1200)));
1359 env(pay(G2, M2, G2["HKD"](5000)));
1360 env.close();
1361
1362 std::optional<uint256> domainID;
1363 if (domainEnabled)
1364 {
1365 domainID =
1366 setupDomain(env, {A1, A2, A3, A4, G1, G2, G3, G4, M1, M2});
1367 env(offer(M1, G1["HKD"](1000), G2["HKD"](1000)), domain(*domainID));
1368 env(offer(M2, XRP(10000), G2["HKD"](1000)), domain(*domainID));
1369 env(offer(M2, G1["HKD"](1000), XRP(10000)), domain(*domainID));
1370 }
1371 else
1372 {
1373 env(offer(M1, G1["HKD"](1000), G2["HKD"](1000)));
1374 env(offer(M2, XRP(10000), G2["HKD"](1000)));
1375 env(offer(M2, G1["HKD"](1000), XRP(10000)));
1376 }
1377
1378 STPathSet st;
1379 STAmount sa, da;
1380
1381 {
1382 // A) Borrow or repay --
1383 // Source -> Destination (repay source issuer)
1384 auto const& send_amt = G1["HKD"](10);
1385 std::tie(st, sa, da) = find_paths(
1386 env,
1387 A1,
1388 G1,
1389 send_amt,
1391 G1["HKD"].currency,
1392 domainID);
1393 BEAST_EXPECT(st.empty());
1394 BEAST_EXPECT(equal(da, send_amt));
1395 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1396 }
1397
1398 {
1399 // A2) Borrow or repay --
1400 // Source -> Destination (repay destination issuer)
1401 auto const& send_amt = A1["HKD"](10);
1402 std::tie(st, sa, da) = find_paths(
1403 env,
1404 A1,
1405 G1,
1406 send_amt,
1408 G1["HKD"].currency,
1409 domainID);
1410 BEAST_EXPECT(st.empty());
1411 BEAST_EXPECT(equal(da, send_amt));
1412 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1413 }
1414
1415 {
1416 // B) Common gateway --
1417 // Source -> AC -> Destination
1418 auto const& send_amt = A3["HKD"](10);
1419 std::tie(st, sa, da) = find_paths(
1420 env,
1421 A1,
1422 A3,
1423 send_amt,
1425 G1["HKD"].currency,
1426 domainID);
1427 BEAST_EXPECT(equal(da, send_amt));
1428 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1429 BEAST_EXPECT(same(st, stpath(G1)));
1430 }
1431
1432 {
1433 // C) Gateway to gateway --
1434 // Source -> OB -> Destination
1435 auto const& send_amt = G2["HKD"](10);
1436 std::tie(st, sa, da) = find_paths(
1437 env,
1438 G1,
1439 G2,
1440 send_amt,
1442 G1["HKD"].currency,
1443 domainID);
1444 BEAST_EXPECT(equal(da, send_amt));
1445 BEAST_EXPECT(equal(sa, G1["HKD"](10)));
1446 BEAST_EXPECT(same(
1447 st,
1448 stpath(IPE(G2["HKD"])),
1449 stpath(M1),
1450 stpath(M2),
1451 stpath(IPE(xrpIssue()), IPE(G2["HKD"]))));
1452 }
1453
1454 {
1455 // D) User to unlinked gateway via order book --
1456 // Source -> AC -> OB -> Destination
1457 auto const& send_amt = G2["HKD"](10);
1458 std::tie(st, sa, da) = find_paths(
1459 env,
1460 A1,
1461 G2,
1462 send_amt,
1464 G1["HKD"].currency,
1465 domainID);
1466 BEAST_EXPECT(equal(da, send_amt));
1467 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1468 BEAST_EXPECT(same(
1469 st,
1470 stpath(G1, M1),
1471 stpath(G1, M2),
1472 stpath(G1, IPE(G2["HKD"])),
1473 stpath(G1, IPE(xrpIssue()), IPE(G2["HKD"]))));
1474 }
1475
1476 {
1477 // I4) XRP bridge" --
1478 // Source -> AC -> OB to XRP -> OB from XRP -> AC ->
1479 // Destination
1480 auto const& send_amt = A2["HKD"](10);
1481 std::tie(st, sa, da) = find_paths(
1482 env,
1483 A1,
1484 A2,
1485 send_amt,
1487 G1["HKD"].currency,
1488 domainID);
1489 BEAST_EXPECT(equal(da, send_amt));
1490 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1491 BEAST_EXPECT(same(
1492 st,
1493 stpath(G1, M1, G2),
1494 stpath(G1, M2, G2),
1495 stpath(G1, IPE(G2["HKD"]), G2),
1496 stpath(G1, IPE(xrpIssue()), IPE(G2["HKD"]), G2)));
1497 }
1498 }
1499
1500 void
1501 path_find_06(bool const domainEnabled)
1502 {
1503 testcase(
1504 std::string("Path Find: non-XRP -> non-XRP, same currency)") +
1505 (domainEnabled ? " w/ " : " w/o ") + "domain");
1506 using namespace jtx;
1507 Env env = pathTestEnv();
1508 Account A1{"A1"};
1509 Account A2{"A2"};
1510 Account A3{"A3"};
1511 Account G1{"G1"};
1512 Account G2{"G2"};
1513 Account M1{"M1"};
1514
1515 env.fund(XRP(11000), M1);
1516 env.fund(XRP(1000), A1, A2, A3, G1, G2);
1517 env.close();
1518
1519 env.trust(G1["HKD"](2000), A1);
1520 env.trust(G2["HKD"](2000), A2);
1521 env.trust(A2["HKD"](2000), A3);
1522 env.trust(G1["HKD"](100000), M1);
1523 env.trust(G2["HKD"](100000), M1);
1524 env.close();
1525
1526 env(pay(G1, A1, G1["HKD"](1000)));
1527 env(pay(G2, A2, G2["HKD"](1000)));
1528 env(pay(G1, M1, G1["HKD"](5000)));
1529 env(pay(G2, M1, G2["HKD"](5000)));
1530 env.close();
1531
1532 std::optional<uint256> domainID;
1533 if (domainEnabled)
1534 {
1535 domainID = setupDomain(env, {A1, A2, A3, G1, G2, M1});
1536 env(offer(M1, G1["HKD"](1000), G2["HKD"](1000)), domain(*domainID));
1537 }
1538 else
1539 {
1540 env(offer(M1, G1["HKD"](1000), G2["HKD"](1000)));
1541 }
1542
1543 // E) Gateway to user
1544 // Source -> OB -> AC -> Destination
1545 auto const& send_amt = A2["HKD"](10);
1546 STPathSet st;
1547 STAmount sa, da;
1548 std::tie(st, sa, da) = find_paths(
1549 env, G1, A2, send_amt, std::nullopt, G1["HKD"].currency, domainID);
1550 BEAST_EXPECT(equal(da, send_amt));
1551 BEAST_EXPECT(equal(sa, G1["HKD"](10)));
1552 BEAST_EXPECT(same(st, stpath(M1, G2), stpath(IPE(G2["HKD"]), G2)));
1553 }
1554
1555 void
1556 receive_max(bool const domainEnabled)
1557 {
1558 testcase(
1559 std::string("Receive max") + (domainEnabled ? " w/ " : " w/o ") +
1560 "domain");
1561
1562 using namespace jtx;
1563 auto const alice = Account("alice");
1564 auto const bob = Account("bob");
1565 auto const charlie = Account("charlie");
1566 auto const gw = Account("gw");
1567 auto const USD = gw["USD"];
1568 {
1569 // XRP -> IOU receive max
1570 Env env = pathTestEnv();
1571 env.fund(XRP(10000), alice, bob, charlie, gw);
1572 env.close();
1573 env.trust(USD(100), alice, bob, charlie);
1574 env.close();
1575 env(pay(gw, charlie, USD(10)));
1576 env.close();
1577
1578 std::optional<uint256> domainID;
1579 if (domainEnabled)
1580 {
1581 domainID = setupDomain(env, {alice, bob, charlie, gw});
1582 env(offer(charlie, XRP(10), USD(10)), domain(*domainID));
1583 env.close();
1584 }
1585 else
1586 {
1587 env(offer(charlie, XRP(10), USD(10)));
1588 env.close();
1590
1591 auto [st, sa, da] = find_paths(
1592 env,
1593 alice,
1594 bob,
1595 USD(-1),
1596 XRP(100).value(),
1598 domainID);
1599 BEAST_EXPECT(sa == XRP(10));
1600 BEAST_EXPECT(equal(da, USD(10)));
1601 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1602 {
1603 auto const& pathElem = st[0][0];
1604 BEAST_EXPECT(
1605 pathElem.isOffer() && pathElem.getIssuerID() == gw.id() &&
1606 pathElem.getCurrency() == USD.currency);
1607 }
1608 }
1609 {
1610 // IOU -> XRP receive max
1611 Env env = pathTestEnv();
1612 env.fund(XRP(10000), alice, bob, charlie, gw);
1613 env.close();
1614 env.trust(USD(100), alice, bob, charlie);
1615 env.close();
1616 env(pay(gw, alice, USD(10)));
1617 env.close();
1618
1619 std::optional<uint256> domainID;
1620 if (domainEnabled)
1621 {
1622 domainID = setupDomain(env, {alice, bob, charlie, gw});
1623 env(offer(charlie, USD(10), XRP(10)), domain(*domainID));
1624 env.close();
1625 }
1626 else
1627 {
1628 env(offer(charlie, USD(10), XRP(10)));
1629 env.close();
1630 }
1631
1632 auto [st, sa, da] = find_paths(
1633 env,
1634 alice,
1635 bob,
1636 drops(-1),
1637 USD(100).value(),
1639 domainID);
1640 BEAST_EXPECT(sa == USD(10));
1641 BEAST_EXPECT(equal(da, XRP(10)));
1642 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1643 {
1644 auto const& pathElem = st[0][0];
1645 BEAST_EXPECT(
1646 pathElem.isOffer() &&
1647 pathElem.getIssuerID() == xrpAccount() &&
1648 pathElem.getCurrency() == xrpCurrency());
1649 }
1650 }
1651 }
1653 void
1655 {
1656 using namespace jtx;
1657 // This test will create trust lines with various values of the noRipple
1658 // flag. alice <-> george <-> bob george will sort of act like a
1659 // gateway, but use a different name to avoid the usual assumptions
1660 // about gateways.
1661 auto const alice = Account("alice");
1662 auto const bob = Account("bob");
1663 auto const george = Account("george");
1664 auto const USD = george["USD"];
1665 auto test = [&](std::string casename,
1666 bool aliceRipple,
1667 bool bobRipple,
1668 bool expectPath) {
1669 testcase(casename);
1670
1671 Env env = pathTestEnv();
1672 env.fund(XRP(10000), noripple(alice, bob, george));
1673 env.close();
1674 // Set the same flags at both ends of the trustline, even though
1675 // only george's matter.
1676 env(trust(
1677 alice,
1678 USD(100),
1679 aliceRipple ? tfClearNoRipple : tfSetNoRipple));
1680 env(trust(
1681 george,
1682 alice["USD"](100),
1683 aliceRipple ? tfClearNoRipple : tfSetNoRipple));
1684 env(trust(
1685 bob, USD(100), bobRipple ? tfClearNoRipple : tfSetNoRipple));
1686 env(trust(
1687 george,
1688 bob["USD"](100),
1689 bobRipple ? tfClearNoRipple : tfSetNoRipple));
1690 env.close();
1691 env(pay(george, alice, USD(70)));
1692 env.close();
1693
1694 auto [st, sa, da] =
1695 find_paths(env, "alice", "bob", Account("bob")["USD"](5));
1696 BEAST_EXPECT(equal(da, bob["USD"](5)));
1697
1698 if (expectPath)
1699 {
1700 BEAST_EXPECT(st.size() == 1);
1701 BEAST_EXPECT(same(st, stpath("george")));
1702 BEAST_EXPECT(equal(sa, alice["USD"](5)));
1703 }
1704 else
1705 {
1706 BEAST_EXPECT(st.size() == 0);
1707 BEAST_EXPECT(equal(sa, XRP(0)));
1708 }
1709 };
1710 test("ripple -> ripple", true, true, true);
1711 test("ripple -> no ripple", true, false, true);
1712 test("no ripple -> ripple", false, true, true);
1713 test("no ripple -> no ripple", false, false, false);
1714 }
1715
1716 void
1718 {
1719 testcase("Hybrid offer path");
1720 using namespace jtx;
1721
1722 // test cases copied from path_find_05 and ensures path results for
1723 // different combinations of open/domain/hybrid offers. `func` is a
1724 // lambda param that creates different types of offers
1725 auto testPathfind = [&](auto func, bool const domainEnabled = false) {
1726 Env env = pathTestEnv();
1727 Account A1{"A1"};
1728 Account A2{"A2"};
1729 Account A3{"A3"};
1730 Account A4{"A4"};
1731 Account G1{"G1"};
1732 Account G2{"G2"};
1733 Account G3{"G3"};
1734 Account G4{"G4"};
1735 Account M1{"M1"};
1736 Account M2{"M2"};
1737
1738 env.fund(XRP(1000), A1, A2, A3, G1, G2, G3, G4);
1739 env.fund(XRP(10000), A4);
1740 env.fund(XRP(11000), M1, M2);
1741 env.close();
1742
1743 env.trust(G1["HKD"](2000), A1);
1744 env.trust(G2["HKD"](2000), A2);
1745 env.trust(G1["HKD"](2000), A3);
1746 env.trust(G1["HKD"](100000), M1);
1747 env.trust(G2["HKD"](100000), M1);
1748 env.trust(G1["HKD"](100000), M2);
1749 env.trust(G2["HKD"](100000), M2);
1750 env.close();
1751
1752 env(pay(G1, A1, G1["HKD"](1000)));
1753 env(pay(G2, A2, G2["HKD"](1000)));
1754 env(pay(G1, A3, G1["HKD"](1000)));
1755 env(pay(G1, M1, G1["HKD"](1200)));
1756 env(pay(G2, M1, G2["HKD"](5000)));
1757 env(pay(G1, M2, G1["HKD"](1200)));
1758 env(pay(G2, M2, G2["HKD"](5000)));
1759 env.close();
1760
1761 std::optional<uint256> domainID =
1762 setupDomain(env, {A1, A2, A3, A4, G1, G2, G3, G4, M1, M2});
1763 BEAST_EXPECT(domainID);
1764
1765 func(env, M1, M2, G1, G2, *domainID);
1766
1767 STPathSet st;
1768 STAmount sa, da;
1769
1770 {
1771 // A) Borrow or repay --
1772 // Source -> Destination (repay source issuer)
1773 auto const& send_amt = G1["HKD"](10);
1774 std::tie(st, sa, da) = find_paths(
1775 env,
1776 A1,
1777 G1,
1778 send_amt,
1780 G1["HKD"].currency,
1781 domainEnabled ? domainID : std::nullopt);
1782 BEAST_EXPECT(st.empty());
1783 BEAST_EXPECT(equal(da, send_amt));
1784 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1785 }
1786
1787 {
1788 // A2) Borrow or repay --
1789 // Source -> Destination (repay destination issuer)
1790 auto const& send_amt = A1["HKD"](10);
1791 std::tie(st, sa, da) = find_paths(
1792 env,
1793 A1,
1794 G1,
1795 send_amt,
1797 G1["HKD"].currency,
1798 domainEnabled ? domainID : std::nullopt);
1799 BEAST_EXPECT(st.empty());
1800 BEAST_EXPECT(equal(da, send_amt));
1801 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1802 }
1803
1804 {
1805 // B) Common gateway --
1806 // Source -> AC -> Destination
1807 auto const& send_amt = A3["HKD"](10);
1808 std::tie(st, sa, da) = find_paths(
1809 env,
1810 A1,
1811 A3,
1812 send_amt,
1814 G1["HKD"].currency,
1815 domainEnabled ? domainID : std::nullopt);
1816 BEAST_EXPECT(equal(da, send_amt));
1817 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1818 BEAST_EXPECT(same(st, stpath(G1)));
1819 }
1820
1821 {
1822 // C) Gateway to gateway --
1823 // Source -> OB -> Destination
1824 auto const& send_amt = G2["HKD"](10);
1825 std::tie(st, sa, da) = find_paths(
1826 env,
1827 G1,
1828 G2,
1829 send_amt,
1831 G1["HKD"].currency,
1832 domainEnabled ? domainID : std::nullopt);
1833 BEAST_EXPECT(equal(da, send_amt));
1834 BEAST_EXPECT(equal(sa, G1["HKD"](10)));
1835 BEAST_EXPECT(same(
1836 st,
1837 stpath(IPE(G2["HKD"])),
1838 stpath(M1),
1839 stpath(M2),
1840 stpath(IPE(xrpIssue()), IPE(G2["HKD"]))));
1841 }
1842
1843 {
1844 // D) User to unlinked gateway via order book --
1845 // Source -> AC -> OB -> Destination
1846 auto const& send_amt = G2["HKD"](10);
1847 std::tie(st, sa, da) = find_paths(
1848 env,
1849 A1,
1850 G2,
1851 send_amt,
1853 G1["HKD"].currency,
1854 domainEnabled ? domainID : std::nullopt);
1855 BEAST_EXPECT(equal(da, send_amt));
1856 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1857 BEAST_EXPECT(same(
1858 st,
1859 stpath(G1, M1),
1860 stpath(G1, M2),
1861 stpath(G1, IPE(G2["HKD"])),
1862 stpath(G1, IPE(xrpIssue()), IPE(G2["HKD"]))));
1863 }
1864
1865 {
1866 // I4) XRP bridge" --
1867 // Source -> AC -> OB to XRP -> OB from XRP -> AC ->
1868 // Destination
1869 auto const& send_amt = A2["HKD"](10);
1870 std::tie(st, sa, da) = find_paths(
1871 env,
1872 A1,
1873 A2,
1874 send_amt,
1876 G1["HKD"].currency,
1877 domainEnabled ? domainID : std::nullopt);
1878 BEAST_EXPECT(equal(da, send_amt));
1879 BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1880 BEAST_EXPECT(same(
1881 st,
1882 stpath(G1, M1, G2),
1883 stpath(G1, M2, G2),
1884 stpath(G1, IPE(G2["HKD"]), G2),
1885 stpath(G1, IPE(xrpIssue()), IPE(G2["HKD"]), G2)));
1886 }
1887 };
1888
1889 // the following tests exercise different combinations of open/hybrid
1890 // offers to make sure that hybrid offers work in pathfinding for open
1891 // order book
1892 {
1893 testPathfind([](Env& env,
1894 Account M1,
1895 Account M2,
1896 Account G1,
1897 Account G2,
1898 uint256 domainID) {
1899 env(offer(M1, G1["HKD"](1000), G2["HKD"](1000)),
1900 domain(domainID),
1901 txflags(tfHybrid));
1902 env(offer(M2, XRP(10000), G2["HKD"](1000)));
1903 env(offer(M2, G1["HKD"](1000), XRP(10000)));
1904 });
1905
1906 testPathfind([](Env& env,
1907 Account M1,
1908 Account M2,
1909 Account G1,
1910 Account G2,
1911 uint256 domainID) {
1912 env(offer(M1, G1["HKD"](1000), G2["HKD"](1000)),
1913 domain(domainID),
1914 txflags(tfHybrid));
1915 env(offer(M2, XRP(10000), G2["HKD"](1000)),
1916 domain(domainID),
1917 txflags(tfHybrid));
1918 env(offer(M2, G1["HKD"](1000), XRP(10000)));
1919 });
1920
1921 testPathfind([](Env& env,
1922 Account M1,
1923 Account M2,
1924 Account G1,
1925 Account G2,
1926 uint256 domainID) {
1927 env(offer(M1, G1["HKD"](1000), G2["HKD"](1000)),
1928 domain(domainID),
1929 txflags(tfHybrid));
1930 env(offer(M2, XRP(10000), G2["HKD"](1000)),
1931 domain(domainID),
1932 txflags(tfHybrid));
1933 env(offer(M2, G1["HKD"](1000), XRP(10000)),
1934 domain(domainID),
1935 txflags(tfHybrid));
1936 });
1937
1938 testPathfind([](Env& env,
1939 Account M1,
1940 Account M2,
1941 Account G1,
1942 Account G2,
1943 uint256 domainID) {
1944 env(offer(M1, G1["HKD"](1000), G2["HKD"](1000)));
1945 env(offer(M2, XRP(10000), G2["HKD"](1000)));
1946 env(offer(M2, G1["HKD"](1000), XRP(10000)),
1947 domain(domainID),
1948 txflags(tfHybrid));
1949 });
1950
1951 testPathfind([](Env& env,
1952 Account M1,
1953 Account M2,
1954 Account G1,
1955 Account G2,
1956 uint256 domainID) {
1957 env(offer(M1, G1["HKD"](1000), G2["HKD"](1000)));
1958 env(offer(M2, XRP(10000), G2["HKD"](1000)),
1959 domain(domainID),
1960 txflags(tfHybrid));
1961 env(offer(M2, G1["HKD"](1000), XRP(10000)),
1962 domain(domainID),
1963 txflags(tfHybrid));
1964 });
1965 }
1966
1967 // the following tests exercise different combinations of domain/hybrid
1968 // offers to make sure that hybrid offers work in pathfinding for domain
1969 // order book
1970 {
1971 testPathfind(
1972 [](Env& env,
1973 Account M1,
1974 Account M2,
1975 Account G1,
1976 Account G2,
1977 uint256 domainID) {
1978 env(offer(M1, G1["HKD"](1000), G2["HKD"](1000)),
1979 domain(domainID),
1980 txflags(tfHybrid));
1981 env(offer(M2, XRP(10000), G2["HKD"](1000)),
1982 domain(domainID));
1983 env(offer(M2, G1["HKD"](1000), XRP(10000)),
1984 domain(domainID));
1985 },
1986 true);
1987
1988 testPathfind(
1989 [](Env& env,
1990 Account M1,
1991 Account M2,
1992 Account G1,
1993 Account G2,
1994 uint256 domainID) {
1995 env(offer(M1, G1["HKD"](1000), G2["HKD"](1000)),
1996 domain(domainID),
1997 txflags(tfHybrid));
1998 env(offer(M2, XRP(10000), G2["HKD"](1000)),
1999 domain(domainID),
2000 txflags(tfHybrid));
2001 env(offer(M2, G1["HKD"](1000), XRP(10000)),
2002 domain(domainID));
2003 },
2004 true);
2005
2006 testPathfind(
2007 [](Env& env,
2008 Account M1,
2009 Account M2,
2010 Account G1,
2011 Account G2,
2012 uint256 domainID) {
2013 env(offer(M1, G1["HKD"](1000), G2["HKD"](1000)),
2014 domain(domainID));
2015 env(offer(M2, XRP(10000), G2["HKD"](1000)),
2016 domain(domainID));
2017 env(offer(M2, G1["HKD"](1000), XRP(10000)),
2018 domain(domainID),
2019 txflags(tfHybrid));
2020 },
2021 true);
2022
2023 testPathfind(
2024 [](Env& env,
2025 Account M1,
2026 Account M2,
2027 Account G1,
2028 Account G2,
2029 uint256 domainID) {
2030 env(offer(M1, G1["HKD"](1000), G2["HKD"](1000)),
2031 domain(domainID));
2032 env(offer(M2, XRP(10000), G2["HKD"](1000)),
2033 domain(domainID),
2034 txflags(tfHybrid));
2035 env(offer(M2, G1["HKD"](1000), XRP(10000)),
2036 domain(domainID),
2037 txflags(tfHybrid));
2038 },
2039 true);
2040 }
2041 }
2042
2043 void
2045 {
2046 testcase("AMM not used in domain path");
2047 using namespace jtx;
2048 Env env = pathTestEnv();
2049 PermissionedDEX permDex(env);
2050 auto const& [gw, domainOwner, alice, bob, carol, USD, domainID, credType] =
2051 permDex;
2052 AMM amm(env, alice, XRP(10), USD(50));
2053
2054 STPathSet st;
2055 STAmount sa, da;
2056
2057 auto const& send_amt = XRP(1);
2058
2059 // doing pathfind with domain won't include amm
2060 std::tie(st, sa, da) = find_paths(
2061 env, bob, carol, send_amt, std::nullopt, USD.currency, domainID);
2062 BEAST_EXPECT(st.empty());
2063
2064 // a non-domain pathfind returns amm in the path
2065 std::tie(st, sa, da) =
2066 find_paths(env, bob, carol, send_amt, std::nullopt, USD.currency);
2067 BEAST_EXPECT(same(st, stpath(gw, IPE(xrpIssue()))));
2068 }
2069
2070 void
2071 run() override
2072 {
2085
2086 for (bool const domainEnabled : {false, true})
2087 {
2088 path_find(domainEnabled);
2089 path_find_consume_all(domainEnabled);
2090 alternative_path_consume_both(domainEnabled);
2093 domainEnabled);
2094 issues_path_negative_issue(domainEnabled);
2095 via_offers_via_gateway(domainEnabled);
2096 xrp_to_xrp(domainEnabled);
2097 receive_max(domainEnabled);
2098
2099 // The following path_find_NN tests are data driven tests
2100 // that were originally implemented in js/coffee and migrated
2101 // here. The quantities and currencies used are taken directly from
2102 // those legacy tests, which in some cases probably represented
2103 // customer use cases.
2104
2105 path_find_01(domainEnabled);
2106 path_find_02(domainEnabled);
2107 path_find_04(domainEnabled);
2108 path_find_05(domainEnabled);
2109 path_find_06(domainEnabled);
2110 }
2111
2114 }
2115};
2116
2117BEAST_DEFINE_TESTSUITE(Path, app, ripple);
2118
2119} // namespace test
2120} // namespace ripple
Unserialize a JSON document into a Value.
Definition json_reader.h:20
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Represents a JSON value.
Definition json_value.h:130
const_iterator begin() const
const_iterator end() const
bool isMember(char const *key) const
Return true if the object has a member named key.
A testsuite class.
Definition suite.h:52
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:152
A consumption charge.
Definition Charge.h:11
An endpoint that consumes resources.
Definition Consumer.h:17
Json::Value getJson(JsonOptions=JsonOptions::none) const override
Definition STAmount.cpp:753
Holds the serialized result of parsing an input JSON object.
std::optional< STObject > object
The STObject if the parse was successful.
bool empty() const
Definition STPathSet.h:489
std::condition_variable cv_
Definition Path_test.cpp:83
bool wait_for(std::chrono::duration< Rep, Period > const &rel_time)
Definition Path_test.cpp:92
void path_find_consume_all(bool const domainEnabled)
void trust_auto_clear_trust_normal_clear()
void alternative_path_consume_both(bool const domainEnabled)
void via_offers_via_gateway(bool const domainEnabled)
void issues_path_negative_issue(bool const domainEnabled)
void trust_auto_clear_trust_auto_clear()
void receive_max(bool const domainEnabled)
void path_find_02(bool const domainEnabled)
void issues_path_negative_ripple_client_issue_23_larger()
void alternative_paths_consume_best_transfer(bool const domainEnabled)
std::tuple< STPathSet, STAmount, STAmount > find_paths(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax=std::nullopt, std::optional< Currency > const &saSrcCurrency=std::nullopt, std::optional< uint256 > const &domain=std::nullopt)
void alternative_paths_limit_returned_paths_to_best_quality(bool const domainEnabled)
void path_find_04(bool const domainEnabled)
auto find_paths_request(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax=std::nullopt, std::optional< Currency > const &saSrcCurrency=std::nullopt, std::optional< uint256 > const &domain=std::nullopt)
void issues_path_negative_ripple_client_issue_23_smaller()
void alternative_paths_consume_best_transfer_first()
void path_find_05(bool const domainEnabled)
void path_find_01(bool const domainEnabled)
void path_find_06(bool const domainEnabled)
void quality_paths_quality_set_and_test()
void xrp_to_xrp(bool const domainEnabled)
void run() override
Runs the suite.
void path_find(bool const domainEnabled)
void no_direct_path_no_intermediary_no_alternatives()
Immutable cryptographic account descriptor.
Definition Account.h:20
A transaction testing environment.
Definition Env.h:102
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:528
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:103
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:302
Application & app()
Definition Env.h:242
beast::Journal const journal
Definition Env.h:143
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:271
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition Env.cpp:259
A balance matches.
Definition balance.h:20
Set the domain on a JTx.
Definition domain.h:11
Inject raw JSON.
Definition jtx_json.h:14
Add a path.
Definition paths.h:39
Set Paths, SendMax on a JTx.
Definition paths.h:16
Sets the SendMax on a JTx.
Definition sendmax.h:14
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:16
Set the flags on a JTx.
Definition txflags.h:12
T is_same_v
T make_tuple(T... args)
@ arrayValue
array value (ordered list)
Definition json_value.h:25
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:26
static int constexpr max_src_cur
Maximum number of source currencies allowed in a path find request.
static int constexpr max_auto_src_cur
Maximum number of auto source currencies in a path find request.
Status doCommand(RPC::JsonContext &context, Json::Value &result)
Execute an RPC command and store the results in a Json::Value.
static constexpr auto apiVersionIfUnspecified
Definition ApiVersion.h:44
Charge const feeReferenceRPC
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:427
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:225
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:13
uint256 setupDomain(jtx::Env &env, std::vector< jtx::Account > const &accounts, jtx::Account const &domainOwner, std::string const &credType)
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
bool same(STPathSet const &st1, Args const &... args)
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:35
Json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
Definition rate.cpp:13
STPathElement IPE(Issue const &iss)
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:10
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
STPath stpath(Args const &... args)
Json::Value rpf(jtx::Account const &src, jtx::Account const &dst, std::uint32_t num_src)
Definition Path_test.cpp:32
bool equal(std::unique_ptr< Step > const &s1, DirectStepInfo const &dsi)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:96
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
AccountID const & xrpAccount()
Compute AccountID from public key.
base_uint< 256 > uint256
Definition base_uint.h:539
STAmount amountFromJson(SField const &name, Json::Value const &v)
Definition STAmount.cpp:987
constexpr std::uint32_t tfHybrid
Definition TxFlags.h:83
constexpr std::uint32_t tfClearNoRipple
Definition TxFlags.h:98
Currency const & xrpCurrency()
XRP currency.
@ tecPATH_DRY
Definition TER.h:276
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:611
SField const sfGeneric
@ jtCLIENT
Definition Job.h:26
constexpr std::uint32_t tfSetNoRipple
Definition TxFlags.h:97
STL namespace.
T tie(T... args)
T to_string(T... args)