rippled
Path_test.cpp
1 //------------------------------------------------------------------------------
2 /*
3  This file is part of rippled: https://github.com/ripple/rippled
4  Copyright (c) 2012-2015 Ripple Labs Inc.
5 
6  Permission to use, copy, modify, and/or distribute this software for any
7  purpose with or without fee is hereby granted, provided that the above
8  copyright notice and this permission notice appear in all copies.
9 
10  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 //==============================================================================
19 
20 #include <ripple/app/paths/AccountCurrencies.h>
21 #include <ripple/basics/contract.h>
22 #include <ripple/beast/unit_test.h>
23 #include <ripple/core/JobQueue.h>
24 #include <ripple/json/json_reader.h>
25 #include <ripple/json/to_string.h>
26 #include <ripple/protocol/STParsedJSON.h>
27 #include <ripple/protocol/TxFlags.h>
28 #include <ripple/protocol/jss.h>
29 #include <ripple/resource/Fees.h>
30 #include <ripple/rpc/Context.h>
31 #include <ripple/rpc/RPCHandler.h>
32 #include <ripple/rpc/impl/RPCHelpers.h>
33 #include <ripple/rpc/impl/Tuning.h>
34 #include <chrono>
35 #include <condition_variable>
36 #include <mutex>
37 #include <test/jtx.h>
38 #include <test/jtx/envconfig.h>
39 #include <thread>
40 
41 namespace ripple {
42 namespace test {
43 
44 //------------------------------------------------------------------------------
45 
46 namespace detail {
47 
48 void
50 {
51  st.push_back(STPathElement({account.id(), std::nullopt, std::nullopt}));
52 }
53 
54 template <class T>
56 stpath_append_one(STPath& st, T const& t)
57 {
59 }
60 
61 void
63 {
64  st.push_back(STPathElement({iou.account.id(), iou.currency, std::nullopt}));
65 }
66 
67 void
69 {
70  st.push_back(pe);
71 }
72 
73 void
75 {
76  st.push_back(STPathElement({std::nullopt, book.currency, book.account}));
77 }
78 
79 template <class T, class... Args>
80 void
81 stpath_append(STPath& st, T const& t, Args const&... args)
82 {
83  stpath_append_one(st, t);
84  if constexpr (sizeof...(args) > 0)
85  stpath_append(st, args...);
86 }
87 
88 template <class... Args>
89 void
90 stpathset_append(STPathSet& st, STPath const& p, Args const&... args)
91 {
92  st.push_back(p);
93  if constexpr (sizeof...(args) > 0)
94  stpathset_append(st, args...);
95 }
96 
97 } // namespace detail
98 
99 template <class... Args>
100 STPath
101 stpath(Args const&... args)
102 {
103  STPath st;
104  detail::stpath_append(st, args...);
105  return st;
106 }
107 
108 template <class... Args>
109 bool
110 same(STPathSet const& st1, Args const&... args)
111 {
112  STPathSet st2;
113  detail::stpathset_append(st2, args...);
114  if (st1.size() != st2.size())
115  return false;
116 
117  for (auto const& p : st2)
118  {
119  if (std::find(st1.begin(), st1.end(), p) == st1.end())
120  return false;
121  }
122  return true;
123 }
124 
125 bool
126 equal(STAmount const& sa1, STAmount const& sa2)
127 {
128  return sa1 == sa2 && sa1.issue().account == sa2.issue().account;
129 }
130 
132 rpf(jtx::Account const& src, jtx::Account const& dst, std::uint32_t num_src)
133 {
135  jv[jss::command] = "ripple_path_find";
136  jv[jss::source_account] = toBase58(src);
137 
138  if (num_src > 0)
139  {
140  auto& sc = (jv[jss::source_currencies] = Json::arrayValue);
142  while (num_src--)
143  {
144  j[jss::currency] = std::to_string(num_src + 100);
145  sc.append(j);
146  }
147  }
148 
149  auto const d = toBase58(dst);
150  jv[jss::destination_account] = d;
151 
152  Json::Value& j = (jv[jss::destination_amount] = Json::objectValue);
153  j[jss::currency] = "USD";
154  j[jss::value] = "0.01";
155  j[jss::issuer] = d;
156 
157  return jv;
158 }
159 
160 // Issue path element
161 auto
162 IPE(Issue const& iss)
163 {
164  return STPathElement(
166  xrpAccount(),
167  iss.currency,
168  iss.account);
169 };
170 
171 //------------------------------------------------------------------------------
172 
173 class Path_test : public beast::unit_test::suite
174 {
175  jtx::Env
177  {
178  // These tests were originally written with search parameters that are
179  // different from the current defaults. This function creates an env
180  // with the search parameters that the tests were written for.
181  using namespace jtx;
182  return Env(*this, envconfig([](std::unique_ptr<Config> cfg) {
183  cfg->PATH_SEARCH_OLD = 7;
184  cfg->PATH_SEARCH = 7;
185  cfg->PATH_SEARCH_MAX = 10;
186  return cfg;
187  }));
188  }
189 
190 public:
191  class gate
192  {
193  private:
196  bool signaled_ = false;
197 
198  public:
199  // Thread safe, blocks until signaled or period expires.
200  // Returns `true` if signaled.
201  template <class Rep, class Period>
202  bool
204  {
206  auto b = cv_.wait_for(lk, rel_time, [this] { return signaled_; });
207  signaled_ = false;
208  return b;
209  }
210 
211  void
213  {
215  signaled_ = true;
216  cv_.notify_all();
217  }
218  };
219 
220  auto
222  jtx::Env& env,
223  jtx::Account const& src,
224  jtx::Account const& dst,
225  STAmount const& saDstAmount,
226  std::optional<STAmount> const& saSendMax = std::nullopt,
227  std::optional<Currency> const& saSrcCurrency = std::nullopt)
228  {
229  using namespace jtx;
230 
231  auto& app = env.app();
234 
235  RPC::JsonContext context{
236  {env.journal,
237  app,
238  loadType,
239  app.getOPs(),
240  app.getLedgerMaster(),
241  c,
242  Role::USER,
243  {},
244  {},
246  {},
247  {}};
248 
250  params[jss::command] = "ripple_path_find";
251  params[jss::source_account] = toBase58(src);
252  params[jss::destination_account] = toBase58(dst);
253  params[jss::destination_amount] =
254  saDstAmount.getJson(JsonOptions::none);
255  if (saSendMax)
256  params[jss::send_max] = saSendMax->getJson(JsonOptions::none);
257  if (saSrcCurrency)
258  {
259  auto& sc = params[jss::source_currencies] = Json::arrayValue;
261  j[jss::currency] = to_string(saSrcCurrency.value());
262  sc.append(j);
263  }
264 
265  Json::Value result;
266  gate g;
267  app.getJobQueue().postCoro(
268  jtCLIENT, "RPC-Client", [&](auto const& coro) {
269  context.params = std::move(params);
270  context.coro = coro;
271  RPC::doCommand(context, result);
272  g.signal();
273  });
274 
275  using namespace std::chrono_literals;
276  BEAST_EXPECT(g.wait_for(5s));
277  BEAST_EXPECT(!result.isMember(jss::error));
278  return result;
279  }
280 
283  jtx::Env& env,
284  jtx::Account const& src,
285  jtx::Account const& dst,
286  STAmount const& saDstAmount,
287  std::optional<STAmount> const& saSendMax = std::nullopt,
288  std::optional<Currency> const& saSrcCurrency = std::nullopt)
289  {
291  env, src, dst, saDstAmount, saSendMax, saSrcCurrency);
292  BEAST_EXPECT(!result.isMember(jss::error));
293 
294  STAmount da;
295  if (result.isMember(jss::destination_amount))
296  da = amountFromJson(sfGeneric, result[jss::destination_amount]);
297 
298  STAmount sa;
300  if (result.isMember(jss::alternatives))
301  {
302  auto const& alts = result[jss::alternatives];
303  if (alts.size() > 0)
304  {
305  auto const& path = alts[0u];
306 
307  if (path.isMember(jss::source_amount))
308  sa = amountFromJson(sfGeneric, path[jss::source_amount]);
309 
310  if (path.isMember(jss::destination_amount))
311  da = amountFromJson(
312  sfGeneric, path[jss::destination_amount]);
313 
314  if (path.isMember(jss::paths_computed))
315  {
316  Json::Value p;
317  p["Paths"] = path[jss::paths_computed];
318  STParsedJSONObject po("generic", p);
319  paths = po.object->getFieldPathSet(sfPaths);
320  }
321  }
322  }
323 
324  return std::make_tuple(std::move(paths), std::move(sa), std::move(da));
325  }
326 
327  void
329  {
330  testcase("source currency limits");
331  using namespace std::chrono_literals;
332  using namespace jtx;
333  Env env = pathTestEnv();
334  auto const gw = Account("gateway");
335  env.fund(XRP(10000), "alice", "bob", gw);
336  env.trust(gw["USD"](100), "alice", "bob");
337  env.close();
338 
339  auto& app = env.app();
342 
343  RPC::JsonContext context{
344  {env.journal,
345  app,
346  loadType,
347  app.getOPs(),
348  app.getLedgerMaster(),
349  c,
350  Role::USER,
351  {},
352  {},
354  {},
355  {}};
356  Json::Value result;
357  gate g;
358  // Test RPC::Tuning::max_src_cur source currencies.
359  app.getJobQueue().postCoro(
360  jtCLIENT, "RPC-Client", [&](auto const& coro) {
361  context.params = rpf(
362  Account("alice"), Account("bob"), RPC::Tuning::max_src_cur);
363  context.coro = coro;
364  RPC::doCommand(context, result);
365  g.signal();
366  });
367  BEAST_EXPECT(g.wait_for(5s));
368  BEAST_EXPECT(!result.isMember(jss::error));
369 
370  // Test more than RPC::Tuning::max_src_cur source currencies.
371  app.getJobQueue().postCoro(
372  jtCLIENT, "RPC-Client", [&](auto const& coro) {
373  context.params =
374  rpf(Account("alice"),
375  Account("bob"),
377  context.coro = coro;
378  RPC::doCommand(context, result);
379  g.signal();
380  });
381  BEAST_EXPECT(g.wait_for(5s));
382  BEAST_EXPECT(result.isMember(jss::error));
383 
384  // Test RPC::Tuning::max_auto_src_cur source currencies.
385  for (auto i = 0; i < (RPC::Tuning::max_auto_src_cur - 1); ++i)
386  env.trust(Account("alice")[std::to_string(i + 100)](100), "bob");
387  app.getJobQueue().postCoro(
388  jtCLIENT, "RPC-Client", [&](auto const& coro) {
389  context.params = rpf(Account("alice"), Account("bob"), 0);
390  context.coro = coro;
391  RPC::doCommand(context, result);
392  g.signal();
393  });
394  BEAST_EXPECT(g.wait_for(5s));
395  BEAST_EXPECT(!result.isMember(jss::error));
396 
397  // Test more than RPC::Tuning::max_auto_src_cur source currencies.
398  env.trust(Account("alice")["AUD"](100), "bob");
399  app.getJobQueue().postCoro(
400  jtCLIENT, "RPC-Client", [&](auto const& coro) {
401  context.params = rpf(Account("alice"), Account("bob"), 0);
402  context.coro = coro;
403  RPC::doCommand(context, result);
404  g.signal();
405  });
406  BEAST_EXPECT(g.wait_for(5s));
407  BEAST_EXPECT(result.isMember(jss::error));
408  }
409 
410  void
412  {
413  testcase("no direct path no intermediary no alternatives");
414  using namespace jtx;
415  Env env = pathTestEnv();
416  env.fund(XRP(10000), "alice", "bob");
417 
418  auto const result =
419  find_paths(env, "alice", "bob", Account("bob")["USD"](5));
420  BEAST_EXPECT(std::get<0>(result).empty());
421  }
422 
423  void
425  {
426  testcase("direct path no intermediary");
427  using namespace jtx;
428  Env env = pathTestEnv();
429  env.fund(XRP(10000), "alice", "bob");
430  env.trust(Account("alice")["USD"](700), "bob");
431 
432  STPathSet st;
433  STAmount sa;
434  std::tie(st, sa, std::ignore) =
435  find_paths(env, "alice", "bob", Account("bob")["USD"](5));
436  BEAST_EXPECT(st.empty());
437  BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
438  }
439 
440  void
442  {
443  testcase("payment auto path find");
444  using namespace jtx;
445  Env env = pathTestEnv();
446  auto const gw = Account("gateway");
447  auto const USD = gw["USD"];
448  env.fund(XRP(10000), "alice", "bob", gw);
449  env.trust(USD(600), "alice");
450  env.trust(USD(700), "bob");
451  env(pay(gw, "alice", USD(70)));
452  env(pay("alice", "bob", USD(24)));
453  env.require(balance("alice", USD(46)));
454  env.require(balance(gw, Account("alice")["USD"](-46)));
455  env.require(balance("bob", USD(24)));
456  env.require(balance(gw, Account("bob")["USD"](-24)));
457  }
458 
459  void
461  {
462  testcase("path find");
463  using namespace jtx;
464  Env env = pathTestEnv();
465  auto const gw = Account("gateway");
466  auto const USD = gw["USD"];
467  env.fund(XRP(10000), "alice", "bob", gw);
468  env.trust(USD(600), "alice");
469  env.trust(USD(700), "bob");
470  env(pay(gw, "alice", USD(70)));
471  env(pay(gw, "bob", USD(50)));
472 
473  STPathSet st;
474  STAmount sa;
475  std::tie(st, sa, std::ignore) =
476  find_paths(env, "alice", "bob", Account("bob")["USD"](5));
477  BEAST_EXPECT(same(st, stpath("gateway")));
478  BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
479  }
480 
481  void
483  {
484  using namespace jtx;
485  testcase("XRP to XRP");
486  Env env = pathTestEnv();
487  env.fund(XRP(10000), "alice", "bob");
488 
489  auto const result = find_paths(env, "alice", "bob", XRP(5));
490  BEAST_EXPECT(std::get<0>(result).empty());
491  }
492 
493  void
495  {
496  testcase("path find consume all");
497  using namespace jtx;
498 
499  {
500  Env env = pathTestEnv();
501  env.fund(XRP(10000), "alice", "bob", "carol", "dan", "edward");
502  env.trust(Account("alice")["USD"](10), "bob");
503  env.trust(Account("bob")["USD"](10), "carol");
504  env.trust(Account("carol")["USD"](10), "edward");
505  env.trust(Account("alice")["USD"](100), "dan");
506  env.trust(Account("dan")["USD"](100), "edward");
507 
508  STPathSet st;
509  STAmount sa;
510  STAmount da;
511  std::tie(st, sa, da) = find_paths(
512  env, "alice", "edward", Account("edward")["USD"](-1));
513  BEAST_EXPECT(same(st, stpath("dan"), stpath("bob", "carol")));
514  BEAST_EXPECT(equal(sa, Account("alice")["USD"](110)));
515  BEAST_EXPECT(equal(da, Account("edward")["USD"](110)));
516  }
517 
518  {
519  Env env = pathTestEnv();
520  auto const gw = Account("gateway");
521  auto const USD = gw["USD"];
522  env.fund(XRP(10000), "alice", "bob", "carol", gw);
523  env.trust(USD(100), "bob", "carol");
524  env(pay(gw, "carol", USD(100)));
525  env(offer("carol", XRP(100), USD(100)));
526 
527  STPathSet st;
528  STAmount sa;
529  STAmount da;
530  std::tie(st, sa, da) = find_paths(
531  env,
532  "alice",
533  "bob",
534  Account("bob")["AUD"](-1),
535  std::optional<STAmount>(XRP(100000000)));
536  BEAST_EXPECT(st.empty());
537  std::tie(st, sa, da) = find_paths(
538  env,
539  "alice",
540  "bob",
541  Account("bob")["USD"](-1),
542  std::optional<STAmount>(XRP(100000000)));
543  BEAST_EXPECT(sa == XRP(100));
544  BEAST_EXPECT(equal(da, Account("bob")["USD"](100)));
545  }
546  }
547 
548  void
550  {
551  testcase("alternative path consume both");
552  using namespace jtx;
553  Env env = pathTestEnv();
554  auto const gw = Account("gateway");
555  auto const USD = gw["USD"];
556  auto const gw2 = Account("gateway2");
557  auto const gw2_USD = gw2["USD"];
558  env.fund(XRP(10000), "alice", "bob", gw, gw2);
559  env.trust(USD(600), "alice");
560  env.trust(gw2_USD(800), "alice");
561  env.trust(USD(700), "bob");
562  env.trust(gw2_USD(900), "bob");
563  env(pay(gw, "alice", USD(70)));
564  env(pay(gw2, "alice", gw2_USD(70)));
565  env(pay("alice", "bob", Account("bob")["USD"](140)),
566  paths(Account("alice")["USD"]));
567  env.require(balance("alice", USD(0)));
568  env.require(balance("alice", gw2_USD(0)));
569  env.require(balance("bob", USD(70)));
570  env.require(balance("bob", gw2_USD(70)));
571  env.require(balance(gw, Account("alice")["USD"](0)));
572  env.require(balance(gw, Account("bob")["USD"](-70)));
573  env.require(balance(gw2, Account("alice")["USD"](0)));
574  env.require(balance(gw2, Account("bob")["USD"](-70)));
575  }
576 
577  void
579  {
580  testcase("alternative paths consume best transfer");
581  using namespace jtx;
582  Env env = pathTestEnv();
583  auto const gw = Account("gateway");
584  auto const USD = gw["USD"];
585  auto const gw2 = Account("gateway2");
586  auto const gw2_USD = gw2["USD"];
587  env.fund(XRP(10000), "alice", "bob", gw, gw2);
588  env(rate(gw2, 1.1));
589  env.trust(USD(600), "alice");
590  env.trust(gw2_USD(800), "alice");
591  env.trust(USD(700), "bob");
592  env.trust(gw2_USD(900), "bob");
593  env(pay(gw, "alice", USD(70)));
594  env(pay(gw2, "alice", gw2_USD(70)));
595  env(pay("alice", "bob", USD(70)));
596  env.require(balance("alice", USD(0)));
597  env.require(balance("alice", gw2_USD(70)));
598  env.require(balance("bob", USD(70)));
599  env.require(balance("bob", gw2_USD(0)));
600  env.require(balance(gw, Account("alice")["USD"](0)));
601  env.require(balance(gw, Account("bob")["USD"](-70)));
602  env.require(balance(gw2, Account("alice")["USD"](-70)));
603  env.require(balance(gw2, Account("bob")["USD"](0)));
604  }
605 
606  void
608  {
609  testcase("alternative paths - consume best transfer first");
610  using namespace jtx;
611  Env env = pathTestEnv();
612  auto const gw = Account("gateway");
613  auto const USD = gw["USD"];
614  auto const gw2 = Account("gateway2");
615  auto const gw2_USD = gw2["USD"];
616  env.fund(XRP(10000), "alice", "bob", gw, gw2);
617  env(rate(gw2, 1.1));
618  env.trust(USD(600), "alice");
619  env.trust(gw2_USD(800), "alice");
620  env.trust(USD(700), "bob");
621  env.trust(gw2_USD(900), "bob");
622  env(pay(gw, "alice", USD(70)));
623  env(pay(gw2, "alice", gw2_USD(70)));
624  env(pay("alice", "bob", Account("bob")["USD"](77)),
625  sendmax(Account("alice")["USD"](100)),
626  paths(Account("alice")["USD"]));
627  env.require(balance("alice", USD(0)));
628  env.require(balance("alice", gw2_USD(62.3)));
629  env.require(balance("bob", USD(70)));
630  env.require(balance("bob", gw2_USD(7)));
631  env.require(balance(gw, Account("alice")["USD"](0)));
632  env.require(balance(gw, Account("bob")["USD"](-70)));
633  env.require(balance(gw2, Account("alice")["USD"](-62.3)));
634  env.require(balance(gw2, Account("bob")["USD"](-7)));
635  }
636 
637  void
639  {
640  testcase("alternative paths - limit returned paths to best quality");
641  using namespace jtx;
642  Env env = pathTestEnv();
643  auto const gw = Account("gateway");
644  auto const USD = gw["USD"];
645  auto const gw2 = Account("gateway2");
646  auto const gw2_USD = gw2["USD"];
647  env.fund(XRP(10000), "alice", "bob", "carol", "dan", gw, gw2);
648  env(rate("carol", 1.1));
649  env.trust(Account("carol")["USD"](800), "alice", "bob");
650  env.trust(Account("dan")["USD"](800), "alice", "bob");
651  env.trust(USD(800), "alice", "bob");
652  env.trust(gw2_USD(800), "alice", "bob");
653  env.trust(Account("alice")["USD"](800), "dan");
654  env.trust(Account("bob")["USD"](800), "dan");
655  env(pay(gw2, "alice", gw2_USD(100)));
656  env(pay("carol", "alice", Account("carol")["USD"](100)));
657  env(pay(gw, "alice", USD(100)));
658 
659  STPathSet st;
660  STAmount sa;
661  std::tie(st, sa, std::ignore) =
662  find_paths(env, "alice", "bob", Account("bob")["USD"](5));
663  BEAST_EXPECT(same(
664  st,
665  stpath("gateway"),
666  stpath("gateway2"),
667  stpath("dan"),
668  stpath("carol")));
669  BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
670  }
671 
672  void
674  {
675  testcase("path negative: Issue #5");
676  using namespace jtx;
677  Env env = pathTestEnv();
678  env.fund(XRP(10000), "alice", "bob", "carol", "dan");
679  env.trust(Account("bob")["USD"](100), "alice", "carol", "dan");
680  env.trust(Account("alice")["USD"](100), "dan");
681  env.trust(Account("carol")["USD"](100), "dan");
682  env(pay("bob", "carol", Account("bob")["USD"](75)));
683  env.require(balance("bob", Account("carol")["USD"](-75)));
684  env.require(balance("carol", Account("bob")["USD"](75)));
685 
686  auto result =
687  find_paths(env, "alice", "bob", Account("bob")["USD"](25));
688  BEAST_EXPECT(std::get<0>(result).empty());
689 
690  env(pay("alice", "bob", Account("alice")["USD"](25)), ter(tecPATH_DRY));
691 
692  result = find_paths(env, "alice", "bob", Account("alice")["USD"](25));
693  BEAST_EXPECT(std::get<0>(result).empty());
694 
695  env.require(balance("alice", Account("bob")["USD"](0)));
696  env.require(balance("alice", Account("dan")["USD"](0)));
697  env.require(balance("bob", Account("alice")["USD"](0)));
698  env.require(balance("bob", Account("carol")["USD"](-75)));
699  env.require(balance("bob", Account("dan")["USD"](0)));
700  env.require(balance("carol", Account("bob")["USD"](75)));
701  env.require(balance("carol", Account("dan")["USD"](0)));
702  env.require(balance("dan", Account("alice")["USD"](0)));
703  env.require(balance("dan", Account("bob")["USD"](0)));
704  env.require(balance("dan", Account("carol")["USD"](0)));
705  }
706 
707  // alice -- limit 40 --> bob
708  // alice --> carol --> dan --> bob
709  // Balance of 100 USD Bob - Balance of 37 USD -> Rod
710  void
712  {
713  testcase("path negative: ripple-client issue #23: smaller");
714  using namespace jtx;
715  Env env = pathTestEnv();
716  env.fund(XRP(10000), "alice", "bob", "carol", "dan");
717  env.trust(Account("alice")["USD"](40), "bob");
718  env.trust(Account("dan")["USD"](20), "bob");
719  env.trust(Account("alice")["USD"](20), "carol");
720  env.trust(Account("carol")["USD"](20), "dan");
721  env(pay("alice", "bob", Account("bob")["USD"](55)),
722  paths(Account("alice")["USD"]));
723  env.require(balance("bob", Account("alice")["USD"](40)));
724  env.require(balance("bob", Account("dan")["USD"](15)));
725  }
726 
727  // alice -120 USD-> edward -25 USD-> bob
728  // alice -25 USD-> carol -75 USD -> dan -100 USD-> bob
729  void
731  {
732  testcase("path negative: ripple-client issue #23: larger");
733  using namespace jtx;
734  Env env = pathTestEnv();
735  env.fund(XRP(10000), "alice", "bob", "carol", "dan", "edward");
736  env.trust(Account("alice")["USD"](120), "edward");
737  env.trust(Account("edward")["USD"](25), "bob");
738  env.trust(Account("dan")["USD"](100), "bob");
739  env.trust(Account("alice")["USD"](25), "carol");
740  env.trust(Account("carol")["USD"](75), "dan");
741  env(pay("alice", "bob", Account("bob")["USD"](50)),
742  paths(Account("alice")["USD"]));
743  env.require(balance("alice", Account("edward")["USD"](-25)));
744  env.require(balance("alice", Account("carol")["USD"](-25)));
745  env.require(balance("bob", Account("edward")["USD"](25)));
746  env.require(balance("bob", Account("dan")["USD"](25)));
747  env.require(balance("carol", Account("alice")["USD"](25)));
748  env.require(balance("carol", Account("dan")["USD"](-25)));
749  env.require(balance("dan", Account("carol")["USD"](25)));
750  env.require(balance("dan", Account("bob")["USD"](-25)));
751  }
752 
753  // carol holds gateway AUD, sells gateway AUD for XRP
754  // bob will hold gateway AUD
755  // alice pays bob gateway AUD using XRP
756  void
758  {
759  testcase("via gateway");
760  using namespace jtx;
761  Env env = pathTestEnv();
762  auto const gw = Account("gateway");
763  auto const AUD = gw["AUD"];
764  env.fund(XRP(10000), "alice", "bob", "carol", gw);
765  env(rate(gw, 1.1));
766  env.trust(AUD(100), "bob", "carol");
767  env(pay(gw, "carol", AUD(50)));
768  env(offer("carol", XRP(50), AUD(50)));
769  env(pay("alice", "bob", AUD(10)), sendmax(XRP(100)), paths(XRP));
770  env.require(balance("bob", AUD(10)));
771  env.require(balance("carol", AUD(39)));
772 
773  auto const result =
774  find_paths(env, "alice", "bob", Account("bob")["USD"](25));
775  BEAST_EXPECT(std::get<0>(result).empty());
776  }
777 
778  void
780  {
781  testcase("path find");
782  using namespace jtx;
783  Env env = pathTestEnv();
784  env.fund(XRP(10000), "alice", "bob", "carol");
785  env.trust(Account("alice")["USD"](1000), "bob");
786  env.trust(Account("bob")["USD"](1000), "carol");
787 
788  STPathSet st;
789  STAmount sa;
790  std::tie(st, sa, std::ignore) =
791  find_paths(env, "alice", "carol", Account("carol")["USD"](5));
792  BEAST_EXPECT(same(st, stpath("bob")));
793  BEAST_EXPECT(equal(sa, Account("alice")["USD"](5)));
794  }
795 
796  void
798  {
799  testcase("quality set and test");
800  using namespace jtx;
801  Env env = pathTestEnv();
802  env.fund(XRP(10000), "alice", "bob");
803  env(trust("bob", Account("alice")["USD"](1000)),
804  json("{\"" + sfQualityIn.fieldName + "\": 2000}"),
805  json("{\"" + sfQualityOut.fieldName + "\": 1400000000}"));
806 
807  Json::Value jv;
809  R"({
810  "Balance" : {
811  "currency" : "USD",
812  "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
813  "value" : "0"
814  },
815  "Flags" : 131072,
816  "HighLimit" : {
817  "currency" : "USD",
818  "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
819  "value" : "1000"
820  },
821  "HighNode" : "0",
822  "HighQualityIn" : 2000,
823  "HighQualityOut" : 1400000000,
824  "LedgerEntryType" : "RippleState",
825  "LowLimit" : {
826  "currency" : "USD",
827  "issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
828  "value" : "0"
829  },
830  "LowNode" : "0"
831  })",
832  jv);
833 
834  auto const jv_l =
835  env.le(keylet::line(
836  Account("bob").id(), Account("alice")["USD"].issue()))
837  ->getJson(JsonOptions::none);
838  for (auto it = jv.begin(); it != jv.end(); ++it)
839  BEAST_EXPECT(*it == jv_l[it.memberName()]);
840  }
841 
842  void
844  {
845  testcase("trust normal clear");
846  using namespace jtx;
847  Env env = pathTestEnv();
848  env.fund(XRP(10000), "alice", "bob");
849  env.trust(Account("bob")["USD"](1000), "alice");
850  env.trust(Account("alice")["USD"](1000), "bob");
851 
852  Json::Value jv;
854  R"({
855  "Balance" : {
856  "currency" : "USD",
857  "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
858  "value" : "0"
859  },
860  "Flags" : 196608,
861  "HighLimit" : {
862  "currency" : "USD",
863  "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
864  "value" : "1000"
865  },
866  "HighNode" : "0",
867  "LedgerEntryType" : "RippleState",
868  "LowLimit" : {
869  "currency" : "USD",
870  "issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
871  "value" : "1000"
872  },
873  "LowNode" : "0"
874  })",
875  jv);
876 
877  auto const jv_l =
878  env.le(keylet::line(
879  Account("bob").id(), Account("alice")["USD"].issue()))
880  ->getJson(JsonOptions::none);
881  for (auto it = jv.begin(); it != jv.end(); ++it)
882  BEAST_EXPECT(*it == jv_l[it.memberName()]);
883 
884  env.trust(Account("bob")["USD"](0), "alice");
885  env.trust(Account("alice")["USD"](0), "bob");
886  BEAST_EXPECT(
887  env.le(keylet::line(
888  Account("bob").id(), Account("alice")["USD"].issue())) ==
889  nullptr);
890  }
891 
892  void
894  {
895  testcase("trust auto clear");
896  using namespace jtx;
897  Env env = pathTestEnv();
898  env.fund(XRP(10000), "alice", "bob");
899  env.trust(Account("bob")["USD"](1000), "alice");
900  env(pay("bob", "alice", Account("bob")["USD"](50)));
901  env.trust(Account("bob")["USD"](0), "alice");
902 
903  Json::Value jv;
905  R"({
906  "Balance" :
907  {
908  "currency" : "USD",
909  "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
910  "value" : "50"
911  },
912  "Flags" : 65536,
913  "HighLimit" :
914  {
915  "currency" : "USD",
916  "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
917  "value" : "0"
918  },
919  "HighNode" : "0",
920  "LedgerEntryType" : "RippleState",
921  "LowLimit" :
922  {
923  "currency" : "USD",
924  "issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
925  "value" : "0"
926  },
927  "LowNode" : "0"
928  })",
929  jv);
930 
931  auto const jv_l =
932  env.le(keylet::line(
933  Account("alice").id(), Account("bob")["USD"].issue()))
934  ->getJson(JsonOptions::none);
935  for (auto it = jv.begin(); it != jv.end(); ++it)
936  BEAST_EXPECT(*it == jv_l[it.memberName()]);
937 
938  env(pay("alice", "bob", Account("alice")["USD"](50)));
939  BEAST_EXPECT(
940  env.le(keylet::line(
941  Account("alice").id(), Account("bob")["USD"].issue())) ==
942  nullptr);
943  }
944 
945  void
946  path_find_01()
947  {
948  testcase("Path Find: XRP -> XRP and XRP -> IOU");
949  using namespace jtx;
950  Env env = pathTestEnv();
951  Account A1{"A1"};
952  Account A2{"A2"};
953  Account A3{"A3"};
954  Account G1{"G1"};
955  Account G2{"G2"};
956  Account G3{"G3"};
957  Account M1{"M1"};
958 
959  env.fund(XRP(100000), A1);
960  env.fund(XRP(10000), A2);
961  env.fund(XRP(1000), A3, G1, G2, G3, M1);
962  env.close();
963 
964  env.trust(G1["XYZ"](5000), A1);
965  env.trust(G3["ABC"](5000), A1);
966  env.trust(G2["XYZ"](5000), A2);
967  env.trust(G3["ABC"](5000), A2);
968  env.trust(A2["ABC"](1000), A3);
969  env.trust(G1["XYZ"](100000), M1);
970  env.trust(G2["XYZ"](100000), M1);
971  env.trust(G3["ABC"](100000), M1);
972  env.close();
973 
974  env(pay(G1, A1, G1["XYZ"](3500)));
975  env(pay(G3, A1, G3["ABC"](1200)));
976  env(pay(G2, M1, G2["XYZ"](25000)));
977  env(pay(G3, M1, G3["ABC"](25000)));
978  env.close();
979 
980  env(offer(M1, G1["XYZ"](1000), G2["XYZ"](1000)));
981  env(offer(M1, XRP(10000), G3["ABC"](1000)));
982 
983  STPathSet st;
984  STAmount sa, da;
985 
986  {
987  auto const& send_amt = XRP(10);
988  std::tie(st, sa, da) =
989  find_paths(env, A1, A2, send_amt, std::nullopt, xrpCurrency());
990  BEAST_EXPECT(equal(da, send_amt));
991  BEAST_EXPECT(st.empty());
992  }
993 
994  {
995  // no path should exist for this since dest account
996  // does not exist.
997  auto const& send_amt = XRP(200);
998  std::tie(st, sa, da) = find_paths(
999  env, A1, Account{"A0"}, send_amt, std::nullopt, xrpCurrency());
1000  BEAST_EXPECT(equal(da, send_amt));
1001  BEAST_EXPECT(st.empty());
1002  }
1003 
1004  {
1005  auto const& send_amt = G3["ABC"](10);
1006  std::tie(st, sa, da) =
1007  find_paths(env, A2, G3, send_amt, std::nullopt, xrpCurrency());
1008  BEAST_EXPECT(equal(da, send_amt));
1009  BEAST_EXPECT(equal(sa, XRP(100)));
1010  BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]))));
1011  }
1012 
1013  {
1014  auto const& send_amt = A2["ABC"](1);
1015  std::tie(st, sa, da) =
1016  find_paths(env, A1, A2, send_amt, std::nullopt, xrpCurrency());
1017  BEAST_EXPECT(equal(da, send_amt));
1018  BEAST_EXPECT(equal(sa, XRP(10)));
1019  BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]), G3)));
1020  }
1021 
1022  {
1023  auto const& send_amt = A3["ABC"](1);
1024  std::tie(st, sa, da) =
1025  find_paths(env, A1, A3, send_amt, std::nullopt, xrpCurrency());
1026  BEAST_EXPECT(equal(da, send_amt));
1027  BEAST_EXPECT(equal(sa, XRP(10)));
1028  BEAST_EXPECT(same(st, stpath(IPE(G3["ABC"]), G3, A2)));
1029  }
1030  }
1031 
1032  void
1033  path_find_02()
1034  {
1035  testcase("Path Find: non-XRP -> XRP");
1036  using namespace jtx;
1037  Env env = pathTestEnv();
1038  Account A1{"A1"};
1039  Account A2{"A2"};
1040  Account G3{"G3"};
1041  Account M1{"M1"};
1042 
1043  env.fund(XRP(1000), A1, A2, G3);
1044  env.fund(XRP(11000), M1);
1045  env.close();
1046 
1047  env.trust(G3["ABC"](1000), A1, A2);
1048  env.trust(G3["ABC"](100000), M1);
1049  env.close();
1050 
1051  env(pay(G3, A1, G3["ABC"](1000)));
1052  env(pay(G3, A2, G3["ABC"](1000)));
1053  env(pay(G3, M1, G3["ABC"](1200)));
1054  env.close();
1055 
1056  env(offer(M1, G3["ABC"](1000), XRP(10000)));
1057 
1058  STPathSet st;
1059  STAmount sa, da;
1060 
1061  auto const& send_amt = XRP(10);
1062  std::tie(st, sa, da) =
1063  find_paths(env, A1, A2, send_amt, std::nullopt, A2["ABC"].currency);
1064  BEAST_EXPECT(equal(da, send_amt));
1065  BEAST_EXPECT(equal(sa, A1["ABC"](1)));
1066  BEAST_EXPECT(same(st, stpath(G3, IPE(xrpIssue()))));
1067  }
1068 
1069  void
1070  path_find_04()
1071  {
1072  testcase("Path Find: Bitstamp and SnapSwap, liquidity with no offers");
1073  using namespace jtx;
1074  Env env = pathTestEnv();
1075  Account A1{"A1"};
1076  Account A2{"A2"};
1077  Account G1BS{"G1BS"};
1078  Account G2SW{"G2SW"};
1079  Account M1{"M1"};
1080 
1081  env.fund(XRP(1000), G1BS, G2SW, A1, A2);
1082  env.fund(XRP(11000), M1);
1083  env.close();
1084 
1085  env.trust(G1BS["HKD"](2000), A1);
1086  env.trust(G2SW["HKD"](2000), A2);
1087  env.trust(G1BS["HKD"](100000), M1);
1088  env.trust(G2SW["HKD"](100000), M1);
1089  env.close();
1090 
1091  env(pay(G1BS, A1, G1BS["HKD"](1000)));
1092  env(pay(G2SW, A2, G2SW["HKD"](1000)));
1093  // SnapSwap wants to be able to set trust line quality settings so they
1094  // can charge a fee when transactions ripple across. Liquidity
1095  // provider, via trusting/holding both accounts
1096  env(pay(G1BS, M1, G1BS["HKD"](1200)));
1097  env(pay(G2SW, M1, G2SW["HKD"](5000)));
1098  env.close();
1099 
1100  STPathSet st;
1101  STAmount sa, da;
1102 
1103  {
1104  auto const& send_amt = A2["HKD"](10);
1105  std::tie(st, sa, da) = find_paths(
1106  env, A1, A2, send_amt, std::nullopt, A2["HKD"].currency);
1107  BEAST_EXPECT(equal(da, send_amt));
1108  BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1109  BEAST_EXPECT(same(st, stpath(G1BS, M1, G2SW)));
1110  }
1111 
1112  {
1113  auto const& send_amt = A1["HKD"](10);
1114  std::tie(st, sa, da) = find_paths(
1115  env, A2, A1, send_amt, std::nullopt, A1["HKD"].currency);
1116  BEAST_EXPECT(equal(da, send_amt));
1117  BEAST_EXPECT(equal(sa, A2["HKD"](10)));
1118  BEAST_EXPECT(same(st, stpath(G2SW, M1, G1BS)));
1119  }
1120 
1121  {
1122  auto const& send_amt = A2["HKD"](10);
1123  std::tie(st, sa, da) = find_paths(
1124  env, G1BS, A2, send_amt, std::nullopt, A1["HKD"].currency);
1125  BEAST_EXPECT(equal(da, send_amt));
1126  BEAST_EXPECT(equal(sa, G1BS["HKD"](10)));
1127  BEAST_EXPECT(same(st, stpath(M1, G2SW)));
1128  }
1129 
1130  {
1131  auto const& send_amt = M1["HKD"](10);
1132  std::tie(st, sa, da) = find_paths(
1133  env, M1, G1BS, send_amt, std::nullopt, A1["HKD"].currency);
1134  BEAST_EXPECT(equal(da, send_amt));
1135  BEAST_EXPECT(equal(sa, M1["HKD"](10)));
1136  BEAST_EXPECT(st.empty());
1137  }
1138 
1139  {
1140  auto const& send_amt = A1["HKD"](10);
1141  std::tie(st, sa, da) = find_paths(
1142  env, G2SW, A1, send_amt, std::nullopt, A1["HKD"].currency);
1143  BEAST_EXPECT(equal(da, send_amt));
1144  BEAST_EXPECT(equal(sa, G2SW["HKD"](10)));
1145  BEAST_EXPECT(same(st, stpath(M1, G1BS)));
1146  }
1147  }
1148 
1149  void
1150  path_find_05()
1151  {
1152  testcase("Path Find: non-XRP -> non-XRP, same currency");
1153  using namespace jtx;
1154  Env env = pathTestEnv();
1155  Account A1{"A1"};
1156  Account A2{"A2"};
1157  Account A3{"A3"};
1158  Account A4{"A4"};
1159  Account G1{"G1"};
1160  Account G2{"G2"};
1161  Account G3{"G3"};
1162  Account G4{"G4"};
1163  Account M1{"M1"};
1164  Account M2{"M2"};
1165 
1166  env.fund(XRP(1000), A1, A2, A3, G1, G2, G3, G4);
1167  env.fund(XRP(10000), A4);
1168  env.fund(XRP(11000), M1, M2);
1169  env.close();
1170 
1171  env.trust(G1["HKD"](2000), A1);
1172  env.trust(G2["HKD"](2000), A2);
1173  env.trust(G1["HKD"](2000), A3);
1174  env.trust(G1["HKD"](100000), M1);
1175  env.trust(G2["HKD"](100000), M1);
1176  env.trust(G1["HKD"](100000), M2);
1177  env.trust(G2["HKD"](100000), M2);
1178  env.close();
1179 
1180  env(pay(G1, A1, G1["HKD"](1000)));
1181  env(pay(G2, A2, G2["HKD"](1000)));
1182  env(pay(G1, A3, G1["HKD"](1000)));
1183  env(pay(G1, M1, G1["HKD"](1200)));
1184  env(pay(G2, M1, G2["HKD"](5000)));
1185  env(pay(G1, M2, G1["HKD"](1200)));
1186  env(pay(G2, M2, G2["HKD"](5000)));
1187  env.close();
1188 
1189  env(offer(M1, G1["HKD"](1000), G2["HKD"](1000)));
1190  env(offer(M2, XRP(10000), G2["HKD"](1000)));
1191  env(offer(M2, G1["HKD"](1000), XRP(10000)));
1192 
1193  STPathSet st;
1194  STAmount sa, da;
1195 
1196  {
1197  // A) Borrow or repay --
1198  // Source -> Destination (repay source issuer)
1199  auto const& send_amt = G1["HKD"](10);
1200  std::tie(st, sa, da) = find_paths(
1201  env, A1, G1, send_amt, std::nullopt, G1["HKD"].currency);
1202  BEAST_EXPECT(st.empty());
1203  BEAST_EXPECT(equal(da, send_amt));
1204  BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1205  }
1206 
1207  {
1208  // A2) Borrow or repay --
1209  // Source -> Destination (repay destination issuer)
1210  auto const& send_amt = A1["HKD"](10);
1211  std::tie(st, sa, da) = find_paths(
1212  env, A1, G1, send_amt, std::nullopt, G1["HKD"].currency);
1213  BEAST_EXPECT(st.empty());
1214  BEAST_EXPECT(equal(da, send_amt));
1215  BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1216  }
1217 
1218  {
1219  // B) Common gateway --
1220  // Source -> AC -> Destination
1221  auto const& send_amt = A3["HKD"](10);
1222  std::tie(st, sa, da) = find_paths(
1223  env, A1, A3, send_amt, std::nullopt, G1["HKD"].currency);
1224  BEAST_EXPECT(equal(da, send_amt));
1225  BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1226  BEAST_EXPECT(same(st, stpath(G1)));
1227  }
1228 
1229  {
1230  // C) Gateway to gateway --
1231  // Source -> OB -> Destination
1232  auto const& send_amt = G2["HKD"](10);
1233  std::tie(st, sa, da) = find_paths(
1234  env, G1, G2, send_amt, std::nullopt, G1["HKD"].currency);
1235  BEAST_EXPECT(equal(da, send_amt));
1236  BEAST_EXPECT(equal(sa, G1["HKD"](10)));
1237  BEAST_EXPECT(same(
1238  st,
1239  stpath(IPE(G2["HKD"])),
1240  stpath(M1),
1241  stpath(M2),
1242  stpath(IPE(xrpIssue()), IPE(G2["HKD"]))));
1243  }
1244 
1245  {
1246  // D) User to unlinked gateway via order book --
1247  // Source -> AC -> OB -> Destination
1248  auto const& send_amt = G2["HKD"](10);
1249  std::tie(st, sa, da) = find_paths(
1250  env, A1, G2, send_amt, std::nullopt, G1["HKD"].currency);
1251  BEAST_EXPECT(equal(da, send_amt));
1252  BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1253  BEAST_EXPECT(same(
1254  st,
1255  stpath(G1, M1),
1256  stpath(G1, M2),
1257  stpath(G1, IPE(G2["HKD"])),
1258  stpath(G1, IPE(xrpIssue()), IPE(G2["HKD"]))));
1259  }
1260 
1261  {
1262  // I4) XRP bridge" --
1263  // Source -> AC -> OB to XRP -> OB from XRP -> AC -> Destination
1264  auto const& send_amt = A2["HKD"](10);
1265  std::tie(st, sa, da) = find_paths(
1266  env, A1, A2, send_amt, std::nullopt, G1["HKD"].currency);
1267  BEAST_EXPECT(equal(da, send_amt));
1268  BEAST_EXPECT(equal(sa, A1["HKD"](10)));
1269  BEAST_EXPECT(same(
1270  st,
1271  stpath(G1, M1, G2),
1272  stpath(G1, M2, G2),
1273  stpath(G1, IPE(G2["HKD"]), G2),
1274  stpath(G1, IPE(xrpIssue()), IPE(G2["HKD"]), G2)));
1275  }
1276  }
1277 
1278  void
1279  path_find_06()
1280  {
1281  testcase("Path Find: non-XRP -> non-XRP, same currency)");
1282  using namespace jtx;
1283  Env env = pathTestEnv();
1284  Account A1{"A1"};
1285  Account A2{"A2"};
1286  Account A3{"A3"};
1287  Account G1{"G1"};
1288  Account G2{"G2"};
1289  Account M1{"M1"};
1290 
1291  env.fund(XRP(11000), M1);
1292  env.fund(XRP(1000), A1, A2, A3, G1, G2);
1293  env.close();
1294 
1295  env.trust(G1["HKD"](2000), A1);
1296  env.trust(G2["HKD"](2000), A2);
1297  env.trust(A2["HKD"](2000), A3);
1298  env.trust(G1["HKD"](100000), M1);
1299  env.trust(G2["HKD"](100000), M1);
1300  env.close();
1301 
1302  env(pay(G1, A1, G1["HKD"](1000)));
1303  env(pay(G2, A2, G2["HKD"](1000)));
1304  env(pay(G1, M1, G1["HKD"](5000)));
1305  env(pay(G2, M1, G2["HKD"](5000)));
1306  env.close();
1307 
1308  env(offer(M1, G1["HKD"](1000), G2["HKD"](1000)));
1309 
1310  // E) Gateway to user
1311  // Source -> OB -> AC -> Destination
1312  auto const& send_amt = A2["HKD"](10);
1313  STPathSet st;
1314  STAmount sa, da;
1315  std::tie(st, sa, da) =
1316  find_paths(env, G1, A2, send_amt, std::nullopt, G1["HKD"].currency);
1317  BEAST_EXPECT(equal(da, send_amt));
1318  BEAST_EXPECT(equal(sa, G1["HKD"](10)));
1319  BEAST_EXPECT(same(st, stpath(M1, G2), stpath(IPE(G2["HKD"]), G2)));
1320  }
1321 
1322  void
1323  receive_max()
1324  {
1325  testcase("Receive max");
1326  using namespace jtx;
1327  auto const alice = Account("alice");
1328  auto const bob = Account("bob");
1329  auto const charlie = Account("charlie");
1330  auto const gw = Account("gw");
1331  auto const USD = gw["USD"];
1332  {
1333  // XRP -> IOU receive max
1334  Env env = pathTestEnv();
1335  env.fund(XRP(10000), alice, bob, charlie, gw);
1336  env.close();
1337  env.trust(USD(100), alice, bob, charlie);
1338  env.close();
1339  env(pay(gw, charlie, USD(10)));
1340  env.close();
1341  env(offer(charlie, XRP(10), USD(10)));
1342  env.close();
1343  auto [st, sa, da] =
1344  find_paths(env, alice, bob, USD(-1), XRP(100).value());
1345  BEAST_EXPECT(sa == XRP(10));
1346  BEAST_EXPECT(equal(da, USD(10)));
1347  if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1348  {
1349  auto const& pathElem = st[0][0];
1350  BEAST_EXPECT(
1351  pathElem.isOffer() && pathElem.getIssuerID() == gw.id() &&
1352  pathElem.getCurrency() == USD.currency);
1353  }
1354  }
1355  {
1356  // IOU -> XRP receive max
1357  Env env = pathTestEnv();
1358  env.fund(XRP(10000), alice, bob, charlie, gw);
1359  env.close();
1360  env.trust(USD(100), alice, bob, charlie);
1361  env.close();
1362  env(pay(gw, alice, USD(10)));
1363  env.close();
1364  env(offer(charlie, USD(10), XRP(10)));
1365  env.close();
1366  auto [st, sa, da] =
1367  find_paths(env, alice, bob, drops(-1), USD(100).value());
1368  BEAST_EXPECT(sa == USD(10));
1369  BEAST_EXPECT(equal(da, XRP(10)));
1370  if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1371  {
1372  auto const& pathElem = st[0][0];
1373  BEAST_EXPECT(
1374  pathElem.isOffer() &&
1375  pathElem.getIssuerID() == xrpAccount() &&
1376  pathElem.getCurrency() == xrpCurrency());
1377  }
1378  }
1379  }
1381  void
1383  {
1384  using namespace jtx;
1385  // This test will create trust lines with various values of the noRipple
1386  // flag. alice <-> george <-> bob george will sort of act like a
1387  // gateway, but use a different name to avoid the usual assumptions
1388  // about gateways.
1389  auto const alice = Account("alice");
1390  auto const bob = Account("bob");
1391  auto const george = Account("george");
1392  auto const USD = george["USD"];
1393  auto test = [&](std::string casename,
1394  bool aliceRipple,
1395  bool bobRipple,
1396  bool expectPath) {
1397  testcase(casename);
1398 
1399  Env env = pathTestEnv();
1400  env.fund(XRP(10000), noripple(alice, bob, george));
1401  env.close();
1402  // Set the same flags at both ends of the trustline, even though
1403  // only george's matter.
1404  env(trust(
1405  alice,
1406  USD(100),
1407  aliceRipple ? tfClearNoRipple : tfSetNoRipple));
1408  env(trust(
1409  george,
1410  alice["USD"](100),
1411  aliceRipple ? tfClearNoRipple : tfSetNoRipple));
1412  env(trust(
1413  bob, USD(100), bobRipple ? tfClearNoRipple : tfSetNoRipple));
1414  env(trust(
1415  george,
1416  bob["USD"](100),
1417  bobRipple ? tfClearNoRipple : tfSetNoRipple));
1418  env.close();
1419  env(pay(george, alice, USD(70)));
1420  env.close();
1421 
1422  auto [st, sa, da] =
1423  find_paths(env, "alice", "bob", Account("bob")["USD"](5));
1424  BEAST_EXPECT(equal(da, bob["USD"](5)));
1425 
1426  if (expectPath)
1427  {
1428  BEAST_EXPECT(st.size() == 1);
1429  BEAST_EXPECT(same(st, stpath("george")));
1430  BEAST_EXPECT(equal(sa, alice["USD"](5)));
1431  }
1432  else
1433  {
1434  BEAST_EXPECT(st.size() == 0);
1435  BEAST_EXPECT(equal(sa, XRP(0)));
1436  }
1437  };
1438  test("ripple -> ripple", true, true, true);
1439  test("ripple -> no ripple", true, false, true);
1440  test("no ripple -> ripple", false, true, true);
1441  test("no ripple -> no ripple", false, false, false);
1442  }
1443 
1444  void
1445  run() override
1446  {
1451  path_find();
1465  xrp_to_xrp();
1466  receive_max();
1468 
1469  // The following path_find_NN tests are data driven tests
1470  // that were originally implemented in js/coffee and migrated
1471  // here. The quantities and currencies used are taken directly from
1472  // those legacy tests, which in some cases probably represented
1473  // customer use cases.
1474 
1475  path_find_01();
1476  path_find_02();
1477  path_find_04();
1478  path_find_05();
1479  path_find_06();
1480  }
1481 };
1482 
1483 BEAST_DEFINE_TESTSUITE(Path, app, ripple);
1484 
1485 } // namespace test
1486 } // namespace ripple
ripple::test::Path_test::path_find_01
void path_find_01()
Definition: Path_test.cpp:881
ripple::test::Path_test::quality_paths_quality_set_and_test
void quality_paths_quality_set_and_test()
Definition: Path_test.cpp:797
ripple::test::jtx::json
Inject raw JSON.
Definition: jtx_json.h:31
ripple::test::Path_test::direct_path_no_intermediary
void direct_path_no_intermediary()
Definition: Path_test.cpp:424
ripple::RPC::apiVersionIfUnspecified
constexpr unsigned int apiVersionIfUnspecified
Definition: RPCHelpers.h:242
ripple::sfPaths
const SField sfPaths
ripple::RPC::JsonContext
Definition: Context.h:53
std::make_tuple
T make_tuple(T... args)
ripple::test::jtx::XRP
const XRP_t XRP
Converts to XRP Issue or STAmount.
Definition: amount.cpp:105
ripple::STPath::push_back
void push_back(STPathElement const &e)
Definition: STPathSet.h:405
ripple::test::Path_test::gate
Definition: Path_test.cpp:191
ripple::Issue
A currency issued by an account.
Definition: Issue.h:34
std::string
STL class.
ripple::jtCLIENT
@ jtCLIENT
Definition: Job.h:45
ripple::sfGeneric
const SField sfGeneric(access, 0)
Definition: SField.h:327
ripple::STAmount::issue
Issue const & issue() const
Definition: STAmount.h:347
ripple::test::jtx::ter
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition: ter.h:33
ripple::test::Path_test::gate::signaled_
bool signaled_
Definition: Path_test.cpp:196
ripple::test::jtx::Env::require
void require(Args const &... args)
Check a set of requirements.
Definition: Env.h:466
ripple::test::Path_test::trust_auto_clear_trust_auto_clear
void trust_auto_clear_trust_auto_clear()
Definition: Path_test.cpp:851
Json::arrayValue
@ arrayValue
array value (ordered list)
Definition: json_value.h:42
ripple::sfQualityOut
const SF_UINT32 sfQualityOut
ripple::test::jtx::balance
A balance matches.
Definition: balance.h:38
ripple::test::Path_test::source_currencies_limit
void source_currencies_limit()
Definition: Path_test.cpp:328
ripple::test::jtx::BookSpec::currency
ripple::Currency currency
Definition: amount.h:158
ripple::test::Path_test::run
void run() override
Definition: Path_test.cpp:1380
std::find
T find(T... args)
ripple::test::Path_test::gate::wait_for
bool wait_for(std::chrono::duration< Rep, Period > const &rel_time)
Definition: Path_test.cpp:203
ripple::test::Path_test::path_find
void path_find()
Definition: Path_test.cpp:460
ripple::test::jtx::trust
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition: trust.cpp:30
ripple::SField::fieldName
const std::string fieldName
Definition: SField.h:132
ripple::tfSetNoRipple
constexpr std::uint32_t tfSetNoRipple
Definition: TxFlags.h:109
ripple::STAmount::getJson
Json::Value getJson(JsonOptions) const override
Definition: STAmount.cpp:655
ripple::test::Path_test::path_find_04
void path_find_04()
Definition: Path_test.cpp:1005
std::chrono::duration
ripple::keylet::offer
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition: Indexes.cpp:222
ripple::Issue::currency
Currency currency
Definition: Issue.h:37
ripple::toBase58
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition: AccountID.cpp:104
ripple::test::Path_test
Definition: Path_test.cpp:173
ripple::sfQualityIn
const SF_UINT32 sfQualityIn
ripple::test::jtx::Env::journal
const beast::Journal journal
Definition: Env.h:144
ripple::test::Path_test::issues_path_negative_ripple_client_issue_23_smaller
void issues_path_negative_ripple_client_issue_23_smaller()
Definition: Path_test.cpp:711
std::lock_guard
STL class.
ripple::STParsedJSONObject
Holds the serialized result of parsing an input JSON object.
Definition: STParsedJSON.h:31
ripple::Resource::feeReferenceRPC
const Charge feeReferenceRPC
std::tuple
ripple::test::Path_test::path_find_05
void path_find_05()
Definition: Path_test.cpp:1085
ripple::test::Path_test::issues_path_negative_issue
void issues_path_negative_issue()
Definition: Path_test.cpp:673
ripple::test::jtx::Env::app
Application & app()
Definition: Env.h:241
Json::Value::end
const_iterator end() const
Definition: json_value.cpp:1064
ripple::test::same
bool same(STPathSet const &st1, Args const &... args)
Definition: Path_test.cpp:110
ripple::test::jtx::IOU::account
Account account
Definition: amount.h:294
ripple::test::Path_test::xrp_to_xrp
void xrp_to_xrp()
Definition: Path_test.cpp:482
Json::Reader
Unserialize a JSON document into a Value.
Definition: json_reader.h:36
ripple::test::jtx::envconfig
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition: envconfig.h:49
ripple::STParsedJSONObject::object
std::optional< STObject > object
The STObject if the parse was successful.
Definition: STParsedJSON.h:50
ripple::STPathSet::end
std::vector< STPath >::const_iterator end() const
Definition: STPathSet.h:491
ripple::test::Path_test::alternative_paths_limit_returned_paths_to_best_quality
void alternative_paths_limit_returned_paths_to_best_quality()
Definition: Path_test.cpp:638
ripple::test::Path_test::gate::mutex_
std::mutex mutex_
Definition: Path_test.cpp:195
ripple::test::Path_test::alternative_paths_consume_best_transfer
void alternative_paths_consume_best_transfer()
Definition: Path_test.cpp:578
ripple::STPathElement::typeCurrency
@ typeCurrency
Definition: STPathSet.h:49
ripple::STPathSet
Definition: STPathSet.h:176
ripple::test::jtx::Env::trust
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition: Env.cpp:259
ripple::test::jtx::BookSpec
Definition: amount.h:155
ripple::STPathSet::empty
bool empty() const
Definition: STPathSet.h:503
ripple::STPathElement::typeIssuer
@ typeIssuer
Definition: STPathSet.h:50
ripple::test::Path_test::path_find_consume_all
void path_find_consume_all()
Definition: Path_test.cpp:494
ripple::test::jtx::Account::id
AccountID id() const
Returns the Account ID.
Definition: Account.h:106
std::tie
T tie(T... args)
ripple::test::stpath
STPath stpath(Args const &... args)
Definition: Path_test.cpp:101
ripple::test::Path_test::payment_auto_path_find
void payment_auto_path_find()
Definition: Path_test.cpp:441
ripple::test::rpf
Json::Value rpf(jtx::Account const &src, jtx::Account const &dst, std::uint32_t num_src)
Definition: Path_test.cpp:132
ripple::RPC::doCommand
Status doCommand(RPC::JsonContext &context, Json::Value &result)
Execute an RPC command and store the results in a Json::Value.
Definition: RPCHandler.cpp:250
thread
ripple::STPathSet::size
std::vector< STPath >::size_type size() const
Definition: STPathSet.h:497
Json::objectValue
@ objectValue
object value (collection of name/value pairs).
Definition: json_value.h:43
ripple::test::Path_test::alternative_path_consume_both
void alternative_path_consume_both()
Definition: Path_test.cpp:549
ripple::test::Path_test::no_direct_path_no_intermediary_no_alternatives
void no_direct_path_no_intermediary_no_alternatives()
Definition: Path_test.cpp:411
ripple::test::detail::stpath_append
void stpath_append(STPath &st, T const &t, Args const &... args)
Definition: Path_test.cpp:81
std::enable_if_t
ripple::Role::USER
@ USER
ripple::test::Path_test::alternative_paths_consume_best_transfer_first
void alternative_paths_consume_best_transfer_first()
Definition: Path_test.cpp:607
chrono
ripple::RPC::Tuning::max_src_cur
static constexpr int max_src_cur
Maximum number of source currencies allowed in a path find request.
Definition: rpc/impl/Tuning.h:81
ripple::JsonOptions::none
@ none
ripple::test::jtx::sendmax
Sets the SendMax on a JTx.
Definition: sendmax.h:31
ripple::test::Path_test::path_find_06
void path_find_06()
Definition: Path_test.cpp:1214
std::unique_lock
STL class.
std::to_string
T to_string(T... args)
ripple::test::jtx::paths
Set Paths, SendMax on a JTx.
Definition: paths.h:32
ripple::test::jtx::Env::close
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition: Env.cpp:121
ripple::test::jtx::IOU::currency
ripple::Currency currency
Definition: amount.h:295
ripple::STAmount
Definition: STAmount.h:45
ripple::xrpAccount
AccountID const & xrpAccount()
Compute AccountID from public key.
Definition: AccountID.cpp:168
ripple::tfClearNoRipple
constexpr std::uint32_t tfClearNoRipple
Definition: TxFlags.h:110
ripple::amountFromJson
STAmount amountFromJson(SField const &name, Json::Value const &v)
Definition: STAmount.cpp:929
ripple::test::jtx::path
Add a path.
Definition: paths.h:55
Json::Value::isMember
bool isMember(const char *key) const
Return true if the object has a member named key.
Definition: json_value.cpp:932
std::uint32_t
ripple::keylet::line
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:193
ripple::test::Path_test::gate::cv_
std::condition_variable cv_
Definition: Path_test.cpp:194
ripple::test::Path_test::gate::signal
void signal()
Definition: Path_test.cpp:212
std::condition_variable::wait_for
T wait_for(T... args)
ripple::test::equal
bool equal(STAmount const &sa1, STAmount const &sa2)
Definition: Path_test.cpp:126
ripple::test::jtx::BookSpec::account
AccountID account
Definition: amount.h:157
ripple
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition: RCLCensorshipDetector.h:29
ripple::test::jtx::noripple
std::array< Account, 1+sizeof...(Args)> noripple(Account const &account, Args const &... args)
Designate accounts as no-ripple in Env::fund.
Definition: Env.h:64
ripple::test::detail::stpath_append_one
void stpath_append_one(STPath &st, jtx::Account const &account)
Definition: Path_test.cpp:49
ripple::test::jtx::IOU
Converts to IOU Issue or STAmount.
Definition: amount.h:291
ripple::test::jtx::pay
Json::Value pay(Account const &account, Account const &to, AnyAmount amount)
Create a payment.
Definition: pay.cpp:29
ripple::test::Path_test::find_paths
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)
Definition: Path_test.cpp:282
ripple::STPathSet::begin
std::vector< STPath >::const_iterator begin() const
Definition: STPathSet.h:485
ripple::STPathElement
Definition: STPathSet.h:34
ripple::test::jtx::Env::fund
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition: Env.cpp:228
ripple::test::jtx::Env::le
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition: Env.cpp:216
ripple::test::Path_test::receive_max
void receive_max()
Definition: Path_test.cpp:1258
ripple::RPC::Tuning::max_auto_src_cur
static constexpr int max_auto_src_cur
Maximum number of auto source currencies in a path find request.
Definition: rpc/impl/Tuning.h:84
Json::Reader::parse
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Definition: json_reader.cpp:74
condition_variable
ripple::Resource::Consumer
An endpoint that consumes resources.
Definition: Consumer.h:34
ripple::Resource::Charge
A consumption charge.
Definition: Charge.h:30
ripple::test::IPE
auto IPE(Issue const &iss)
Definition: Path_test.cpp:162
ripple::test::Path_test::noripple_combinations
void noripple_combinations()
Definition: Path_test.cpp:1317
ripple::test::Path_test::find_paths_request
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)
Definition: Path_test.cpp:221
ripple::test::Path_test::via_offers_via_gateway
void via_offers_via_gateway()
Definition: Path_test.cpp:757
ripple::tecPATH_DRY
@ tecPATH_DRY
Definition: TER.h:261
ripple::test::Path_test::issues_path_negative_ripple_client_issue_23_larger
void issues_path_negative_ripple_client_issue_23_larger()
Definition: Path_test.cpp:730
ripple::xrpIssue
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition: Issue.h:95
ripple::test::detail::stpathset_append
void stpathset_append(STPathSet &st, STPath const &p, Args const &... args)
Definition: Path_test.cpp:90
std::optional< STAmount >
mutex
ripple::test::Path_test::trust_auto_clear_trust_normal_clear
void trust_auto_clear_trust_normal_clear()
Definition: Path_test.cpp:821
ripple::to_string
std::string to_string(Manifest const &m)
Format the specified manifest to a string for debugging purposes.
Definition: app/misc/impl/Manifest.cpp:41
ripple::test::jtx::Account
Immutable cryptographic account descriptor.
Definition: Account.h:37
ripple::test::Path_test::path_find_02
void path_find_02()
Definition: Path_test.cpp:968
ripple::test::Path_test::indirect_paths_path_find
void indirect_paths_path_find()
Definition: Path_test.cpp:779
Json::Value::begin
const_iterator begin() const
Definition: json_value.cpp:1046
std::unique_ptr
STL class.
ripple::STPath
Definition: STPathSet.h:118
std::condition_variable::notify_all
T notify_all(T... args)
ripple::test::Path_test::pathTestEnv
jtx::Env pathTestEnv()
Definition: Path_test.cpp:176
ripple::xrpCurrency
Currency const & xrpCurrency()
XRP currency.
Definition: UintTypes.cpp:121
ripple::test::jtx::Env
A transaction testing environment.
Definition: Env.h:116
ripple::Issue::account
AccountID account
Definition: Issue.h:38
Json::Value
Represents a JSON value.
Definition: json_value.h:145
ripple::test::jtx::rate
Json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
Definition: rate.cpp:30
ripple::STPathSet::push_back
void push_back(STPath const &e)
Definition: STPathSet.h:509
ripple::test::BEAST_DEFINE_TESTSUITE
BEAST_DEFINE_TESTSUITE(DeliverMin, app, ripple)