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