xrpld
Loading...
Searching...
No Matches
PathMPT_test.cpp
1#include <test/jtx/Account.h>
2#include <test/jtx/Env.h>
3#include <test/jtx/TestHelpers.h>
4#include <test/jtx/amount.h>
5#include <test/jtx/balance.h>
6#include <test/jtx/domain.h>
7#include <test/jtx/envconfig.h>
8#include <test/jtx/mpt.h>
9#include <test/jtx/offer.h>
10#include <test/jtx/pay.h>
11#include <test/jtx/permissioned_dex.h>
12
13#include <xrpld/core/Config.h>
14#include <xrpld/rpc/Context.h>
15#include <xrpld/rpc/RPCHandler.h>
16#include <xrpld/rpc/Role.h>
17#include <xrpld/rpc/detail/AccountAssets.h>
18#include <xrpld/rpc/detail/AssetCache.h>
19#include <xrpld/rpc/detail/RPCHelpers.h>
20#include <xrpld/rpc/detail/Tuning.h>
21
22#include <xrpl/basics/base_uint.h>
23#include <xrpl/beast/unit_test/suite.h>
24#include <xrpl/core/Job.h>
25#include <xrpl/core/JobQueue.h>
26#include <xrpl/json/json_value.h>
27#include <xrpl/protocol/AccountID.h>
28#include <xrpl/protocol/ApiVersion.h>
29#include <xrpl/protocol/Indexes.h>
30#include <xrpl/protocol/PathAsset.h>
31#include <xrpl/protocol/SField.h>
32#include <xrpl/protocol/STAmount.h>
33#include <xrpl/protocol/STPathSet.h>
34#include <xrpl/protocol/UintTypes.h>
35#include <xrpl/protocol/jss.h>
36#include <xrpl/resource/Charge.h>
37#include <xrpl/resource/Consumer.h>
38#include <xrpl/resource/Fees.h>
39
40#include <algorithm>
41#include <cstdint>
42#include <memory>
43#include <optional>
44#include <tuple>
45#include <vector>
46
47namespace xrpl::test {
48namespace detail {
49
50static json::Value
51rpf(jtx::Account const& src,
52 jtx::Account const& dst,
53 xrpl::test::jtx::MPT const& usd,
54 std::vector<MPTID> const& numSrc)
55{
57 jv[jss::command] = "ripple_path_find";
58 jv[jss::source_account] = toBase58(src);
59
60 if (!numSrc.empty())
61 {
62 auto& sc = (jv[jss::source_currencies] = json::ValueType::Array);
64 for (auto const& id : numSrc)
65 {
66 j[jss::mpt_issuance_id] = to_string(id);
67 sc.append(j);
68 }
69 }
70
71 auto const d = toBase58(dst);
72 jv[jss::destination_account] = d;
73
74 json::Value& j = (jv[jss::destination_amount] = json::ValueType::Object);
75 j[jss::mpt_issuance_id] = to_string(usd.mpt());
76 j[jss::value] = "1";
77
78 return jv;
79}
80
81} // namespace detail
82
83//------------------------------------------------------------------------------
84
86{
89 {
90 // These tests were originally written with search parameters that are
91 // different from the current defaults. This function creates an env
92 // with the search parameters that the tests were written for.
93 using namespace jtx;
94 return Env(*this, envconfig([](std::unique_ptr<Config> cfg) {
95 cfg->pathSearchOld = 7;
96 cfg->pathSearch = 7;
97 cfg->pathSearchMax = 10;
98 return cfg;
99 }));
100 }
101
102public:
103 void
105 {
106 testcase("source currency limits");
107 using namespace std::chrono_literals;
108 using namespace jtx;
109 Env env = pathTestEnv();
110 auto const gw = Account("gateway");
111 auto const alice = Account("alice");
112 auto const bob = Account("bob");
113
114 env.fund(XRP(10'000), "alice", "bob", gw);
115
116 MPT const usd =
117 MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}, .maxAmt = 100});
118
119 auto& app = env.app();
122
123 rpc::JsonContext context{
124 {.j = env.journal,
125 .app = app,
126 .loadType = loadType,
127 .netOps = app.getOPs(),
128 .ledgerMaster = app.getLedgerMaster(),
129 .consumer = c,
130 .role = Role::USER,
131 .coro = {},
132 .infoSub = {},
133 .apiVersion = rpc::kApiVersionIfUnspecified},
134 {},
135 {}};
136 json::Value result;
137 Gate g;
138 // Test rpc::tuning::max_src_cur source currencies.
139 std::vector<MPTID> numSrc;
141 for (std::uint8_t i = 0; i < rpc::tuning::kMaxSrcCur; ++i)
142 numSrc.push_back(makeMptID(i, bob));
143 app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) {
144 context.params = xrpl::test::detail::rpf(alice, bob, usd, numSrc);
145 context.coro = coro;
146 rpc::doCommand(context, result);
147 g.signal();
148 });
149 BEAST_EXPECT(g.waitFor(5s));
150 BEAST_EXPECT(!result.isMember(jss::error));
151
152 // Test more than rpc::tuning::max_src_cur source currencies.
154 app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) {
155 context.params = xrpl::test::detail::rpf(alice, bob, usd, numSrc);
156 context.coro = coro;
157 rpc::doCommand(context, result);
158 g.signal();
159 });
160 BEAST_EXPECT(g.waitFor(5s));
161 BEAST_EXPECT(result.isMember(jss::error));
162
163 // Test rpc::tuning::max_auto_src_cur source currencies.
164 numSrc.clear();
165 for (auto i = 0; i < (rpc::tuning::kMaxAutoSrcCur - 1); ++i)
166 {
167 auto curm = MPTTester({.env = env, .issuer = alice, .holders = {bob}});
168 numSrc.push_back(curm.issuanceID());
169 }
170 app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) {
171 context.params = xrpl::test::detail::rpf(alice, bob, usd, {});
172 context.coro = coro;
173 rpc::doCommand(context, result);
174 g.signal();
175 });
176 BEAST_EXPECT(g.waitFor(5s));
177 BEAST_EXPECT(!result.isMember(jss::error));
178
179 // Test more than rpc::tuning::max_auto_src_cur source currencies.
180 auto curm = MPTTester({.env = env, .issuer = alice, .holders = {bob}});
181 app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) {
182 context.params = xrpl::test::detail::rpf(alice, bob, usd, {});
183 context.coro = coro;
184 rpc::doCommand(context, result);
185 g.signal();
186 });
187 BEAST_EXPECT(g.waitFor(5s));
188 BEAST_EXPECT(result.isMember(jss::error));
189 }
190
191 void
193 {
194 testcase("no direct path no intermediary no alternatives");
195 using namespace jtx;
196
197 Env env = pathTestEnv();
198
199 env.fund(XRP(10'000), "alice", "bob");
200
201 auto usdm = MPTTester({.env = env, .issuer = "bob"});
202
203 auto const result = findPaths(env, "alice", "bob", usdm(5));
204 BEAST_EXPECT(std::get<0>(result).empty());
205 }
206
207 void
209 {
210 testcase("direct path no intermediary");
211 using namespace jtx;
212 Env env = pathTestEnv();
213 env.fund(XRP(10'000), "alice", "bob");
214
215 MPT const usd = MPTTester({.env = env, .issuer = "alice", .holders = {"bob"}});
216
217 STPathSet st;
218 STAmount sa;
219 std::tie(st, sa, std::ignore) = findPaths(env, "alice", "bob", usd(5));
220 BEAST_EXPECT(st.empty());
221 BEAST_EXPECT(equal(sa, usd(5)));
222 }
223
224 void
226 {
227 testcase("payment auto path find");
228 using namespace jtx;
229 Env env = pathTestEnv();
230 auto const gw = Account("gateway");
231 env.fund(XRP(10'000), "alice", "bob", gw);
232 MPT const usd = MPTTester({.env = env, .issuer = gw, .holders = {"alice", "bob"}});
233 env(pay(gw, "alice", usd(70)));
234 env(pay("alice", "bob", usd(24)));
235 env.require(Balance("alice", usd(46)));
236 env.require(Balance("bob", usd(24)));
237 }
238
239 void
241 {
242 testcase("source currency with send_max");
243 using namespace jtx;
244
245 Env env = pathTestEnv();
246 auto const alice = Account("alice");
247 auto const bob = Account("bob");
248 auto const gw = Account("gateway");
249 env.fund(XRP(10'000), alice, bob, gw);
250
251 MPT const usd = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}});
252 env(pay(gw, alice, usd(25)));
253 env.close();
254
255 // MPT source_currencies entries do not carry an issuer. A matching
256 // send_max identifies the same issuance, so the request should not run
257 // the IOU issuer reconciliation path.
258 auto const result = findPathsRequest(
259 env,
260 alice,
261 bob,
262 usd(-1),
263 std::optional<STAmount>(usd(10).value()),
265 BEAST_EXPECTS(!result.isMember(jss::error), result.toStyledString());
266
267 auto const& alternatives = result[jss::alternatives];
268 if (BEAST_EXPECT(alternatives.size() == 1))
269 {
270 auto const sa = amountFromJson(sfGeneric, alternatives[0u][jss::source_amount]);
271 auto const da = amountFromJson(sfGeneric, alternatives[0u][jss::destination_amount]);
272 BEAST_EXPECTS(equal(sa, usd(10)), sa.getFullText());
273 BEAST_EXPECTS(equal(da, usd(10)), da.getFullText());
274 }
275 }
276
277 void
279 {
280 testcase("maxed-out MPT pathfinding");
281 using namespace jtx;
282
283 auto hasMPT = [](auto const& assets, MPT const& mpt) {
284 return std::ranges::any_of(assets, [&](auto const& asset) {
285 return asset.template holds<MPTID>() && asset.template get<MPTID>() == mpt.mpt();
286 });
287 };
288
289 Env env = pathTestEnv();
290 auto const gw = Account("gateway");
291 auto const alice = Account("alice");
292 auto const bob = Account("bob");
293 auto const carol = Account("carol");
294
295 env.fund(XRP(10'000), gw, alice, bob, carol);
296 env.close();
297
298 MPT const usd =
299 MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, carol}, .maxAmt = 100});
300 env(pay(gw, alice, usd(90)));
301 env(pay(gw, bob, usd(10)));
302 env.close();
303
304 auto const cache =
305 std::make_shared<AssetCache>(env.current(), env.app().getJournal("AssetCache"));
306
307 BEAST_EXPECT(hasMPT(accountSourceAssets(alice.id(), cache, false), usd));
308 BEAST_EXPECT(hasMPT(accountDestAssets(bob.id(), cache, false), usd));
309 BEAST_EXPECT(hasMPT(accountDestAssets(carol.id(), cache, false), usd));
310
311 // A fully minted issuance should not be advertised as issuer-side
312 // mintable source liquidity.
313 BEAST_EXPECT(!hasMPT(accountSourceAssets(gw.id(), cache, false), usd));
314
315 auto [st, sa, da] = findPaths(env, alice, bob, usd(5));
316 BEAST_EXPECT(st.empty());
317 BEAST_EXPECT(equal(sa, usd(5)));
318 BEAST_EXPECT(equal(da, usd(5)));
319
320 env(offer(carol, usd(5), XRP(5)));
321 env.close();
322
323 std::tie(st, sa, da) = findPaths(env, alice, bob, drops(-1), usd(100).value());
324 BEAST_EXPECT(sa == usd(5));
325 BEAST_EXPECT(equal(da, XRP(5)));
326 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
327 {
328 auto const& pathElem = st[0][0];
329 BEAST_EXPECT(
330 pathElem.isOffer() && pathElem.getIssuerID() == xrpAccount() &&
331 pathElem.getCurrency() == xrpCurrency());
332 }
333 }
334
335 void
336 pathFind(bool const domainEnabled)
337 {
338 testcase(std::string("path find") + (domainEnabled ? " w/ " : " w/o ") + "domain");
339 using namespace jtx;
340 Env env = pathTestEnv();
341 auto const gw = Account("gateway");
342 env.fund(XRP(10'000), "alice", "bob", gw);
343 MPT const usd = MPTTester({.env = env, .issuer = gw, .holders = {"alice", "bob"}});
344 env(pay(gw, "alice", usd(70)));
345 env(pay(gw, "bob", usd(50)));
346
347 std::optional<UInt256> domainID;
348 if (domainEnabled)
349 domainID = setupDomain(env, {"alice", "bob", gw});
350
351 STPathSet st;
352 STAmount sa;
353 STAmount da;
354 std::tie(st, sa, da) = findPaths(
355 env, "alice", "bob", usd(5), std::nullopt, std::nullopt, std::nullopt, domainID);
356 // Note, a direct IOU payment will have "gateway" as alternative path
357 // since IOU supports rippling
358 BEAST_EXPECT(st.empty());
359 BEAST_EXPECT(equal(sa, usd(5)));
360 BEAST_EXPECT(equal(da, usd(5)));
361 }
362
363 void
364 pathFindConsumeAll(bool const domainEnabled)
365 {
366 testcase(
367 std::string("path find consume all") + (domainEnabled ? " w/ " : " w/o ") + "domain");
368 using namespace jtx;
369
370 {
371 Env env = pathTestEnv();
372 auto const gw = Account("gateway");
373 env.fund(XRP(10'000), "alice", "bob", "carol", gw);
374 MPT const usd = MPTTester({.env = env, .issuer = gw, .holders = {"bob", "carol"}});
375 MPT const aud(makeMptID(0, gw));
376 env(pay(gw, "carol", usd(100)));
377 std::optional<UInt256> domainID;
378 if (domainEnabled)
379 {
380 domainID = setupDomain(env, {"alice", "bob", "carol", "gateway"});
381 env(offer("carol", XRP(100), usd(100)), Domain(*domainID));
382 }
383 else
384 {
385 env(offer("carol", XRP(100), usd(100)));
386 }
387 env.close();
388
389 STPathSet st;
390 STAmount sa;
391 STAmount da;
392 std::tie(st, sa, da) = findPaths(
393 env,
394 "alice",
395 "bob",
396 aud(-1),
397 std::optional<STAmount>(XRP(100'000'000)),
398 std::nullopt,
399 std::nullopt,
400 domainID);
401 BEAST_EXPECT(st.empty());
402 std::tie(st, sa, da) = findPaths(
403 env,
404 "alice",
405 "bob",
406 usd(-1),
407 std::optional<STAmount>(XRP(100'000'000)),
408 std::nullopt,
409 std::nullopt,
410 domainID);
411 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
412 {
413 auto const& pathElem = st[0][0];
414 BEAST_EXPECT(
415 pathElem.isOffer() && pathElem.getIssuerID() == gw.id() &&
416 pathElem.getMPTID() == usd.issuanceID);
417 }
418 BEAST_EXPECT(sa == XRP(100));
419 BEAST_EXPECT(equal(da, usd(100)));
420
421 // if domain is used, finding path in the open offerbook will return
422 // empty result
423 if (domainEnabled)
424 {
425 std::tie(st, sa, da) = findPaths(
426 env,
427 "alice",
428 "bob",
429 Account("bob")["USD"](-1),
431 std::nullopt,
432 std::nullopt); // not specifying a domain
433 BEAST_EXPECT(st.empty());
434 }
435 }
436 }
437
438 void
439 alternativePathsConsumeBestTransfer(bool const domainEnabled)
440 {
441 testcase(
442 std::string("alternative path consume best transfer") +
443 (domainEnabled ? " w/ " : " w/o ") + "domain");
444 using namespace jtx;
445 Env env = pathTestEnv();
446 auto const gw = Account("gateway");
447 auto const gw2 = Account("gateway2");
448 env.fund(XRP(10'000), "alice", "bob", gw, gw2);
449 MPT const usd = MPTTester({.env = env, .issuer = gw, .holders = {"alice", "bob"}});
450 MPT const gw2Usd = MPTTester(
451 {.env = env, .issuer = gw2, .holders = {"alice", "bob"}, .transferFee = 1'000});
452 std::optional<UInt256> domainID;
453 if (domainEnabled)
454 {
455 domainID = setupDomain(env, {"alice", "bob", "gateway", "gateway2"});
456 env(pay(gw, "alice", usd(70)), Domain(*domainID));
457 env(pay(gw2, "alice", gw2Usd(70)), Domain(*domainID));
458 env(pay("alice", "bob", usd(70)), Domain(*domainID));
459 }
460 else
461 {
462 env(pay(gw, "alice", usd(70)));
463 env(pay(gw2, "alice", gw2Usd(70)));
464 env(pay("alice", "bob", usd(70)));
465 }
466 env.require(Balance("alice", usd(0)));
467 env.require(Balance("alice", gw2Usd(70)));
468 env.require(Balance("bob", usd(70)));
469 env.require(Balance("bob", gw2Usd(0)));
470 }
471
472 void
473 receiveMax(bool const domainEnabled)
474 {
475 testcase(std::string("Receive max") + (domainEnabled ? " w/ " : " w/o ") + "domain");
476 using namespace jtx;
477 auto const alice = Account("alice");
478 auto const bob = Account("bob");
479 auto const charlie = Account("charlie");
480 auto const gw = Account("gw");
481 {
482 // XRP -> MPT receive max
483 Env env = pathTestEnv();
484 env.fund(XRP(10'000), alice, bob, charlie, gw);
485 env.close();
486 MPT const usd = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, charlie}});
487 env(pay(gw, charlie, usd(10)));
488 env.close();
489 std::optional<UInt256> domainID;
490 if (domainEnabled)
491 {
492 domainID = setupDomain(env, {alice, bob, charlie, gw});
493 env(offer(charlie, XRP(10), usd(10)), Domain(*domainID));
494 }
495 else
496 {
497 env(offer(charlie, XRP(10), usd(10)));
498 }
499 env.close();
500 auto [st, sa, da] = findPaths(
501 env, alice, bob, usd(-1), XRP(100).value(), std::nullopt, std::nullopt, domainID);
502 BEAST_EXPECT(sa == XRP(10));
503 BEAST_EXPECT(equal(da, usd(10)));
504 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
505 {
506 auto const& pathElem = st[0][0];
507 BEAST_EXPECT(
508 pathElem.isOffer() && pathElem.getIssuerID() == gw.id() &&
509 pathElem.getMPTID() == usd.mpt());
510 }
511 }
512 {
513 // MPT -> XRP receive max
514 Env env = pathTestEnv();
515 env.fund(XRP(10'000), alice, bob, charlie, gw);
516 env.close();
517 MPT const usd = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, charlie}});
518 env(pay(gw, alice, usd(10)));
519 env.close();
520 std::optional<UInt256> domainID;
521 if (domainEnabled)
522 {
523 domainID = setupDomain(env, {alice, bob, charlie, gw});
524 env(offer(charlie, usd(10), XRP(10)), Domain(*domainID));
525 }
526 else
527 {
528 env(offer(charlie, usd(10), XRP(10)));
529 }
530 env.close();
531 auto [st, sa, da] = findPaths(
532 env, alice, bob, drops(-1), usd(100).value(), std::nullopt, std::nullopt, domainID);
533 BEAST_EXPECT(sa == usd(10));
534 BEAST_EXPECT(equal(da, XRP(10)));
535 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
536 {
537 auto const& pathElem = st[0][0];
538 BEAST_EXPECT(
539 pathElem.isOffer() && pathElem.getIssuerID() == xrpAccount() &&
540 pathElem.getCurrency() == xrpCurrency());
541 }
542 }
543 }
544
545 // Regression test: the Pathfinder constructor must honor the
546 // caller-supplied srcAmount (= the user's send_max from PathRequest)
547 // when ranking candidate paths in convert_all mode.
548 //
549 // Background. The MPT-DEX refactor of `Pathfinder::Pathfinder`
550 // (src/xrpld/rpc/detail/Pathfinder.cpp) replaced the original
551 // `mSrcAmount(srcAmount.value_or(...))` initializer with an
552 // unconditional `amountFromPathAsset(...)` call. The latter always
553 // returns the negative "no limit" STAmount sentinel, so the
554 // `srcAmount` constructor parameter became dead code:
555 // `getPathLiquidity` and `computePathRanks` ran `rippleCalculate`
556 // with `saMaxAmountReq` = sentinel and recorded each path's
557 // saturated capacity instead of the capacity reachable inside
558 // send_max.
559 //
560 // In convert_all_ mode (the only mode that allows send_max),
561 // `Pathfinder::rankPaths` ignores quality and orders purely by
562 // liquidity, then `Pathfinder::getBestPaths` only fills the last
563 // (kMaxPaths-th = 4th) slot when `pathRank.liquidity >= remaining`.
564 // For convert_all_ `remaining = largestAmount(dstAmount_)`, so the
565 // last slot effectively never fills and the cut keeps the top 3
566 // ranked paths. With the wrong (unbounded-budget) ranking, a
567 // low-capacity / high-rate path that would actually deliver the
568 // most under the user's send_max can be excluded entirely.
569 //
570 // Topology. Four candidate paths from alice's XRP to bob's USD-MPT,
571 // each via a distinct IOU intermediary issued by a different market
572 // maker:
573 //
574 // charlie: XRP(1000) -> AUD(1000) -> USD(500) cap 1000 XRP, rate 0.5
575 // dave: XRP(1000) -> EUR(1000) -> USD(500) cap 1000 XRP, rate 0.5
576 // eve: XRP(1000) -> GBP(1000) -> USD(500) cap 1000 XRP, rate 0.5
577 // frank: XRP(50) -> JPY(50) -> USD(75) cap 50 XRP, rate 1.5
578 //
579 // Alice queries findPaths with destination = USD-MPT(-1) (convert_all)
580 // and send_max = XRP(100).
581 //
582 // Bug-free ranking (post-fix), with srcAmount = XRP(100):
583 // charlie/dave/eve liquidity = min(100, 1000) * 0.5 = 50 USD each
584 // frank liquidity = min(100, 50) * 1.5 = 75 USD
585 // -> frank ranks first; the flow uses frank's 50 XRP at 1.5 (=75 USD)
586 // plus 50 XRP via a 0.5-rate path (=25 USD), delivering USD(100).
587 //
588 // Pre-fix ranking, with srcAmount silently replaced by the sentinel:
589 // charlie/dave/eve liquidity = 1000 * 0.5 = 500 USD each
590 // frank liquidity = 50 * 1.5 = 75 USD
591 // -> frank ranks 4th; the last-slot rule excludes it from the
592 // surviving path set, the cut keeps the three 0.5-rate paths,
593 // and the flow delivers only 100 * 0.5 = USD(50).
594 //
595 // This test asserts the post-fix outcome (USD(100)). On the pre-fix
596 // tree the assertion fails with USD(50).
597 void
599 {
600 testcase("convert_all + send_max: srcAmount governs path ranking");
601 using namespace jtx;
602
603 Env env = pathTestEnv();
604 auto const alice = Account("alice");
605 auto const bob = Account("bob");
606 auto const gw = Account("gateway");
607 auto const charlie = Account("charlie");
608 auto const dave = Account("dave");
609 auto const eve = Account("eve");
610 auto const frank = Account("frank");
611
612 env.fund(XRP(10'000), alice, bob, gw, charlie, dave, eve, frank);
613 env.close();
614
615 // USD MPT issued by gw; the four market makers and bob are holders.
616 // alice is not a holder because she only pays XRP; USD only ever
617 // flows from gw / market-maker offers to bob.
618 MPT const usd =
619 MPTTester({.env = env, .issuer = gw, .holders = {charlie, dave, eve, frank, bob}});
620
621 // Capitalize each market maker with the USD-MPT they will sell.
622 env(pay(gw, charlie, usd(500)));
623 env(pay(gw, dave, usd(500)));
624 env(pay(gw, eve, usd(500)));
625 env(pay(gw, frank, usd(75)));
626 env.close();
627
628 // Each market maker issues their own intermediate IOU.
629 auto const aud = charlie["AUD"];
630 auto const eur = dave["EUR"];
631 auto const gbp = eve["GBP"];
632 auto const jpy = frank["JPY"];
633
634 // Three high-capacity, low-rate paths (1 XRP -> 0.5 USD-MPT,
635 // capacity 1000 XRP each).
636 env(offer(charlie, XRP(1'000), aud(1'000)));
637 env(offer(charlie, aud(1'000), usd(500)));
638 env(offer(dave, XRP(1'000), eur(1'000)));
639 env(offer(dave, eur(1'000), usd(500)));
640 env(offer(eve, XRP(1'000), gbp(1'000)));
641 env(offer(eve, gbp(1'000), usd(500)));
642
643 // One low-capacity, high-rate path (1 XRP -> 1.5 USD-MPT,
644 // capacity 50 XRP).
645 env(offer(frank, XRP(50), jpy(50)));
646 env(offer(frank, jpy(50), usd(75)));
647 env.close();
648
649 // ripple_path_find with convert_all (USD(-1)) and send_max XRP(100).
650 STPathSet st;
651 STAmount sa;
652 STAmount da;
653 std::tie(st, sa, da) =
654 findPaths(env, alice, bob, usd(-1), std::optional<STAmount>(XRP(100).value()));
655
656 // Post-fix: frank's high-rate path is included in the surviving
657 // path set, so the flow uses 50 XRP at 1.5 plus 50 XRP at 0.5,
658 // delivering exactly USD(100) on alice's 100-XRP budget.
659 BEAST_EXPECT(sa == XRP(100));
660 BEAST_EXPECT(equal(da, usd(100)));
661 }
662
663 void
664 run() override
665 {
673 for (auto const domainEnabled : {false, true})
674 {
675 pathFind(domainEnabled);
676 pathFindConsumeAll(domainEnabled);
678 receiveMax(domainEnabled);
679 }
680 }
681};
682
684
685} // namespace xrpl::test
T any_of(T... args)
A testsuite class.
Definition suite.h:52
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
Represents a JSON value.
Definition json_value.h:117
bool isMember(char const *key) const
Return true if the object has a member named key.
std::vector< STPath >::size_type size() const
Definition STPathSet.h:624
bool empty() const
Definition STPathSet.h:630
virtual beast::Journal getJournal(std::string const &name)=0
A consumption charge.
Definition Charge.h:13
An endpoint that consumes resources.
Definition Consumer.h:20
void pathFindConsumeAll(bool const domainEnabled)
void noDirectPathNoIntermediaryNoAlternatives()
void receiveMax(bool const domainEnabled)
void run() override
Runs the suite.
void alternativePathsConsumeBestTransfer(bool const domainEnabled)
void pathFind(bool const domainEnabled)
Immutable cryptographic account descriptor.
Definition jtx/Account.h:21
A transaction testing environment.
Definition Env.h:161
Application & app()
Definition Env.h:300
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:323
beast::Journal const journal
Definition Env.h:204
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:766
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:377
bool waitFor(std::chrono::duration< Rep, Period > const &relTime)
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
Definition mpt.h:512
Converts to MPT Issue or STAmount.
xrpl::MPTID const & mpt() const
T clear(T... args)
T empty(T... args)
T make_shared(T... args)
@ Array
array value (ordered list)
Definition json_value.h:28
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
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
Definition ApiVersion.h:44
Status doCommand(rpc::JsonContext &context, json::Value &result)
Execute an RPC command and store the results in a json::Value.
static json::Value rpf(jtx::Account const &src, jtx::Account const &dst, xrpl::test::jtx::MPT const &usd, std::vector< MPTID > const &numSrc)
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:14
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
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::optional< PathAsset > const &srcAsset, std::optional< AccountID > const &srcIssuer, std::optional< UInt256 > const &domain)
json::Value findPathsRequest(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax, std::optional< PathAsset > const &srcAsset, std::optional< AccountID > const &srcIssuer, std::optional< UInt256 > const &domain)
bool equal(STAmount const &sa1, STAmount const &sa2)
json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:14
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:38
UInt256 setupDomain(jtx::Env &env, std::vector< jtx::Account > const &accounts, jtx::Account const &domainOwner, std::string const &credType)
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
BaseUInt< 256 > Domain
Domain is a 256-bit hash representing a specific domain.
Definition UintTypes.h:59
SField const sfGeneric
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
Currency const & xrpCurrency()
XRP currency.
Definition UintTypes.cpp:99
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
HashSet< PathAsset > accountDestAssets(AccountID const &account, std::shared_ptr< AssetCache > const &lrCache, bool includeXRP)
@ USER
Definition Role.h:27
@ JtClient
Definition Job.h:31
STAmount amountFromJson(SField const &name, json::Value const &v)
Definition STAmount.cpp:917
HashSet< PathAsset > accountSourceAssets(AccountID const &account, std::shared_ptr< AssetCache > const &lrCache, bool includeXRP)
MPTID makeMptID(std::uint32_t const sequence, AccountID const &account)
Definition Indexes.cpp:206
AccountID const & xrpAccount()
Compute AccountID from public key.
T push_back(T... args)
T reserve(T... args)
std::shared_ptr< JobQueue::Coro > coro
Definition Context.h:35
json::Value params
Definition Context.h:51
T tie(T... args)