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