xrpld
Loading...
Searching...
No Matches
PayStrand_test.cpp
1#include <test/jtx/Account.h>
2#include <test/jtx/Env.h>
3#include <test/jtx/PathSet.h>
4#include <test/jtx/TestHelpers.h>
5#include <test/jtx/amount.h>
6#include <test/jtx/balance.h>
7#include <test/jtx/flags.h>
8#include <test/jtx/jtx_json.h>
9#include <test/jtx/offer.h>
10#include <test/jtx/owners.h> // IWYU pragma: keep
11#include <test/jtx/paths.h>
12#include <test/jtx/pay.h>
13#include <test/jtx/sendmax.h>
14#include <test/jtx/ter.h>
15#include <test/jtx/trust.h>
16#include <test/jtx/txflags.h>
17
18#include <xrpl/basics/contract.h>
19#include <xrpl/basics/safe_cast.h>
20#include <xrpl/beast/unit_test/suite.h>
21#include <xrpl/ledger/ApplyView.h>
22#include <xrpl/ledger/PaymentSandbox.h>
23#include <xrpl/protocol/AccountID.h>
24#include <xrpl/protocol/Book.h>
25#include <xrpl/protocol/Feature.h>
26#include <xrpl/protocol/Indexes.h>
27#include <xrpl/protocol/Issue.h>
28#include <xrpl/protocol/Keylet.h>
29#include <xrpl/protocol/LedgerFormats.h>
30#include <xrpl/protocol/SField.h>
31#include <xrpl/protocol/STAmount.h>
32#include <xrpl/protocol/STPathSet.h>
33#include <xrpl/protocol/TER.h>
34#include <xrpl/protocol/TxFlags.h>
35#include <xrpl/protocol/UintTypes.h>
36#include <xrpl/tx/paths/RippleCalc.h>
37#include <xrpl/tx/paths/detail/Steps.h>
38#include <xrpl/tx/transactors/dex/AMMContext.h>
39
40#include <algorithm>
41#include <cassert>
42#include <cstddef>
43#include <cstdint>
44#include <initializer_list>
45#include <optional>
46#include <stdexcept>
47#include <string>
48#include <tuple>
49#include <vector>
50
51namespace xrpl::test {
52
53enum class TrustFlag { Freeze, Auth, Noripple };
54
55/*constexpr*/ std::uint32_t
56trustFlag(TrustFlag f, bool useHigh)
57{
58 switch (f)
59 {
61 if (useHigh)
62 return lsfHighFreeze;
63 return lsfLowFreeze;
64 case TrustFlag::Auth:
65 if (useHigh)
66 return lsfHighAuth;
67 return lsfLowAuth;
69 if (useHigh)
70 return lsfHighNoRipple;
71 return lsfLowNoRipple;
72 }
73 return 0; // Silence warning about end of non-void function
74}
75
76bool
78 jtx::Env const& env,
79 jtx::Account const& src,
80 jtx::Account const& dst,
81 Currency const& cur,
82 TrustFlag flag)
83{
84 if (auto sle = env.le(keylet::trustLine(src, dst, cur)))
85 {
86 auto const useHigh = src.id() > dst.id();
87 return sle->isFlag(trustFlag(flag, useHigh));
88 }
89 Throw<std::runtime_error>("No line in getTrustFlag");
90 return false; // silence warning
91}
92
94{
113
115 static_assert(safeCast<size_t>(SB::Last) <= sizeof(decltype(state_)) * 8);
116 STPathElement const* prev_ = nullptr;
117 // disallow iss and cur to be specified with acc is specified (simplifies
118 // some tests)
119 bool const allowCompound_ = false;
120
121 [[nodiscard]] bool
122 has(SB s) const
123 {
124 return (state_ & (1 << safeCast<int>(s))) != 0;
125 }
126
127 [[nodiscard]] bool
129 {
130 return std::ranges::any_of(sb, [this](auto const s) { return has(s); });
131 }
132
133 [[nodiscard]] size_t
135 {
136 size_t result = 0;
137
138 for (auto const s : sb)
139 {
140 if (has(s))
141 result++;
142 }
143 return result;
144 }
145
146public:
147 explicit ElementComboIter(STPathElement const* prev = nullptr) : prev_(prev)
148 {
149 }
150
151 [[nodiscard]] bool
152 valid() const
153 {
154 return (allowCompound_ || !(has(SB::Acc) && hasAny({SB::Cur, SB::Iss}))) &&
155 (!hasAny({SB::PrevAcc, SB::PrevCur, SB::PrevIss}) || (prev_ != nullptr)) &&
157 has(SB::Acc)) &&
159 has(SB::Iss)) &&
161 // These will be duplicates
165 }
166 bool
168 {
169 if (!(has(SB::Last)))
170 {
171 do
172 {
173 ++state_;
174 } while (!valid());
175 }
176 return !has(SB::Last);
177 }
178
179 template <class Col, class AccFactory, class IssFactory, class CurrencyFactory>
180 void
182 Col& col,
183 AccFactory&& accF,
184 IssFactory&& issF,
185 CurrencyFactory&& currencyF,
186 std::optional<AccountID> const& existingAcc,
187 std::optional<Currency> const& existingCur,
188 std::optional<AccountID> const& existingIss)
189 {
190 assert(!has(SB::Last));
191
192 auto const acc = [&]() -> std::optional<AccountID> {
193 if (!has(SB::Acc))
194 return std::nullopt;
195 if (has(SB::RootAcc))
196 return xrpAccount();
197 if (has(SB::ExistingAcc) && existingAcc)
198 return existingAcc;
199 return accF().id();
200 }();
201 auto const iss = [&]() -> std::optional<AccountID> {
202 if (!has(SB::Iss))
203 return std::nullopt;
204 if (has(SB::RootIss))
205 return xrpAccount();
206 if (has(SB::SameAccIss))
207 return acc;
208 if (has(SB::ExistingIss) && existingIss)
209 return existingIss;
210 return issF().id();
211 }();
212 auto const cur = [&]() -> std::optional<Currency> {
213 if (!has(SB::Cur))
214 return std::nullopt;
215 if (has(SB::Xrp))
216 return xrpCurrency();
217 if (has(SB::ExistingCur) && existingCur)
218 return existingCur;
219 return currencyF();
220 }();
221 if (!has(SB::Boundary))
222 {
223 col.emplace_back(acc, cur, iss);
224 }
225 else
226 {
227 col.emplace_back(
229 acc.value_or(AccountID{}),
230 cur.value_or(Currency{}),
231 iss.value_or(AccountID{}));
232 }
233 }
234};
235
237{
241
243 getAccount(size_t id)
244 {
245 assert(id < accounts.size());
246 return accounts[id];
247 }
248
250 getCurrency(size_t id)
251 {
252 assert(id < currencies.size());
253 return currencies[id];
254 }
255
256 // ids from 0 through (nextAvail -1) have already been used in the
257 // path
260
262 [[nodiscard]] ResetState
267
268 void
273
275 {
278
280 {
281 }
283 {
284 p.resetTo(state);
285 }
286 };
287
288 // Create the given number of accounts, and add trust lines so every
289 // account trusts every other with every currency
290 // Create an offer from every currency/account to every other
291 // currency/account; the offer owner is either the specified
292 // account or the issuer of the "taker gets" account
293 void
294 setupEnv(jtx::Env& env, size_t numAct, size_t numCur, std::optional<size_t> const& offererIndex)
295 {
296 using namespace jtx;
297
298 assert(!offererIndex || offererIndex < numAct);
299
300 accounts.clear();
301 accounts.reserve(numAct);
302 currencies.clear();
303 currencies.reserve(numCur);
304 currencyNames.clear();
305 currencyNames.reserve(numCur);
306
307 for (size_t id = 0; id < numAct; ++id)
308 accounts.emplace_back("A" + std::to_string(id));
309
310 for (size_t id = 0; id < numCur; ++id)
311 {
312 std::string name;
313 if (id < 10)
314 {
315 name = "CC" + std::to_string(id);
316 }
317 else if (id < 100)
318 {
319 name = "C" + std::to_string(id);
320 }
321 else
322 {
323 name = std::to_string(id);
324 }
325 currencies.emplace_back(toCurrency(name));
326 currencyNames.emplace_back(name);
327 }
328
329 for (auto const& a : accounts)
330 env.fund(XRP(100000), a);
331
332 // Every account trusts every other account with every currency
333 for (auto ai1 = accounts.begin(), aie = accounts.end(); ai1 != aie; ++ai1)
334 {
335 for (auto ai2 = accounts.begin(); ai2 != aie; ++ai2)
336 {
337 if (ai1 == ai2)
338 continue;
339 for (auto const& cn : currencyNames)
340 {
341 env.trust((*ai1)[cn](1'000'000), *ai2);
342 if (ai1 > ai2)
343 {
344 // accounts with lower indexes hold balances from
345 // accounts
346 // with higher indexes
347 auto const& src = *ai1;
348 auto const& dst = *ai2;
349 env(pay(src, dst, src[cn](500000)));
350 }
351 }
352 env.close();
353 }
354 }
355
356 std::vector<IOU> ious;
357 ious.reserve(numAct * numCur);
358 for (auto const& a : accounts)
359 {
360 for (auto const& cn : currencyNames)
361 ious.emplace_back(a[cn]);
362 }
363
364 // create offers from every currency to every other currency
365 for (auto takerPays = ious.begin(), ie = ious.end(); takerPays != ie; ++takerPays)
366 {
367 for (auto takerGets = ious.begin(); takerGets != ie; ++takerGets)
368 {
369 if (takerPays == takerGets)
370 continue;
371 auto const owner = offererIndex ? accounts[*offererIndex] : takerGets->account;
372 if (owner.id() != takerGets->account.id())
373 env(pay(takerGets->account, owner, (*takerGets)(1000)));
374
375 env(offer(owner, (*takerPays)(1000), (*takerGets)(1000)), Txflags(tfPassive));
376 }
377 env.close();
378 }
379
380 // create offers to/from xrp to every other ious
381 for (auto const& iou : ious)
382 {
383 auto const owner = offererIndex ? accounts[*offererIndex] : iou.account;
384 env(offer(owner, iou(1000), XRP(1000)), Txflags(tfPassive));
385 env(offer(owner, XRP(1000), iou(1000)), Txflags(tfPassive));
386 env.close();
387 }
388 }
389
391 totalXRP(ReadView const& v, bool incRoot)
392 {
394 auto add = [&](auto const& a) {
395 // XRP balance
396 auto const sle = v.read(keylet::account(a));
397 if (!sle)
398 return;
399 auto const b = (*sle)[sfBalance];
400 totalXRP += b.mantissa();
401 };
402 for (auto const& a : accounts)
403 add(a);
404 if (incRoot)
405 add(xrpAccount());
406 return totalXRP;
407 }
408
409 // Check that the balances for all accounts for all currencies & XRP are the
410 // same
411 bool
412 checkBalances(ReadView const& v1, ReadView const& v2)
413 {
415
416 auto xrpBalance = [](ReadView const& v, xrpl::Keylet const& k) {
417 auto const sle = v.read(k);
418 if (!sle)
419 return STAmount{};
420 return (*sle)[sfBalance];
421 };
422 auto lineBalance = [](ReadView const& v, xrpl::Keylet const& k) {
423 auto const sle = v.read(k);
424 if (!sle)
425 return STAmount{};
426 return (*sle)[sfBalance];
427 };
429 for (auto ai1 = accounts.begin(), aie = accounts.end(); ai1 != aie; ++ai1)
430 {
431 {
432 // XRP balance
433 auto const ak = keylet::account(*ai1);
434 auto const b1 = xrpBalance(v1, ak);
435 auto const b2 = xrpBalance(v2, ak);
436 totalXRP[0] += b1.mantissa();
437 totalXRP[1] += b2.mantissa();
438 if (b1 != b2)
439 diffs.emplace_back(b1, b2, xrpAccount(), *ai1);
440 }
441 for (auto ai2 = accounts.begin(); ai2 != aie; ++ai2)
442 {
443 if (ai1 >= ai2)
444 continue;
445 for (auto const& c : currencies)
446 {
447 // Line balance
448 auto const lk = keylet::trustLine(*ai1, *ai2, c);
449 auto const b1 = lineBalance(v1, lk);
450 auto const b2 = lineBalance(v2, lk);
451 if (b1 != b2)
452 diffs.emplace_back(b1, b2, *ai1, *ai2);
453 }
454 }
455 }
456 return diffs.empty();
457 }
458
461 {
463 }
464
467 {
469 }
470
471 template <class F>
472 void
474 STAmount const& sendMax,
475 STAmount const& deliver,
476 std::vector<STPathElement> const& prefix,
477 std::vector<STPathElement> const& suffix,
478 std::optional<AccountID> const& existingAcc,
479 std::optional<Currency> const& existingCur,
480 std::optional<AccountID> const& existingIss,
481 F&& f)
482 {
483 auto accF = [&] { return this->getAvailAccount(); };
484 auto issF = [&] { return this->getAvailAccount(); };
485 auto currencyF = [&] { return this->getAvailCurrency(); };
486
487 STPathElement const* prevOuter = prefix.empty() ? nullptr : &prefix.back();
488 ElementComboIter outer(prevOuter);
489
490 std::vector<STPathElement> outerResult;
492 auto const resultSize = prefix.size() + suffix.size() + 2;
493 outerResult.reserve(resultSize);
494 result.reserve(resultSize);
495 while (outer.next())
496 {
497 StateGuard const og{*this};
498 outerResult = prefix;
499 outer.emplaceInto(
500 outerResult, accF, issF, currencyF, existingAcc, existingCur, existingIss);
501 STPathElement const* prevInner = &outerResult.back();
502 ElementComboIter inner(prevInner);
503 while (inner.next())
504 {
505 StateGuard const ig{*this};
506 result = outerResult;
507 inner.emplaceInto(
508 result, accF, issF, currencyF, existingAcc, existingCur, existingIss);
509 result.insert(result.end(), suffix.begin(), suffix.end());
510 f(sendMax, deliver, result);
511 }
512 };
513 }
514};
515
517{
518 void
520 {
521 testcase("To Strand");
522
523 using namespace jtx;
524
525 auto const alice = Account("alice");
526 auto const bob = Account("bob");
527 auto const carol = Account("carol");
528 auto const gw = Account("gw");
529
530 auto const usd = gw["USD"];
531 auto const eur = gw["EUR"];
532
533 auto const eurC = eur.currency;
534 auto const usdC = usd.currency;
535
536 using D = DirectStepInfo;
537 using B = xrpl::Book;
538 using XRPS = XRPEndpointStepInfo;
539
540 AMMContext ammContext(alice, false);
541
542 auto test = [&, this](
543 jtx::Env& env,
544 Issue const& deliver,
545 std::optional<Issue> const& sendMaxIssue,
546 STPath const& path,
547 TER expTer,
548 auto&&... expSteps) {
549 auto [ter, strand] = toStrand(
550 *env.current(),
551 alice,
552 bob,
553 deliver,
554 std::nullopt,
555 sendMaxIssue,
556 path,
557 true,
559 ammContext,
560 std::nullopt,
561 env.app().getJournal("Flow"));
562 BEAST_EXPECT(ter == expTer);
563 if (sizeof...(expSteps) != 0)
564 BEAST_EXPECT(equal(strand, std::forward<decltype(expSteps)>(expSteps)...));
565 };
566
567 {
568 Env env(*this, features);
569 env.fund(XRP(10000), alice, bob, gw);
570 env.trust(usd(1000), alice, bob);
571 env.trust(eur(1000), alice, bob);
572 env(pay(gw, alice, eur(100)));
573
574 {
575 STPath const path = STPath({ipe(bob["USD"]), cpe(eur.currency)});
576 auto [ter, _] = toStrand(
577 *env.current(),
578 alice,
579 alice,
580 /*deliver*/ xrpIssue(),
581 /*limitQuality*/ std::nullopt,
582 /*sendMaxIssue*/ eur,
583 path,
584 true,
586 ammContext,
587 std::nullopt,
588 env.app().getJournal("Flow"));
589 (void)_;
590 BEAST_EXPECT(isTesSuccess(ter));
591 }
592 {
593 STPath const path = STPath({ipe(usd), cpe(xrpCurrency())});
594 auto [ter, _] = toStrand(
595 *env.current(),
596 alice,
597 alice,
598 /*deliver*/ xrpIssue(),
599 /*limitQuality*/ std::nullopt,
600 /*sendMaxIssue*/ eur,
601 path,
602 true,
604 ammContext,
605 std::nullopt,
606 env.app().getJournal("Flow"));
607 (void)_;
608 BEAST_EXPECT(isTesSuccess(ter));
609 }
610 }
611
612 {
613 Env env(*this, features);
614 env.fund(XRP(10000), alice, bob, carol, gw);
615
616 test(env, usd, std::nullopt, STPath(), terNO_LINE);
617
618 env.trust(usd(1000), alice, bob, carol);
619 test(env, usd, std::nullopt, STPath(), tecPATH_DRY);
620
621 env(pay(gw, alice, usd(100)));
622 env(pay(gw, carol, usd(100)));
623
624 // Insert implied account
625 test(
626 env,
627 usd,
628 std::nullopt,
629 STPath(),
631 D{.src = alice, .dst = gw, .currency = usdC},
632 D{.src = gw, .dst = bob, .currency = usdC});
633 env.trust(eur(1000), alice, bob);
634
635 // Insert implied offer
636 test(
637 env,
638 eur,
639 usd,
640 STPath(),
642 D{.src = alice, .dst = gw, .currency = usdC},
643 B{usd, eur, std::nullopt},
644 D{.src = gw, .dst = bob, .currency = eurC});
645
646 // Path with explicit offer
647 test(
648 env,
649 eur,
650 usd,
651 STPath({ipe(eur)}),
653 D{.src = alice, .dst = gw, .currency = usdC},
654 B{usd, eur, std::nullopt},
655 D{.src = gw, .dst = bob, .currency = eurC});
656
657 // Path with offer that changes issuer only
658 env.trust(carol["USD"](1000), bob);
659 test(
660 env,
661 carol["USD"],
662 usd,
663 STPath({iape(carol)}),
665 D{.src = alice, .dst = gw, .currency = usdC},
666 B{usd, carol["USD"], std::nullopt},
667 D{.src = carol, .dst = bob, .currency = usdC});
668
669 // Path with XRP src currency
670 test(
671 env,
672 usd,
673 xrpIssue(),
674 STPath({ipe(usd)}),
676 XRPS{alice},
677 B{XRP, usd, std::nullopt},
678 D{.src = gw, .dst = bob, .currency = usdC});
679
680 // Path with XRP dst currency.
681 test(
682 env,
683 xrpIssue(),
684 usd,
688 D{.src = alice, .dst = gw, .currency = usdC},
689 B{usd, XRP, std::nullopt},
690 XRPS{bob});
691
692 // Path with XRP cross currency bridged payment
693 test(
694 env,
695 eur,
696 usd,
697 STPath({cpe(xrpCurrency())}),
699 D{.src = alice, .dst = gw, .currency = usdC},
700 B{usd, XRP, std::nullopt},
701 B{XRP, eur, std::nullopt},
702 D{.src = gw, .dst = bob, .currency = eurC});
703
704 // XRP -> XRP transaction can't include a path
705 test(env, XRP, std::nullopt, STPath({ape(carol)}), temBAD_PATH);
706
707 {
708 // The root account can't be the src or dst
709 auto flowJournal = env.app().getJournal("Flow");
710 {
711 // The root account can't be the dst
712 auto r = toStrand(
713 *env.current(),
714 alice,
715 xrpAccount(),
716 XRP,
717 std::nullopt,
718 usd,
719 STPath(),
720 true,
722 ammContext,
723 std::nullopt,
724 flowJournal);
725 BEAST_EXPECT(r.first == temBAD_PATH);
726 }
727 {
728 // The root account can't be the src
729 auto r = toStrand(
730 *env.current(),
731 xrpAccount(),
732 alice,
733 XRP,
734 std::nullopt,
735 std::nullopt,
736 STPath(),
737 true,
739 ammContext,
740 std::nullopt,
741 flowJournal);
742 BEAST_EXPECT(r.first == temBAD_PATH);
743 }
744 {
745 // The root account can't be the src.
746 auto r = toStrand(
747 *env.current(),
748 noAccount(),
749 bob,
750 usd,
751 std::nullopt,
752 std::nullopt,
753 STPath(),
754 true,
756 ammContext,
757 std::nullopt,
758 flowJournal);
759 BEAST_EXPECT(r.first == temBAD_PATH);
760 }
761 }
762
763 // Create an offer with the same in/out issue
764 test(env, eur, usd, STPath({ipe(usd), ipe(eur)}), temBAD_PATH);
765
766 // Path element with type zero
767 test(
768 env,
769 usd,
770 std::nullopt,
773
774 // The same account can't appear more than once on a path
775 // `gw` will be used from alice->carol and implied between carol
776 // and bob
777 test(env, usd, std::nullopt, STPath({ape(gw), ape(carol)}), temBAD_PATH_LOOP);
778
779 // The same offer can't appear more than once on a path
780 test(env, eur, usd, STPath({ipe(eur), ipe(usd), ipe(eur)}), temBAD_PATH_LOOP);
781 }
782
783 {
784 // cannot have more than one offer with the same output issue
785
786 using namespace jtx;
787 Env env(*this, features);
788
789 env.fund(XRP(10000), alice, bob, carol, gw);
790 env.trust(usd(10000), alice, bob, carol);
791 env.trust(eur(10000), alice, bob, carol);
792
793 env(pay(gw, bob, usd(100)));
794 env(pay(gw, bob, eur(100)));
795
796 env(offer(bob, XRP(100), usd(100)));
797 env(offer(bob, usd(100), eur(100)), Txflags(tfPassive));
798 env(offer(bob, eur(100), usd(100)), Txflags(tfPassive));
799
800 // payment path: XRP -> XRP/USD -> USD/EUR -> EUR/USD
801 env(pay(alice, carol, usd(100)),
802 Path(~usd, ~eur, ~usd),
803 Sendmax(XRP(200)),
804 Txflags(tfNoRippleDirect),
806 }
807
808 {
809 Env env(*this, features);
810 env.fund(XRP(10000), alice, bob, noripple(gw));
811 env.trust(usd(1000), alice, bob);
812 env(pay(gw, alice, usd(100)));
813 test(env, usd, std::nullopt, STPath(), terNO_RIPPLE);
814 }
815
816 {
817 // check global freeze
818 Env env(*this, features);
819 env.fund(XRP(10000), alice, bob, gw);
820 env.trust(usd(1000), alice, bob);
821 env(pay(gw, alice, usd(100)));
822
823 // Account can still issue payments
824 env(fset(alice, asfGlobalFreeze));
825 test(env, usd, std::nullopt, STPath(), tesSUCCESS);
826 env(fclear(alice, asfGlobalFreeze));
827 test(env, usd, std::nullopt, STPath(), tesSUCCESS);
828
829 // Account can not issue funds
830 env(fset(gw, asfGlobalFreeze));
831 test(env, usd, std::nullopt, STPath(), terNO_LINE);
832 env(fclear(gw, asfGlobalFreeze));
833 test(env, usd, std::nullopt, STPath(), tesSUCCESS);
834
835 // Account can not receive funds
836 env(fset(bob, asfGlobalFreeze));
837 test(env, usd, std::nullopt, STPath(), terNO_LINE);
838 env(fclear(bob, asfGlobalFreeze));
839 test(env, usd, std::nullopt, STPath(), tesSUCCESS);
840 }
841 {
842 // Freeze between gw and alice
843 Env env(*this, features);
844 env.fund(XRP(10000), alice, bob, gw);
845 env.trust(usd(1000), alice, bob);
846 env(pay(gw, alice, usd(100)));
847 test(env, usd, std::nullopt, STPath(), tesSUCCESS);
848 env(trust(gw, alice["USD"](0), tfSetFreeze));
849 BEAST_EXPECT(getTrustFlag(env, gw, alice, usdC, TrustFlag::Freeze));
850 test(env, usd, std::nullopt, STPath(), terNO_LINE);
851 }
852 {
853 // check no auth
854 // An account may require authorization to receive IOUs from an
855 // issuer
856 Env env(*this, features);
857 env.fund(XRP(10000), alice, bob, gw);
858 env(fset(gw, asfRequireAuth));
859 env.trust(usd(1000), alice, bob);
860 // Authorize alice but not bob
861 env(trust(gw, alice["USD"](1000), tfSetfAuth));
862 BEAST_EXPECT(getTrustFlag(env, gw, alice, usdC, TrustFlag::Auth));
863 env(pay(gw, alice, usd(100)));
864 env.require(Balance(alice, usd(100)));
865 test(env, usd, std::nullopt, STPath(), terNO_AUTH);
866
867 // Check pure issue redeem still works
868 auto [ter, strand] = toStrand(
869 *env.current(),
870 alice,
871 gw,
872 usd,
873 std::nullopt,
874 std::nullopt,
875 STPath(),
876 true,
878 ammContext,
879 std::nullopt,
880 env.app().getJournal("Flow"));
881 BEAST_EXPECT(isTesSuccess(ter));
882 BEAST_EXPECT(equal(strand, D{alice, gw, usdC}));
883 }
884
885 {
886 // last step xrp from offer
887 Env env(*this, features);
888 env.fund(XRP(10000), alice, bob, gw);
889 env.trust(usd(1000), alice, bob);
890 env(pay(gw, alice, usd(100)));
891
892 // alice -> USD/XRP -> bob
893 STPath path;
894 path.emplaceBack(std::nullopt, xrpCurrency(), std::nullopt);
895
896 auto [ter, strand] = toStrand(
897 *env.current(),
898 alice,
899 bob,
900 XRP,
901 std::nullopt,
902 usd,
903 path,
904 false,
906 ammContext,
907 std::nullopt,
908 env.app().getJournal("Flow"));
909 BEAST_EXPECT(isTesSuccess(ter));
910 BEAST_EXPECT(
911 equal(strand, D{alice, gw, usdC}, B{usd, xrpIssue(), std::nullopt}, XRPS{bob}));
912 }
913 }
914
915 void
917 {
918 using namespace jtx;
919 testcase("RIPD1373");
920
921 auto const alice = Account("alice");
922 auto const bob = Account("bob");
923 auto const carol = Account("carol");
924 auto const gw = Account("gw");
925 auto const usd = gw["USD"];
926 auto const eur = gw["EUR"];
927
928 {
929 Env env(*this, features);
930 env.fund(XRP(10000), alice, bob, gw);
931
932 env.trust(usd(1000), alice, bob);
933 env.trust(eur(1000), alice, bob);
934 env.trust(bob["USD"](1000), alice, gw);
935 env.trust(bob["EUR"](1000), alice, gw);
936
937 env(offer(bob, XRP(100), bob["USD"](100)), Txflags(tfPassive));
938 env(offer(gw, XRP(100), usd(100)), Txflags(tfPassive));
939
940 env(offer(bob, bob["USD"](100), bob["EUR"](100)), Txflags(tfPassive));
941 env(offer(gw, usd(100), eur(100)), Txflags(tfPassive));
942
943 TestPath const p = [&] {
944 TestPath result;
945 result.pushBack(allPathElements(gw, bob["USD"]));
946 result.pushBack(cpe(eur.currency));
947 return result;
948 }();
949
950 PathSet const paths(p);
951
952 env(pay(alice, alice, eur(1)),
953 Json(paths.json()),
954 Sendmax(XRP(10)),
955 Txflags(tfNoRippleDirect | tfPartialPayment),
957 }
958
959 {
960 Env env(*this, features);
961
962 env.fund(XRP(10000), alice, bob, carol, gw);
963 env.trust(usd(10000), alice, bob, carol);
964
965 env(pay(gw, bob, usd(100)));
966
967 env(offer(bob, XRP(100), usd(100)), Txflags(tfPassive));
968 env(offer(bob, usd(100), XRP(100)), Txflags(tfPassive));
969
970 // payment path: XRP -> XRP/USD -> USD/XRP
971 env(pay(alice, carol, XRP(100)),
972 Path(~usd, ~XRP),
973 Txflags(tfNoRippleDirect),
975 }
976
977 {
978 Env env(*this, features);
979
980 env.fund(XRP(10000), alice, bob, carol, gw);
981 env.trust(usd(10000), alice, bob, carol);
982
983 env(pay(gw, bob, usd(100)));
984
985 env(offer(bob, XRP(100), usd(100)), Txflags(tfPassive));
986 env(offer(bob, usd(100), XRP(100)), Txflags(tfPassive));
987
988 // payment path: XRP -> XRP/USD -> USD/XRP
989 env(pay(alice, carol, XRP(100)),
990 Path(~usd, ~XRP),
991 Sendmax(XRP(200)),
992 Txflags(tfNoRippleDirect),
994 }
995 }
996
997 void
999 {
1000 testcase("test loop");
1001 using namespace jtx;
1002
1003 auto const alice = Account("alice");
1004 auto const bob = Account("bob");
1005 auto const carol = Account("carol");
1006 auto const gw = Account("gw");
1007 auto const usd = gw["USD"];
1008 auto const eur = gw["EUR"];
1009 auto const cny = gw["CNY"];
1010
1011 {
1012 Env env(*this, features);
1013
1014 env.fund(XRP(10000), alice, bob, carol, gw);
1015 env.trust(usd(10000), alice, bob, carol);
1016
1017 env(pay(gw, bob, usd(100)));
1018 env(pay(gw, alice, usd(100)));
1019
1020 env(offer(bob, XRP(100), usd(100)), Txflags(tfPassive));
1021 env(offer(bob, usd(100), XRP(100)), Txflags(tfPassive));
1022
1023 // payment path: USD -> USD/XRP -> XRP/USD
1024 env(pay(alice, carol, usd(100)),
1025 Sendmax(usd(100)),
1026 Path(~XRP, ~usd),
1027 Txflags(tfNoRippleDirect),
1029 }
1030 {
1031 Env env(*this, features);
1032
1033 env.fund(XRP(10000), alice, bob, carol, gw);
1034 env.trust(usd(10000), alice, bob, carol);
1035 env.trust(eur(10000), alice, bob, carol);
1036 env.trust(cny(10000), alice, bob, carol);
1037
1038 env(pay(gw, bob, usd(100)));
1039 env(pay(gw, bob, eur(100)));
1040 env(pay(gw, bob, cny(100)));
1041
1042 env(offer(bob, XRP(100), usd(100)), Txflags(tfPassive));
1043 env(offer(bob, usd(100), eur(100)), Txflags(tfPassive));
1044 env(offer(bob, eur(100), cny(100)), Txflags(tfPassive));
1045
1046 // payment path: XRP->XRP/USD->USD/EUR->USD/CNY
1047 env(pay(alice, carol, cny(100)),
1048 Sendmax(XRP(100)),
1049 Path(~usd, ~eur, ~usd, ~cny),
1050 Txflags(tfNoRippleDirect),
1052 }
1053 }
1054
1055 void
1057 {
1058 testcase("test no account");
1059 using namespace jtx;
1060
1061 auto const alice = Account("alice");
1062 auto const bob = Account("bob");
1063 auto const gw = Account("gw");
1064 auto const usd = gw["USD"];
1065
1066 Env env(*this, features);
1067 env.fund(XRP(10000), alice, bob, gw);
1068
1069 STAmount const sendMax{usd, 100, 1};
1070 STAmount const noAccountAmount{Issue{usd.currency, noAccount()}, 100, 1};
1071 STAmount const deliver;
1072 AccountID const srcAcc = alice.id();
1073 AccountID const dstAcc = bob.id();
1074 STPathSet const pathSet;
1076 inputs.defaultPathsAllowed = true;
1077 try
1078 {
1079 PaymentSandbox sb{env.current().get(), TapNone};
1080 {
1082 sb,
1083 sendMax,
1084 deliver,
1085 dstAcc,
1086 noAccount(),
1087 pathSet,
1088 std::nullopt,
1089 env.app(),
1090 &inputs);
1091 BEAST_EXPECT(r.result() == temBAD_PATH);
1092 }
1093 {
1095 sb,
1096 sendMax,
1097 deliver,
1098 noAccount(),
1099 srcAcc,
1100 pathSet,
1101 std::nullopt,
1102 env.app(),
1103 &inputs);
1104 BEAST_EXPECT(r.result() == temBAD_PATH);
1105 }
1106 {
1108 sb,
1109 noAccountAmount,
1110 deliver,
1111 dstAcc,
1112 srcAcc,
1113 pathSet,
1114 std::nullopt,
1115 env.app(),
1116 &inputs);
1117 BEAST_EXPECT(r.result() == temBAD_PATH);
1118 }
1119 {
1121 sb,
1122 sendMax,
1123 noAccountAmount,
1124 dstAcc,
1125 srcAcc,
1126 pathSet,
1127 std::nullopt,
1128 env.app(),
1129 &inputs);
1130 BEAST_EXPECT(r.result() == temBAD_PATH);
1131 }
1132 }
1133 catch (...)
1134 {
1135 this->fail();
1136 }
1137 }
1138
1139 void
1140 run() override
1141 {
1142 using namespace jtx;
1143 auto const sa = testableAmendments();
1144 testToStrand(sa - featurePermissionedDEX);
1145 testToStrand(sa);
1146
1147 testRIPD1373(sa - featurePermissionedDEX);
1148 testRIPD1373(sa);
1149
1150 testLoop(sa - featurePermissionedDEX);
1151 testLoop(sa);
1152
1153 testNoAccount(sa);
1154 }
1155};
1156
1158
1159} // namespace xrpl::test
T any_of(T... args)
T back(T... args)
T begin(T... args)
A testsuite class.
Definition suite.h:52
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:554
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
Maintains AMM info per overall payment engine execution and individual iteration.
Definition AMMContext.h:17
Specifies an order book.
Definition Book.h:28
A currency issued by an account.
Definition Issue.h:18
A wrapper which makes credits unavailable to balances.
A view into a ledger.
Definition ReadView.h:41
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
virtual beast::Journal getJournal(std::string const &name)=0
static Output rippleCalculate(PaymentSandbox &view, STAmount const &saMaxAmountReq, STAmount const &saDstAmountReq, AccountID const &uDstAccountID, AccountID const &uSrcAccountID, STPathSet const &spsPaths, std::optional< uint256 > const &domainID, ServiceRegistry &registry, Input const *const pInputs=nullptr)
ElementComboIter(STPathElement const *prev=nullptr)
void emplaceInto(Col &col, AccFactory &&accF, IssFactory &&issF, CurrencyFactory &&currencyF, std::optional< AccountID > const &existingAcc, std::optional< Currency > const &existingCur, std::optional< AccountID > const &existingIss)
bool hasAny(std::initializer_list< SB > sb) const
STPathElement const * prev_
size_t count(std::initializer_list< SB > sb) const
json::Value json() const
Definition PathSet.h:183
TestPath & pushBack(Issue const &iss)
Definition PathSet.h:121
Immutable cryptographic account descriptor.
Definition jtx/Account.h:21
AccountID id() const
Returns the Account ID.
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
SLE::const_pointer le(Account const &account) const
Return an account root.
Definition Env.cpp:311
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:323
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:354
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:764
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:377
Inject raw JSON.
Definition jtx_json.h:16
Add a path.
Definition paths.h:47
Sets the SendMax on a JTx.
Definition sendmax.h:16
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:18
Set the flags on a JTx.
Definition txflags.h:14
T emplace_back(T... args)
T empty(T... args)
T end(T... args)
T forward(T... args)
T insert(T... args)
T make_tuple(T... args)
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:198
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:253
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:14
STPathElement allPathElements(AccountID const &a, Asset const &asset)
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition flags.h:110
FeatureBitset testableAmendments()
Definition Env.h:92
STPathElement cpe(PathAsset const &pa)
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.
Definition Env.h:86
bool equal(STAmount const &sa1, STAmount const &sa2)
STPathElement iape(AccountID const &account)
json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:14
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:18
STPathElement ape(AccountID const &a)
json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:15
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
bool getTrustFlag(jtx::Env const &env, jtx::Account const &src, jtx::Account const &dst, Currency const &cur, TrustFlag flag)
std::uint32_t trustFlag(TrustFlag f, bool useHigh)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ terNO_LINE
Definition TER.h:215
@ terNO_AUTH
Definition TER.h:214
@ terNO_RIPPLE
Definition TER.h:220
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:108
std::pair< TER, Strand > toStrand(ReadView const &sb, AccountID const &src, AccountID const &dst, Asset const &deliver, std::optional< Quality > const &limitQuality, std::optional< Asset > const &sendMaxAsset, STPath const &path, bool ownerPaysTransferFee, OfferCrossing offerCrossing, AMMContext &ammContext, std::optional< uint256 > const &domainID, beast::Journal j)
Create a Strand for the specified path.
Definition PaySteps.cpp:170
BaseUInt< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition UintTypes.h:42
bool toCurrency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Definition UintTypes.cpp:65
constexpr Dest safeCast(Src s) noexcept
Definition safe_cast.h:21
Currency const & xrpCurrency()
XRP currency.
Definition UintTypes.cpp:99
@ TapNone
Definition ApplyView.h:28
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
AccountID const & noAccount()
A placeholder for empty accounts.
@ temBAD_PATH
Definition TER.h:84
@ temBAD_SEND_XRP_PATHS
Definition TER.h:91
@ temBAD_SEND_XRP_MAX
Definition TER.h:88
@ temBAD_PATH_LOOP
Definition TER.h:85
bool isTesSuccess(TER x) noexcept
Definition TER.h:676
TERSubset< CanCvtToTER > TER
Definition TER.h:647
AccountID const & xrpAccount()
Compute AccountID from public key.
@ tecPATH_DRY
Definition TER.h:297
@ tesSUCCESS
Definition TER.h:245
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:52
T reserve(T... args)
T size(T... args)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
std::vector< jtx::Account > accounts
std::int64_t totalXRP(ReadView const &v, bool incRoot)
bool checkBalances(ReadView const &v1, ReadView const &v2)
std::vector< xrpl::Currency > currencies
void resetTo(ResetState const &s)
jtx::Account getAccount(size_t id)
void setupEnv(jtx::Env &env, size_t numAct, size_t numCur, std::optional< size_t > const &offererIndex)
void forEachElementPair(STAmount const &sendMax, STAmount const &deliver, std::vector< STPathElement > const &prefix, std::vector< STPathElement > const &suffix, std::optional< AccountID > const &existingAcc, std::optional< Currency > const &existingCur, std::optional< AccountID > const &existingIss, F &&f)
std::vector< std::string > currencyNames
xrpl::Currency getCurrency(size_t id)
std::tuple< size_t, size_t > ResetState
void testRIPD1373(FeatureBitset features)
void testLoop(FeatureBitset features)
void run() override
Runs the suite.
void testToStrand(FeatureBitset features)
void testNoAccount(FeatureBitset features)
T tie(T... args)
T to_string(T... args)