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