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