1#include <test/jtx/AMM.h>
2#include <test/jtx/AMMTest.h>
3#include <test/jtx/Account.h>
4#include <test/jtx/Env.h>
5#include <test/jtx/TestHelpers.h>
6#include <test/jtx/amount.h>
7#include <test/jtx/balance.h>
8#include <test/jtx/domain.h>
9#include <test/jtx/envconfig.h>
10#include <test/jtx/jtx_json.h>
11#include <test/jtx/offer.h>
12#include <test/jtx/owners.h>
13#include <test/jtx/paths.h>
14#include <test/jtx/pay.h>
15#include <test/jtx/permissioned_dex.h>
16#include <test/jtx/rate.h>
17#include <test/jtx/sendmax.h>
18#include <test/jtx/ter.h>
19#include <test/jtx/trust.h>
20#include <test/jtx/txflags.h>
22#include <xrpld/core/Config.h>
23#include <xrpld/rpc/RPCHandler.h>
24#include <xrpld/rpc/Role.h>
25#include <xrpld/rpc/detail/Tuning.h>
27#include <xrpl/basics/base_uint.h>
28#include <xrpl/beast/unit_test/suite.h>
29#include <xrpl/core/Job.h>
30#include <xrpl/core/JobQueue.h>
31#include <xrpl/json/json_reader.h>
32#include <xrpl/json/json_value.h>
33#include <xrpl/protocol/AccountID.h>
34#include <xrpl/protocol/ApiVersion.h>
35#include <xrpl/protocol/Indexes.h>
36#include <xrpl/protocol/Issue.h>
37#include <xrpl/protocol/SField.h>
38#include <xrpl/protocol/STAmount.h>
39#include <xrpl/protocol/STParsedJSON.h>
40#include <xrpl/protocol/STPathSet.h>
41#include <xrpl/protocol/TER.h>
42#include <xrpl/protocol/TxFlags.h>
43#include <xrpl/protocol/UintTypes.h>
44#include <xrpl/protocol/jss.h>
45#include <xrpl/resource/Charge.h>
46#include <xrpl/resource/Consumer.h>
47#include <xrpl/resource/Fees.h>
68 jv[jss::command] =
"ripple_path_find";
69 jv[jss::source_account] =
toBase58(src);
75 while ((numSrc--) != 0u)
83 jv[jss::destination_account] = d;
86 j[jss::currency] =
"USD";
87 j[jss::value] =
"0.01";
105 cfg->pathSearchOld = 7;
107 cfg->pathSearchMax = 10;
123 template <
class Rep,
class Period>
128 auto b =
cv_.wait_for(lk, relTime, [
this] {
return signaled_; });
154 auto& app = env.
app();
161 .loadType = loadType,
162 .netOps = app.getOPs(),
163 .ledgerMaster = app.getLedgerMaster(),
173 params[jss::command] =
"ripple_path_find";
174 params[jss::source_account] =
toBase58(src);
175 params[jss::destination_account] =
toBase58(dst);
183 j[jss::currency] =
to_string(saSrcCurrency.value());
187 params[jss::domain] =
to_string(*domain);
191 app.getJobQueue().postCoro(
JtClient,
"RPC-Client", [&](
auto const& coro) {
192 context.
params = std::move(params);
198 using namespace std::chrono_literals;
200 BEAST_EXPECT(!result.
isMember(jss::error));
215 findPathsRequest(env, src, dst, saDstAmount, saSendMax, saSrcCurrency, domain);
216 BEAST_EXPECT(!result.
isMember(jss::error));
219 if (result.
isMember(jss::destination_amount))
224 if (result.
isMember(jss::alternatives))
226 auto const& alts = result[jss::alternatives];
229 auto const&
path = alts[0u];
231 if (
path.isMember(jss::source_amount))
234 if (
path.isMember(jss::destination_amount))
237 if (
path.isMember(jss::paths_computed))
240 p[
"Paths"] =
path[jss::paths_computed];
244 paths = po.
object->getFieldPathSet(sfPaths);
249 return std::make_tuple(std::move(paths), std::move(sa), std::move(da));
256 using namespace std::chrono_literals;
259 auto const gw =
Account(
"gateway");
260 env.
fund(
XRP(10000),
"alice",
"bob", gw);
262 env.
trust(gw[
"USD"](100),
"alice",
"bob");
265 auto& app = env.
app();
272 .loadType = loadType,
273 .netOps = app.getOPs(),
274 .ledgerMaster = app.getLedgerMaster(),
285 app.getJobQueue().postCoro(
JtClient,
"RPC-Client", [&](
auto const& coro) {
292 BEAST_EXPECT(!result.
isMember(jss::error));
295 app.getJobQueue().postCoro(
JtClient,
"RPC-Client", [&](
auto const& coro) {
302 BEAST_EXPECT(result.
isMember(jss::error));
307 app.getJobQueue().postCoro(
JtClient,
"RPC-Client", [&](
auto const& coro) {
314 BEAST_EXPECT(!result.
isMember(jss::error));
318 app.getJobQueue().postCoro(
JtClient,
"RPC-Client", [&](
auto const& coro) {
325 BEAST_EXPECT(result.
isMember(jss::error));
331 testcase(
"no direct path no intermediary no alternatives");
334 env.
fund(
XRP(10000),
"alice",
"bob");
337 auto const result =
findPaths(env,
"alice",
"bob",
Account(
"bob")[
"USD"](5));
338 BEAST_EXPECT(std::get<0>(result).empty());
344 testcase(
"direct path no intermediary");
347 env.
fund(
XRP(10000),
"alice",
"bob");
354 BEAST_EXPECT(st.
empty());
364 auto const gw =
Account(
"gateway");
365 auto const usd = gw[
"USD"];
366 env.
fund(
XRP(10000),
"alice",
"bob", gw);
368 env.
trust(usd(600),
"alice");
369 env.
trust(usd(700),
"bob");
370 env(
pay(gw,
"alice", usd(70)));
371 env(
pay(
"alice",
"bob", usd(24)));
384 auto const gw =
Account(
"gateway");
385 auto const usd = gw[
"USD"];
386 env.
fund(
XRP(10000),
"alice",
"bob", gw);
388 env.
trust(usd(600),
"alice");
389 env.
trust(usd(700),
"bob");
390 env(
pay(gw,
"alice", usd(70)));
391 env(
pay(gw,
"bob", usd(50)));
400 env,
"alice",
"bob",
Account(
"bob")[
"USD"](5), std::nullopt, std::nullopt, domainID);
411 env.
fund(
XRP(10000),
"alice",
"bob");
419 findPaths(env,
"alice",
"bob",
XRP(5), std::nullopt, std::nullopt, domainID);
420 BEAST_EXPECT(std::get<0>(result).empty());
427 std::string(
"path find consume all") + (domainEnabled ?
" w/ " :
" w/o ") +
"domain");
432 env.
fund(
XRP(10000),
"alice",
"bob",
"carol",
"dan",
"edward");
442 domainID =
setupDomain(env, {
"alice",
"bob",
"carol",
"dan",
"edward"});
462 auto const gw =
Account(
"gateway");
463 auto const usd = gw[
"USD"];
464 env.
fund(
XRP(10000),
"alice",
"bob",
"carol", gw);
466 env.
trust(usd(100),
"bob",
"carol");
468 env(
pay(gw,
"carol", usd(100)));
474 domainID =
setupDomain(env, {
"alice",
"bob",
"carol",
"gateway"});
479 env(
offer(
"carol",
XRP(100), usd(100)));
494 BEAST_EXPECT(st.
empty());
503 BEAST_EXPECT(sa ==
XRP(100));
518 BEAST_EXPECT(st.
empty());
527 std::string(
"alternative path consume both") + (domainEnabled ?
" w/ " :
" w/o ") +
531 auto const gw =
Account(
"gateway");
532 auto const usd = gw[
"USD"];
533 auto const gw2 =
Account(
"gateway2");
534 auto const gw2Usd = gw2[
"USD"];
535 env.
fund(
XRP(10000),
"alice",
"bob", gw, gw2);
537 env.
trust(usd(600),
"alice");
538 env.
trust(gw2Usd(800),
"alice");
539 env.
trust(usd(700),
"bob");
540 env.
trust(gw2Usd(900),
"bob");
545 domainID =
setupDomain(env, {
"alice",
"bob",
"gateway",
"gateway2"});
546 env(
pay(gw,
"alice", usd(70)),
Domain(*domainID));
547 env(
pay(gw2,
"alice", gw2Usd(70)),
Domain(*domainID));
548 env(
pay(
"alice",
"bob",
Account(
"bob")[
"USD"](140)),
554 env(
pay(gw,
"alice", usd(70)));
555 env(
pay(gw2,
"alice", gw2Usd(70)));
573 std::string(
"alternative paths consume best transfer") +
574 (domainEnabled ?
" w/ " :
" w/o ") +
"domain");
577 auto const gw =
Account(
"gateway");
578 auto const usd = gw[
"USD"];
579 auto const gw2 =
Account(
"gateway2");
580 auto const gw2Usd = gw2[
"USD"];
581 env.
fund(
XRP(10000),
"alice",
"bob", gw, gw2);
584 env.
trust(usd(600),
"alice");
585 env.
trust(gw2Usd(800),
"alice");
586 env.
trust(usd(700),
"bob");
587 env.
trust(gw2Usd(900),
"bob");
592 domainID =
setupDomain(env, {
"alice",
"bob",
"gateway",
"gateway2"});
593 env(
pay(gw,
"alice", usd(70)),
Domain(*domainID));
594 env(
pay(gw2,
"alice", gw2Usd(70)),
Domain(*domainID));
595 env(
pay(
"alice",
"bob", usd(70)),
Domain(*domainID));
599 env(
pay(gw,
"alice", usd(70)));
600 env(
pay(gw2,
"alice", gw2Usd(70)));
601 env(
pay(
"alice",
"bob", usd(70)));
616 testcase(
"alternative paths - consume best transfer first");
619 auto const gw =
Account(
"gateway");
620 auto const usd = gw[
"USD"];
621 auto const gw2 =
Account(
"gateway2");
622 auto const gw2Usd = gw2[
"USD"];
623 env.
fund(
XRP(10000),
"alice",
"bob", gw, gw2);
626 env.
trust(usd(600),
"alice");
627 env.
trust(gw2Usd(800),
"alice");
628 env.
trust(usd(700),
"bob");
629 env.
trust(gw2Usd(900),
"bob");
630 env(
pay(gw,
"alice", usd(70)));
631 env(
pay(gw2,
"alice", gw2Usd(70)));
632 env(
pay(
"alice",
"bob",
Account(
"bob")[
"USD"](77)),
649 std::string(
"alternative paths - limit returned paths to best quality") +
650 (domainEnabled ?
" w/ " :
" w/o ") +
"domain");
653 auto const gw =
Account(
"gateway");
654 auto const usd = gw[
"USD"];
655 auto const gw2 =
Account(
"gateway2");
656 auto const gw2Usd = gw2[
"USD"];
657 env.
fund(
XRP(10000),
"alice",
"bob",
"carol",
"dan", gw, gw2);
659 env(
rate(
"carol", 1.1));
660 env.
trust(
Account(
"carol")[
"USD"](800),
"alice",
"bob");
662 env.
trust(usd(800),
"alice",
"bob");
663 env.
trust(gw2Usd(800),
"alice",
"bob");
667 env(
pay(gw2,
"alice", gw2Usd(100)));
669 env(
pay(
"carol",
"alice",
Account(
"carol")[
"USD"](100)));
671 env(
pay(gw,
"alice", usd(100)));
677 domainID =
setupDomain(env, {
"alice",
"bob",
"carol",
"dan", gw, gw2});
683 env,
"alice",
"bob",
Account(
"bob")[
"USD"](5), std::nullopt, std::nullopt, domainID);
693 std::string(
"path negative: Issue #5") + (domainEnabled ?
" w/ " :
" w/o ") +
"domain");
696 env.
fund(
XRP(10000),
"alice",
"bob",
"carol",
"dan");
698 env.
trust(
Account(
"bob")[
"USD"](100),
"alice",
"carol",
"dan");
701 env(
pay(
"bob",
"carol",
Account(
"bob")[
"USD"](75)));
709 domainID =
setupDomain(env, {
"alice",
"bob",
"carol",
"dan"});
713 env,
"alice",
"bob",
Account(
"bob")[
"USD"](25), std::nullopt, std::nullopt, domainID);
714 BEAST_EXPECT(std::get<0>(result).empty());
720 env,
"alice",
"bob",
Account(
"alice")[
"USD"](25), std::nullopt, std::nullopt, domainID);
721 BEAST_EXPECT(std::get<0>(result).empty());
741 testcase(
"path negative: ripple-client issue #23: smaller");
744 env.
fund(
XRP(10000),
"alice",
"bob",
"carol",
"dan");
760 testcase(
"path negative: ripple-client issue #23: larger");
763 env.
fund(
XRP(10000),
"alice",
"bob",
"carol",
"dan",
"edward");
790 auto const gw =
Account(
"gateway");
791 auto const aud = gw[
"AUD"];
792 env.
fund(
XRP(10000),
"alice",
"bob",
"carol", gw);
796 env.
trust(aud(100),
"bob",
"carol");
798 env(
pay(gw,
"carol", aud(50)));
804 domainID =
setupDomain(env, {
"alice",
"bob",
"carol", gw});
812 env(
offer(
"carol",
XRP(50), aud(50)));
822 env,
"alice",
"bob",
Account(
"bob")[
"USD"](25), std::nullopt, std::nullopt, domainID);
823 BEAST_EXPECT(std::get<0>(result).empty());
832 env.
fund(
XRP(10000),
"alice",
"bob",
"carol");
851 env.
fund(
XRP(10000),
"alice",
"bob");
854 Json(
"{\"" + sfQualityIn.fieldName +
"\": 2000}"),
855 Json(
"{\"" + sfQualityOut.fieldName +
"\": 1400000000}"));
862 "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
868 "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
872 "HighQualityIn" : 2000,
873 "HighQualityOut" : 1400000000,
874 "LedgerEntryType" : "RippleState",
877 "issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
886 for (
auto it = jv.
begin(); it != jv.
end(); ++it)
887 BEAST_EXPECT(*it == jvL[it.memberName()]);
906 "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
912 "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
916 "LedgerEntryType" : "RippleState",
919 "issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
928 for (
auto it = jv.
begin(); it != jv.
end(); ++it)
929 BEAST_EXPECT(*it == jvL[it.memberName()]);
943 env.
fund(
XRP(10000),
"alice",
"bob");
946 env(
pay(
"bob",
"alice",
Account(
"bob")[
"USD"](50)));
955 "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
962 "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
966 "LedgerEntryType" : "RippleState",
970 "issuer" : "rG1QQv2nh2gr7RCZ1P8YYcBUKCCN633jCn",
979 for (
auto it = jv.
begin(); it != jv.
end(); ++it)
980 BEAST_EXPECT(*it == jvL[it.memberName()]);
982 env(
pay(
"alice",
"bob",
Account(
"alice")[
"USD"](50)));
991 std::string(
"Path Find: XRP -> XRP and XRP -> IOU") +
992 (domainEnabled ?
" w/ " :
" w/o ") +
"domain");
1003 env.fund(
XRP(100000), a1);
1004 env.fund(
XRP(10000), a2);
1005 env.fund(
XRP(1000), a3, g1, g2, g3, m1);
1008 env.trust(g1[
"XYZ"](5000), a1);
1009 env.trust(g3[
"ABC"](5000), a1);
1010 env.trust(g2[
"XYZ"](5000), a2);
1011 env.trust(g3[
"ABC"](5000), a2);
1012 env.trust(a2[
"ABC"](1000), a3);
1013 env.trust(g1[
"XYZ"](100000), m1);
1014 env.trust(g2[
"XYZ"](100000), m1);
1015 env.trust(g3[
"ABC"](100000), m1);
1018 env(
pay(g1, a1, g1[
"XYZ"](3500)));
1019 env(
pay(g3, a1, g3[
"ABC"](1200)));
1020 env(
pay(g2, m1, g2[
"XYZ"](25000)));
1021 env(
pay(g3, m1, g3[
"ABC"](25000)));
1027 domainID =
setupDomain(env, {a1, a2, a3, g1, g2, g3, m1});
1028 env(
offer(m1, g1[
"XYZ"](1000), g2[
"XYZ"](1000)),
Domain(*domainID));
1034 env(
offer(m1, g1[
"XYZ"](1000), g2[
"XYZ"](1000)));
1035 env(
offer(m1,
XRP(10000), g3[
"ABC"](1000)));
1043 auto const& sendAmt =
XRP(10);
1046 BEAST_EXPECT(
equal(da, sendAmt));
1047 BEAST_EXPECT(st.
empty());
1053 auto const& sendAmt =
XRP(200);
1056 BEAST_EXPECT(
equal(da, sendAmt));
1057 BEAST_EXPECT(st.
empty());
1061 auto const& sendAmt = g3[
"ABC"](10);
1064 BEAST_EXPECT(
equal(da, sendAmt));
1070 auto const& sendAmt = a2[
"ABC"](1);
1073 BEAST_EXPECT(
equal(da, sendAmt));
1079 auto const& sendAmt = a3[
"ABC"](1);
1082 BEAST_EXPECT(
equal(da, sendAmt));
1092 std::string(
"Path Find: non-XRP -> XRP") + (domainEnabled ?
" w/ " :
" w/o ") +
1094 using namespace jtx;
1101 env.fund(
XRP(1000), a1, a2, g3);
1102 env.fund(
XRP(11000), m1);
1105 env.trust(g3[
"ABC"](1000), a1, a2);
1106 env.trust(g3[
"ABC"](100000), m1);
1109 env(
pay(g3, a1, g3[
"ABC"](1000)));
1110 env(
pay(g3, a2, g3[
"ABC"](1000)));
1111 env(
pay(g3, m1, g3[
"ABC"](1200)));
1114 std::optional<uint256> domainID;
1122 env(
offer(m1, g3[
"ABC"](1000),
XRP(10000)));
1127 auto const& sendAmt =
XRP(10);
1131 findPaths(env, a1, a2, sendAmt, std::nullopt, a2[
"ABC"].currency, domainID);
1132 BEAST_EXPECT(
equal(da, sendAmt));
1133 BEAST_EXPECT(
equal(sa, a1[
"ABC"](1)));
1142 findPaths(env, a1, a2, sendAmt, std::nullopt, a2[
"ABC"].currency);
1143 BEAST_EXPECT(
equal(da, sendAmt));
1144 BEAST_EXPECT(st.empty());
1152 std::string(
"Path Find: Bitstamp and SnapSwap, liquidity with no offers") +
1153 (domainEnabled ?
" w/ " :
" w/o ") +
"domain");
1154 using namespace jtx;
1162 env.fund(
XRP(1000), g1Bs, g2Sw, a1, a2);
1163 env.fund(
XRP(11000), m1);
1166 env.trust(g1Bs[
"HKD"](2000), a1);
1167 env.trust(g2Sw[
"HKD"](2000), a2);
1168 env.trust(g1Bs[
"HKD"](100000), m1);
1169 env.trust(g2Sw[
"HKD"](100000), m1);
1172 env(
pay(g1Bs, a1, g1Bs[
"HKD"](1000)));
1173 env(
pay(g2Sw, a2, g2Sw[
"HKD"](1000)));
1177 env(
pay(g1Bs, m1, g1Bs[
"HKD"](1200)));
1178 env(
pay(g2Sw, m1, g2Sw[
"HKD"](5000)));
1183 domainID =
setupDomain(env, {a1, a2, g1Bs, g2Sw, m1});
1189 auto const& sendAmt = a2[
"HKD"](10);
1191 findPaths(env, a1, a2, sendAmt, std::nullopt, a2[
"HKD"].currency, domainID);
1192 BEAST_EXPECT(
equal(da, sendAmt));
1193 BEAST_EXPECT(
equal(sa, a1[
"HKD"](10)));
1194 BEAST_EXPECT(
same(st,
stpath(g1Bs, m1, g2Sw)));
1198 auto const& sendAmt = a1[
"HKD"](10);
1200 findPaths(env, a2, a1, sendAmt, std::nullopt, a1[
"HKD"].currency, domainID);
1201 BEAST_EXPECT(
equal(da, sendAmt));
1202 BEAST_EXPECT(
equal(sa, a2[
"HKD"](10)));
1203 BEAST_EXPECT(
same(st,
stpath(g2Sw, m1, g1Bs)));
1207 auto const& sendAmt = a2[
"HKD"](10);
1209 findPaths(env, g1Bs, a2, sendAmt, std::nullopt, a1[
"HKD"].currency, domainID);
1210 BEAST_EXPECT(
equal(da, sendAmt));
1211 BEAST_EXPECT(
equal(sa, g1Bs[
"HKD"](10)));
1216 auto const& sendAmt = m1[
"HKD"](10);
1218 findPaths(env, m1, g1Bs, sendAmt, std::nullopt, a1[
"HKD"].currency, domainID);
1219 BEAST_EXPECT(
equal(da, sendAmt));
1220 BEAST_EXPECT(
equal(sa, m1[
"HKD"](10)));
1221 BEAST_EXPECT(st.empty());
1225 auto const& sendAmt = a1[
"HKD"](10);
1227 findPaths(env, g2Sw, a1, sendAmt, std::nullopt, a1[
"HKD"].currency, domainID);
1228 BEAST_EXPECT(
equal(da, sendAmt));
1229 BEAST_EXPECT(
equal(sa, g2Sw[
"HKD"](10)));
1238 std::string(
"Path Find: non-XRP -> non-XRP, same currency") +
1239 (domainEnabled ?
" w/ " :
" w/o ") +
"domain");
1240 using namespace jtx;
1253 env.fund(
XRP(1000), a1, a2, a3, g1, g2, g3, g4);
1254 env.fund(
XRP(10000), a4);
1255 env.fund(
XRP(11000), m1, m2);
1258 env.trust(g1[
"HKD"](2000), a1);
1259 env.trust(g2[
"HKD"](2000), a2);
1260 env.trust(g1[
"HKD"](2000), a3);
1261 env.trust(g1[
"HKD"](100000), m1);
1262 env.trust(g2[
"HKD"](100000), m1);
1263 env.trust(g1[
"HKD"](100000), m2);
1264 env.trust(g2[
"HKD"](100000), m2);
1267 env(
pay(g1, a1, g1[
"HKD"](1000)));
1268 env(
pay(g2, a2, g2[
"HKD"](1000)));
1269 env(
pay(g1, a3, g1[
"HKD"](1000)));
1270 env(
pay(g1, m1, g1[
"HKD"](1200)));
1271 env(
pay(g2, m1, g2[
"HKD"](5000)));
1272 env(
pay(g1, m2, g1[
"HKD"](1200)));
1273 env(
pay(g2, m2, g2[
"HKD"](5000)));
1276 std::optional<uint256> domainID;
1279 domainID =
setupDomain(env, {a1, a2, a3, a4, g1, g2, g3, g4, m1, m2});
1280 env(
offer(m1, g1[
"HKD"](1000), g2[
"HKD"](1000)),
Domain(*domainID));
1286 env(
offer(m1, g1[
"HKD"](1000), g2[
"HKD"](1000)));
1287 env(
offer(m2,
XRP(10000), g2[
"HKD"](1000)));
1288 env(
offer(m2, g1[
"HKD"](1000),
XRP(10000)));
1297 auto const& sendAmt = g1[
"HKD"](10);
1299 findPaths(env, a1, g1, sendAmt, std::nullopt, g1[
"HKD"].currency, domainID);
1300 BEAST_EXPECT(st.empty());
1301 BEAST_EXPECT(
equal(da, sendAmt));
1302 BEAST_EXPECT(
equal(sa, a1[
"HKD"](10)));
1308 auto const& sendAmt = a1[
"HKD"](10);
1310 findPaths(env, a1, g1, sendAmt, std::nullopt, g1[
"HKD"].currency, domainID);
1311 BEAST_EXPECT(st.empty());
1312 BEAST_EXPECT(
equal(da, sendAmt));
1319 auto const& sendAmt = a3[
"HKD"](10);
1321 findPaths(env, a1, a3, sendAmt, std::nullopt, g1[
"HKD"].currency, domainID);
1322 BEAST_EXPECT(
equal(da, sendAmt));
1323 BEAST_EXPECT(
equal(sa, a1[
"HKD"](10)));
1330 auto const& sendAmt = g2[
"HKD"](10);
1332 findPaths(env, g1, g2, sendAmt, std::nullopt, g1[
"HKD"].currency, domainID);
1333 BEAST_EXPECT(
equal(da, sendAmt));
1334 BEAST_EXPECT(
equal(sa, g1[
"HKD"](10)));
1346 auto const& sendAmt = g2[
"HKD"](10);
1348 findPaths(env, a1, g2, sendAmt, std::nullopt, g1[
"HKD"].currency, domainID);
1349 BEAST_EXPECT(
equal(da, sendAmt));
1350 BEAST_EXPECT(
equal(sa, a1[
"HKD"](10)));
1363 auto const& sendAmt = a2[
"HKD"](10);
1365 findPaths(env, a1, a2, sendAmt, std::nullopt, g1[
"HKD"].currency, domainID);
1366 BEAST_EXPECT(
equal(da, sendAmt));
1367 BEAST_EXPECT(
equal(sa, a1[
"HKD"](10)));
1381 std::string(
"Path Find: non-XRP -> non-XRP, same currency)") +
1382 (domainEnabled ?
" w/ " :
" w/o ") +
"domain");
1383 using namespace jtx;
1392 env.fund(
XRP(11000), m1);
1393 env.fund(
XRP(1000), a1, a2, a3, g1, g2);
1396 env.trust(g1[
"HKD"](2000), a1);
1397 env.trust(g2[
"HKD"](2000), a2);
1398 env.trust(a2[
"HKD"](2000), a3);
1399 env.trust(g1[
"HKD"](100000), m1);
1400 env.trust(g2[
"HKD"](100000), m1);
1403 env(
pay(g1, a1, g1[
"HKD"](1000)));
1404 env(
pay(g2, a2, g2[
"HKD"](1000)));
1405 env(
pay(g1, m1, g1[
"HKD"](5000)));
1406 env(
pay(g2, m1, g2[
"HKD"](5000)));
1409 std::optional<uint256> domainID;
1412 domainID =
setupDomain(env, {a1, a2, a3, g1, g2, m1});
1413 env(
offer(m1, g1[
"HKD"](1000), g2[
"HKD"](1000)),
Domain(*domainID));
1417 env(
offer(m1, g1[
"HKD"](1000), g2[
"HKD"](1000)));
1422 auto const& sendAmt = a2[
"HKD"](10);
1426 findPaths(env, g1, a2, sendAmt, std::nullopt, g1[
"HKD"].currency, domainID);
1427 BEAST_EXPECT(
equal(da, sendAmt));
1428 BEAST_EXPECT(
equal(sa, g1[
"HKD"](10)));
1435 testcase(std::string(
"Receive max") + (domainEnabled ?
" w/ " :
" w/o ") +
"domain");
1437 using namespace jtx;
1438 auto const alice =
Account(
"alice");
1439 auto const bob =
Account(
"bob");
1440 auto const charlie =
Account(
"charlie");
1441 auto const gw =
Account(
"gw");
1442 auto const usd = gw[
"USD"];
1446 env.fund(
XRP(10000), alice, bob, charlie, gw);
1448 env.trust(usd(100), alice, bob, charlie);
1450 env(
pay(gw, charlie, usd(10)));
1456 domainID =
setupDomain(env, {alice, bob, charlie, gw});
1462 env(
offer(charlie,
XRP(10), usd(10)));
1467 findPaths(env, alice, bob, usd(-1),
XRP(100).value(), std::nullopt, domainID);
1468 BEAST_EXPECT(sa ==
XRP(10));
1469 BEAST_EXPECT(
equal(da, usd(10)));
1470 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1472 auto const& pathElem = st[0][0];
1474 pathElem.isOffer() && pathElem.getIssuerID() == gw.id() &&
1475 pathElem.getCurrency() == usd.currency);
1481 env.fund(
XRP(10000), alice, bob, charlie, gw);
1483 env.trust(usd(100), alice, bob, charlie);
1485 env(
pay(gw, alice, usd(10)));
1488 std::optional<uint256> domainID;
1491 domainID =
setupDomain(env, {alice, bob, charlie, gw});
1497 env(
offer(charlie, usd(10),
XRP(10)));
1502 findPaths(env, alice, bob,
drops(-1), usd(100).value(), std::nullopt, domainID);
1503 BEAST_EXPECT(sa == usd(10));
1505 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
1507 auto const& pathElem = st[0][0];
1509 pathElem.isOffer() && pathElem.getIssuerID() ==
xrpAccount() &&
1518 using namespace jtx;
1523 auto const alice =
Account(
"alice");
1524 auto const bob =
Account(
"bob");
1525 auto const george =
Account(
"george");
1526 auto const usd = george[
"USD"];
1527 auto test = [&](
std::string casename,
bool aliceRipple,
bool bobRipple,
bool expectPath) {
1531 env.fund(
XRP(10000),
noripple(alice, bob, george));
1535 env(
trust(alice, usd(100), aliceRipple ? tfClearNoRipple : tfSetNoRipple));
1536 env(
trust(george, alice[
"USD"](100), aliceRipple ? tfClearNoRipple : tfSetNoRipple));
1537 env(
trust(bob, usd(100), bobRipple ? tfClearNoRipple : tfSetNoRipple));
1538 env(
trust(george, bob[
"USD"](100), bobRipple ? tfClearNoRipple : tfSetNoRipple));
1540 env(
pay(george, alice, usd(70)));
1543 auto [st, sa, da] =
findPaths(env,
"alice",
"bob",
Account(
"bob")[
"USD"](5));
1544 BEAST_EXPECT(
equal(da, bob[
"USD"](5)));
1548 BEAST_EXPECT(st.size() == 1);
1550 BEAST_EXPECT(
equal(sa, alice[
"USD"](5)));
1554 BEAST_EXPECT(st.empty());
1558 test(
"ripple -> ripple",
true,
true,
true);
1559 test(
"ripple -> no ripple",
true,
false,
true);
1560 test(
"no ripple -> ripple",
false,
true,
true);
1561 test(
"no ripple -> no ripple",
false,
false,
false);
1568 using namespace jtx;
1573 auto testPathfind = [&](
auto func,
bool const domainEnabled =
false) {
1586 env.fund(
XRP(1000), a1, a2, a3, g1, g2, g3, g4);
1587 env.fund(
XRP(10000), a4);
1588 env.fund(
XRP(11000), m1, m2);
1591 env.trust(g1[
"HKD"](2000), a1);
1592 env.trust(g2[
"HKD"](2000), a2);
1593 env.trust(g1[
"HKD"](2000), a3);
1594 env.trust(g1[
"HKD"](100000), m1);
1595 env.trust(g2[
"HKD"](100000), m1);
1596 env.trust(g1[
"HKD"](100000), m2);
1597 env.trust(g2[
"HKD"](100000), m2);
1600 env(
pay(g1, a1, g1[
"HKD"](1000)));
1601 env(
pay(g2, a2, g2[
"HKD"](1000)));
1602 env(
pay(g1, a3, g1[
"HKD"](1000)));
1603 env(
pay(g1, m1, g1[
"HKD"](1200)));
1604 env(
pay(g2, m1, g2[
"HKD"](5000)));
1605 env(
pay(g1, m2, g1[
"HKD"](1200)));
1606 env(
pay(g2, m2, g2[
"HKD"](5000)));
1609 std::optional<uint256> domainID =
1610 setupDomain(env, {a1, a2, a3, a4, g1, g2, g3, g4, m1, m2});
1611 BEAST_EXPECT(domainID);
1613 func(env, m1, m2, g1, g2, *domainID);
1621 auto const& sendAmt = g1[
"HKD"](10);
1629 domainEnabled ? domainID : std::nullopt);
1630 BEAST_EXPECT(st.empty());
1631 BEAST_EXPECT(
equal(da, sendAmt));
1632 BEAST_EXPECT(
equal(sa, a1[
"HKD"](10)));
1638 auto const& sendAmt = a1[
"HKD"](10);
1646 domainEnabled ? domainID : std::nullopt);
1647 BEAST_EXPECT(st.empty());
1648 BEAST_EXPECT(
equal(da, sendAmt));
1649 BEAST_EXPECT(
equal(sa, a1[
"HKD"](10)));
1655 auto const& sendAmt = a3[
"HKD"](10);
1663 domainEnabled ? domainID : std::nullopt);
1664 BEAST_EXPECT(
equal(da, sendAmt));
1665 BEAST_EXPECT(
equal(sa, a1[
"HKD"](10)));
1672 auto const& sendAmt = g2[
"HKD"](10);
1680 domainEnabled ? domainID : std::nullopt);
1681 BEAST_EXPECT(
equal(da, sendAmt));
1682 BEAST_EXPECT(
equal(sa, g1[
"HKD"](10)));
1694 auto const& sendAmt = g2[
"HKD"](10);
1702 domainEnabled ? domainID : std::nullopt);
1703 BEAST_EXPECT(
equal(da, sendAmt));
1704 BEAST_EXPECT(
equal(sa, a1[
"HKD"](10)));
1717 auto const& sendAmt = a2[
"HKD"](10);
1725 domainEnabled ? domainID : std::nullopt);
1726 BEAST_EXPECT(
equal(da, sendAmt));
1727 BEAST_EXPECT(
equal(sa, a1[
"HKD"](10)));
1743 env(
offer(m1, g1[
"HKD"](1000), g2[
"HKD"](1000)),
1746 env(
offer(m2,
XRP(10000), g2[
"HKD"](1000)));
1747 env(
offer(m2, g1[
"HKD"](1000),
XRP(10000)));
1752 env(
offer(m1, g1[
"HKD"](1000), g2[
"HKD"](1000)),
1755 env(
offer(m2,
XRP(10000), g2[
"HKD"](1000)),
1758 env(
offer(m2, g1[
"HKD"](1000),
XRP(10000)));
1763 env(
offer(m1, g1[
"HKD"](1000), g2[
"HKD"](1000)),
1766 env(
offer(m2,
XRP(10000), g2[
"HKD"](1000)),
1769 env(
offer(m2, g1[
"HKD"](1000),
XRP(10000)),
1776 env(
offer(m1, g1[
"HKD"](1000), g2[
"HKD"](1000)));
1777 env(
offer(m2,
XRP(10000), g2[
"HKD"](1000)));
1778 env(
offer(m2, g1[
"HKD"](1000),
XRP(10000)),
1785 env(
offer(m1, g1[
"HKD"](1000), g2[
"HKD"](1000)));
1786 env(
offer(m2,
XRP(10000), g2[
"HKD"](1000)),
1789 env(
offer(m2, g1[
"HKD"](1000),
XRP(10000)),
1801 env(
offer(m1, g1[
"HKD"](1000), g2[
"HKD"](1000)),
1811 env(
offer(m1, g1[
"HKD"](1000), g2[
"HKD"](1000)),
1814 env(
offer(m2,
XRP(10000), g2[
"HKD"](1000)),
1823 env(
offer(m1, g1[
"HKD"](1000), g2[
"HKD"](1000)),
Domain(domainID));
1825 env(
offer(m2, g1[
"HKD"](1000),
XRP(10000)),
1833 env(
offer(m1, g1[
"HKD"](1000), g2[
"HKD"](1000)),
Domain(domainID));
1834 env(
offer(m2,
XRP(10000), g2[
"HKD"](1000)),
1837 env(
offer(m2, g1[
"HKD"](1000),
XRP(10000)),
1848 testcase(
"AMM not used in domain path");
1849 using namespace jtx;
1851 PermissionedDEX
const permDex(env);
1852 auto const& [gw_, domainOwner, alice_, bob_, carol_, USD, domainID, credType] = permDex;
1853 AMM
const amm(env, alice_,
XRP(10), USD(50));
1858 auto const& sendAmt =
XRP(1);
1862 findPaths(env, bob_, carol_, sendAmt, std::nullopt, USD.currency, domainID);
1863 BEAST_EXPECT(st.empty());
1866 std::tie(st, sa, da) =
findPaths(env, bob_, carol_, sendAmt, std::nullopt, USD.currency);
1873 testcase(
"STPathSet::assembleAdd deduplication — O(N^2) regression");
1875 static constexpr std::string_view kAccount1 =
"A3F19C7B2E5D08146FB93A7C0E2D5184BC6F3A09";
1876 static constexpr std::string_view kAccount2 =
"1D7E4B90C2A6F3851E0B9D47A2C5F8136E0A4B7D";
1877 static constexpr std::string_view kAccount3 =
"F08C36A1D95E27B40CA1F63E8D204B7950E1C3A6";
1878 static constexpr std::string_view kAccount4 =
"4B6209E7F1A3C85D0E94B27Af3D6018C5A7E92B4";
1879 static constexpr std::string_view kAccount5 =
"9E2D7041BCA3F6589D013E7B2A4C6F80159D3E7A";
1880 static constexpr std::string_view kAccount6 =
"7C5A91E384F2D06BA19C4E73D820F516B3A9C0E4";
1881 static constexpr std::string_view kAccount7 =
"2F8B043C6A1E9D75B0C38E14F6A2D509731BC4E8";
1882 static constexpr std::string_view kAccount8 =
"E61D9A30F47C285BA0D31E96C7B4F802513A8D6F";
1884 static constexpr AccountID kAccountID1{kAccount1};
1885 static constexpr AccountID kAccountID2{kAccount2};
1886 static constexpr AccountID kAccountID3{kAccount3};
1887 static constexpr AccountID kAccountID4{kAccount4};
1888 static constexpr AccountID kAccountID5{kAccount5};
1889 static constexpr AccountID kAccountID6{kAccount6};
1890 static constexpr AccountID kAccountID7{kAccount7};
1891 static constexpr AccountID kAccountID8{kAccount8};
1893 auto ps = STPathSet{};
1895 auto createPathElements = [](
auto const& account1,
auto const& account2) {
1896 auto base = STPath{};
1905 auto [base, tail] = createPathElements(kAccountID1, kAccountID2);
1907 for (
auto i = 0uz; i < 10000; ++i)
1909 ps.assembleAdd(base, tail);
1912 BEAST_EXPECT(ps.size() == 1);
1916 auto [base, tail] = createPathElements(kAccountID3, kAccountID4);
1917 ps.assembleAdd(base, tail);
1921 auto [base, tail] = createPathElements(kAccountID5, kAccountID6);
1922 ps.assembleAdd(base, tail);
1926 auto [base, tail] = createPathElements(kAccountID7, kAccountID8);
1928 auto before = ps.size();
1930 for (
auto i = 0uz; i < 10000; ++i)
1932 ps.assembleAdd(base, tail);
1935 BEAST_EXPECT(ps.size() - before == 1);
1939 auto [base, tail] = createPathElements(kAccountID1, kAccountID3);
1941 copy.pushBack(tail);
1943 auto before = ps.size();
1946 ps.assembleAdd(base, tail);
1948 BEAST_EXPECT(ps.size() - before == 1);
1952 auto [base, tail] = createPathElements(kAccountID2, kAccountID4);
1954 copy.pushBack(tail);
1956 auto before = ps.size();
1958 ps.emplaceBack(copy);
1959 ps.assembleAdd(base, tail);
1961 BEAST_EXPECT(ps.size() - before == 1);
1964 BEAST_EXPECT(ps.size() == 6);
1984 for (
bool const domainEnabled : {
false,
true})
TestcaseT testcase
Memberspace for declaring test cases.
Unserialize a JSON document into a Value.
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
const_iterator begin() const
const_iterator end() const
bool isMember(char const *key) const
Return true if the object has a member named key.
json::Value getJson(JsonOptions=JsonOptions::Values::None) const override
Holds the serialized result of parsing an input JSON object.
std::optional< STObject > object
The STObject if the parse was successful.
An endpoint that consumes resources.
std::condition_variable cv_
bool waitFor(std::chrono::duration< Rep, Period > const &relTime)
void pathFind06(bool const domainEnabled)
void issuesPathNegativeRippleClientIssue23Larger()
void testAssembleAddDeduplication()
void run() override
Runs the suite.
void issuesPathNegativeIssue(bool const domainEnabled)
void alternativePathsConsumeBestTransferFirst()
void xrpToXrp(bool const domainEnabled)
void pathFind01(bool const domainEnabled)
void pathFind04(bool const domainEnabled)
void receiveMax(bool const domainEnabled)
void alternativePathsLimitReturnedPathsToBestQuality(bool const domainEnabled)
void pathFind05(bool const domainEnabled)
void viaOffersViaGateway(bool const domainEnabled)
void trustAutoClearTrustNormalClear()
void paymentAutoPathFind()
void directPathNoIntermediary()
void alternativePathConsumeBoth(bool const domainEnabled)
void norippleCombinations()
void issuesPathNegativeRippleClientIssue23Smaller()
auto findPathsRequest(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax=std::nullopt, std::optional< Currency > const &saSrcCurrency=std::nullopt, std::optional< uint256 > const &domain=std::nullopt)
void pathFind02(bool const domainEnabled)
void indirectPathsPathFind()
void noDirectPathNoIntermediaryNoAlternatives()
void qualityPathsQualitySetAndTest()
void pathFind(bool const domainEnabled)
std::tuple< STPathSet, STAmount, STAmount > findPaths(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax=std::nullopt, std::optional< Currency > const &saSrcCurrency=std::nullopt, std::optional< uint256 > const &domain=std::nullopt)
void pathFindConsumeAll(bool const domainEnabled)
void trustAutoClearTrustAutoClear()
void alternativePathsConsumeBestTransfer(bool const domainEnabled)
void sourceCurrenciesLimit()
Immutable cryptographic account descriptor.
A transaction testing environment.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
SLE::const_pointer le(Account const &account) const
Return an account root.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
beast::Journal const journal
void require(Args const &... args)
Check a set of requirements.
Set Paths, SendMax on a JTx.
Sets the SendMax on a JTx.
Set the expected result code for a JTx The test will fail if the code doesn't match.
@ Array
array value (ordered list)
@ Object
object value (collection of name/value pairs).
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const ¤cy) noexcept
The index of a trust line for a given currency.
Charge const kFeeReferenceRpc
static constexpr int kMaxSrcCur
Maximum number of source currencies allowed in a path find request.
static constexpr int kMaxAutoSrcCur
Maximum number of auto source currencies in a path find request.
static constexpr auto kApiVersionIfUnspecified
Status doCommand(rpc::JsonContext &context, json::Value &result)
Execute an RPC command and store the results in a json::Value.
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
XrpT const XRP
Converts to XRP Issue or STAmount.
bool same(STPathSet const &st1, Args const &... args)
uint256 setupDomain(jtx::Env &env, std::vector< jtx::Account > const &accounts, jtx::Account const &domainOwner, std::string const &credType)
STPathElement ipe(Asset const &asset)
std::array< Account, 1+sizeof...(Args)> noripple(Account const &account, Args const &... args)
Designate accounts as no-ripple in Env::fund.
bool equal(STAmount const &sa1, STAmount const &sa2)
json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
STPath stpath(Args const &... args)
json::Value rate(Account const &account, double multiplier)
Set a transfer rate.
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
json::Value rpf(jtx::Account const &src, jtx::Account const &dst, std::uint32_t numSrc)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
BaseUInt< 256 > Domain
Domain is a 256-bit hash representing a specific domain.
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Currency const & xrpCurrency()
XRP currency.
std::string to_string(BaseUInt< Bits, Tag > const &a)
STAmount amountFromJson(SField const &name, json::Value const &v)
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
AccountID const & xrpAccount()
Compute AccountID from public key.
std::shared_ptr< JobQueue::Coro > coro