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