rippled
Loading...
Searching...
No Matches
PayStrand_test.cpp
1#include <test/jtx.h>
2#include <test/jtx/PathSet.h>
3
4#include <xrpld/app/paths/AMMContext.h>
5#include <xrpld/app/paths/RippleCalc.h>
6#include <xrpld/app/paths/detail/Steps.h>
7#include <xrpld/core/Config.h>
8
9#include <xrpl/basics/contract.h>
10#include <xrpl/basics/safe_cast.h>
11#include <xrpl/ledger/PaymentSandbox.h>
12#include <xrpl/protocol/Feature.h>
13#include <xrpl/protocol/jss.h>
14
15#include <optional>
16
17namespace xrpl {
18namespace test {
19
26
31
32enum class TrustFlag { freeze, auth, noripple };
33
34/*constexpr*/ std::uint32_t
35trustFlag(TrustFlag f, bool useHigh)
36{
37 switch (f)
38 {
40 if (useHigh)
41 return lsfHighFreeze;
42 return lsfLowFreeze;
43 case TrustFlag::auth:
44 if (useHigh)
45 return lsfHighAuth;
46 return lsfLowAuth;
48 if (useHigh)
49 return lsfHighNoRipple;
50 return lsfLowNoRipple;
51 }
52 return 0; // Silence warning about end of non-void function
53}
54
55bool
56getTrustFlag(jtx::Env const& env, jtx::Account const& src, jtx::Account const& dst, Currency const& cur, TrustFlag flag)
57{
58 if (auto sle = env.le(keylet::line(src, dst, cur)))
59 {
60 auto const useHigh = src.id() > dst.id();
61 return sle->isFlag(trustFlag(flag, useHigh));
62 }
63 Throw<std::runtime_error>("No line in getTrustFlag");
64 return false; // silence warning
65}
66
67bool
69{
70 if (!s1)
71 return false;
72 return test::directStepEqual(*s1, dsi.src, dsi.dst, dsi.currency);
73}
74
75bool
77{
78 if (!s1)
79 return false;
80 return test::xrpEndpointStepEqual(*s1, xrpsi.acc);
81}
82
83bool
85{
86 if (!s1)
87 return false;
88 return bookStepEqual(*s1, bsi);
89}
90
91template <class Iter>
92bool
94{
95 // base case. all args processed and found equal.
96 return true;
97}
98
99template <class Iter, class StepInfo, class... Args>
100bool
101strandEqualHelper(Iter i, StepInfo&& si, Args&&... args)
102{
103 if (!equal(*i, std::forward<StepInfo>(si)))
104 return false;
105 return strandEqualHelper(++i, std::forward<Args>(args)...);
106}
107
108template <class... Args>
109bool
110equal(Strand const& strand, Args&&... args)
111{
112 if (strand.size() != sizeof...(Args))
113 return false;
114 if (strand.empty())
115 return true;
116 return strandEqualHelper(strand.begin(), std::forward<Args>(args)...);
117}
118
124
125// Issue path element
132
133// Issuer path element
135iape(AccountID const& account)
136{
138};
139
141{
142 enum class SB /*state bit*/
143 : std::uint16_t {
144 acc,
145 iss,
146 cur,
147 rootAcc,
148 rootIss,
149 xrp,
154 prevAcc,
155 prevCur,
156 prevIss,
157 boundary,
158 last
159 };
160
162 static_assert(safe_cast<size_t>(SB::last) <= sizeof(decltype(state_)) * 8, "");
163 STPathElement const* prev_ = nullptr;
164 // disallow iss and cur to be specified with acc is specified (simplifies
165 // some tests)
166 bool const allowCompound_ = false;
167
168 bool
169 has(SB s) const
170 {
171 return state_ & (1 << safe_cast<int>(s));
172 }
173
174 bool
176 {
177 for (auto const s : sb)
178 if (has(s))
179 return true;
180 return false;
181 }
182
183 size_t
185 {
186 size_t result = 0;
187
188 for (auto const s : sb)
189 if (has(s))
190 result++;
191 return result;
192 }
193
194public:
195 explicit ElementComboIter(STPathElement const* prev = nullptr) : prev_(prev)
196 {
197 }
198
199 bool
212 bool
214 {
215 if (!(has(SB::last)))
216 {
217 do
218 {
219 ++state_;
220 } while (!valid());
221 }
222 return !has(SB::last);
223 }
224
225 template <class Col, class AccFactory, class IssFactory, class CurrencyFactory>
226 void
228 Col& col,
229 AccFactory&& accF,
230 IssFactory&& issF,
231 CurrencyFactory&& currencyF,
235 {
236 assert(!has(SB::last));
237
238 auto const acc = [&]() -> std::optional<AccountID> {
239 if (!has(SB::acc))
240 return std::nullopt;
241 if (has(SB::rootAcc))
242 return xrpAccount();
244 return existingAcc;
245 return accF().id();
246 }();
247 auto const iss = [&]() -> std::optional<AccountID> {
248 if (!has(SB::iss))
249 return std::nullopt;
250 if (has(SB::rootIss))
251 return xrpAccount();
252 if (has(SB::sameAccIss))
253 return acc;
255 return *existingIss;
256 return issF().id();
257 }();
258 auto const cur = [&]() -> std::optional<Currency> {
259 if (!has(SB::cur))
260 return std::nullopt;
261 if (has(SB::xrp))
262 return xrpCurrency();
264 return *existingCur;
265 return currencyF();
266 }();
267 if (!has(SB::boundary))
268 col.emplace_back(acc, cur, iss);
269 else
270 col.emplace_back(
272 acc.value_or(AccountID{}),
273 cur.value_or(Currency{}),
274 iss.value_or(AccountID{}));
275 }
276};
277
279{
283
285 getAccount(size_t id)
286 {
287 assert(id < accounts.size());
288 return accounts[id];
289 }
290
292 getCurrency(size_t id)
293 {
294 assert(id < currencies.size());
295 return currencies[id];
296 }
297
298 // ids from 0 through (nextAvail -1) have already been used in the
299 // path
302
309
310 void
315
317 {
320
322 {
323 }
325 {
327 }
328 };
329
330 // Create the given number of accounts, and add trust lines so every
331 // account trusts every other with every currency
332 // Create an offer from every currency/account to every other
333 // currency/account; the offer owner is either the specified
334 // account or the issuer of the "taker gets" account
335 void
336 setupEnv(jtx::Env& env, size_t numAct, size_t numCur, std::optional<size_t> const& offererIndex)
337 {
338 using namespace jtx;
339
340 assert(!offererIndex || offererIndex < numAct);
341
342 accounts.clear();
343 accounts.reserve(numAct);
344 currencies.clear();
345 currencies.reserve(numCur);
347 currencyNames.reserve(numCur);
348
349 constexpr size_t bufSize = 32;
350 char buf[bufSize];
351
352 for (size_t id = 0; id < numAct; ++id)
353 {
354 snprintf(buf, bufSize, "A%zu", id);
355 accounts.emplace_back(buf);
356 }
357
358 for (size_t id = 0; id < numCur; ++id)
359 {
360 if (id < 10)
361 snprintf(buf, bufSize, "CC%zu", id);
362 else if (id < 100)
363 snprintf(buf, bufSize, "C%zu", id);
364 else
365 snprintf(buf, bufSize, "%zu", id);
366 currencies.emplace_back(to_currency(buf));
368 }
369
370 for (auto const& a : accounts)
371 env.fund(XRP(100000), a);
372
373 // Every account trusts every other account with every currency
374 for (auto ai1 = accounts.begin(), aie = accounts.end(); ai1 != aie; ++ai1)
375 {
376 for (auto ai2 = accounts.begin(); ai2 != aie; ++ai2)
377 {
378 if (ai1 == ai2)
379 continue;
380 for (auto const& cn : currencyNames)
381 {
382 env.trust((*ai1)[cn](1'000'000), *ai2);
383 if (ai1 > ai2)
384 {
385 // accounts with lower indexes hold balances from
386 // accounts
387 // with higher indexes
388 auto const& src = *ai1;
389 auto const& dst = *ai2;
390 env(pay(src, dst, src[cn](500000)));
391 }
392 }
393 env.close();
394 }
395 }
396
397 std::vector<IOU> ious;
398 ious.reserve(numAct * numCur);
399 for (auto const& a : accounts)
400 for (auto const& cn : currencyNames)
401 ious.emplace_back(a[cn]);
402
403 // create offers from every currency to every other currency
404 for (auto takerPays = ious.begin(), ie = ious.end(); takerPays != ie; ++takerPays)
405 {
406 for (auto takerGets = ious.begin(); takerGets != ie; ++takerGets)
407 {
408 if (takerPays == takerGets)
409 continue;
410 auto const owner = offererIndex ? accounts[*offererIndex] : takerGets->account;
411 if (owner.id() != takerGets->account.id())
412 env(pay(takerGets->account, owner, (*takerGets)(1000)));
413
414 env(offer(owner, (*takerPays)(1000), (*takerGets)(1000)), txflags(tfPassive));
415 }
416 env.close();
417 }
418
419 // create offers to/from xrp to every other ious
420 for (auto const& iou : ious)
421 {
422 auto const owner = offererIndex ? accounts[*offererIndex] : iou.account;
423 env(offer(owner, iou(1000), XRP(1000)), txflags(tfPassive));
424 env(offer(owner, XRP(1000), iou(1000)), txflags(tfPassive));
425 env.close();
426 }
427 }
428
430 totalXRP(ReadView const& v, bool incRoot)
431 {
433 auto add = [&](auto const& a) {
434 // XRP balance
435 auto const sle = v.read(keylet::account(a));
436 if (!sle)
437 return;
438 auto const b = (*sle)[sfBalance];
439 totalXRP += b.mantissa();
440 };
441 for (auto const& a : accounts)
442 add(a);
443 if (incRoot)
444 add(xrpAccount());
445 return totalXRP;
446 }
447
448 // Check that the balances for all accounts for all currencies & XRP are the
449 // same
450 bool
451 checkBalances(ReadView const& v1, ReadView const& v2)
452 {
454
455 auto xrpBalance = [](ReadView const& v, xrpl::Keylet const& k) {
456 auto const sle = v.read(k);
457 if (!sle)
458 return STAmount{};
459 return (*sle)[sfBalance];
460 };
461 auto lineBalance = [](ReadView const& v, xrpl::Keylet const& k) {
462 auto const sle = v.read(k);
463 if (!sle)
464 return STAmount{};
465 return (*sle)[sfBalance];
466 };
468 for (auto ai1 = accounts.begin(), aie = accounts.end(); ai1 != aie; ++ai1)
469 {
470 {
471 // XRP balance
472 auto const ak = keylet::account(*ai1);
473 auto const b1 = xrpBalance(v1, ak);
474 auto const b2 = xrpBalance(v2, ak);
475 totalXRP[0] += b1.mantissa();
476 totalXRP[1] += b2.mantissa();
477 if (b1 != b2)
478 diffs.emplace_back(b1, b2, xrpAccount(), *ai1);
479 }
480 for (auto ai2 = accounts.begin(); ai2 != aie; ++ai2)
481 {
482 if (ai1 >= ai2)
483 continue;
484 for (auto const& c : currencies)
485 {
486 // Line balance
487 auto const lk = keylet::line(*ai1, *ai2, c);
488 auto const b1 = lineBalance(v1, lk);
489 auto const b2 = lineBalance(v2, lk);
490 if (b1 != b2)
491 diffs.emplace_back(b1, b2, *ai1, *ai2);
492 }
493 }
494 }
495 return diffs.empty();
496 }
497
500 {
502 }
503
506 {
508 }
509
510 template <class F>
511 void
513 STAmount const& sendMax,
514 STAmount const& deliver,
515 std::vector<STPathElement> const& prefix,
516 std::vector<STPathElement> const& suffix,
517 std::optional<AccountID> const& existingAcc,
518 std::optional<Currency> const& existingCur,
519 std::optional<AccountID> const& existingIss,
520 F&& f)
521 {
522 auto accF = [&] { return this->getAvailAccount(); };
523 auto issF = [&] { return this->getAvailAccount(); };
524 auto currencyF = [&] { return this->getAvailCurrency(); };
525
526 STPathElement const* prevOuter = prefix.empty() ? nullptr : &prefix.back();
527 ElementComboIter outer(prevOuter);
528
529 std::vector<STPathElement> outerResult;
531 auto const resultSize = prefix.size() + suffix.size() + 2;
532 outerResult.reserve(resultSize);
533 result.reserve(resultSize);
534 while (outer.next())
535 {
536 StateGuard og{*this};
537 outerResult = prefix;
538 outer.emplace_into(outerResult, accF, issF, currencyF, existingAcc, existingCur, existingIss);
539 STPathElement const* prevInner = &outerResult.back();
540 ElementComboIter inner(prevInner);
541 while (inner.next())
542 {
543 StateGuard ig{*this};
544 result = outerResult;
545 inner.emplace_into(result, accF, issF, currencyF, existingAcc, existingCur, existingIss);
546 result.insert(result.end(), suffix.begin(), suffix.end());
547 f(sendMax, deliver, result);
548 }
549 };
550 }
551};
552
554{
555 void
557 {
558 testcase("To Strand");
559
560 using namespace jtx;
561
562 auto const alice = Account("alice");
563 auto const bob = Account("bob");
564 auto const carol = Account("carol");
565 auto const gw = Account("gw");
566
567 auto const USD = gw["USD"];
568 auto const EUR = gw["EUR"];
569
570 auto const eurC = EUR.currency;
571 auto const usdC = USD.currency;
572
573 using D = DirectStepInfo;
574 using B = xrpl::Book;
575 using XRPS = XRPEndpointStepInfo;
576
577 AMMContext ammContext(alice, false);
578
579 auto test = [&, this](
580 jtx::Env& env,
581 Issue const& deliver,
582 std::optional<Issue> const& sendMaxIssue,
583 STPath const& path,
584 TER expTer,
585 auto&&... expSteps) {
586 auto [ter, strand] = toStrand(
587 *env.current(),
588 alice,
589 bob,
590 deliver,
592 sendMaxIssue,
593 path,
594 true,
596 ammContext,
598 env.app().logs().journal("Flow"));
599 BEAST_EXPECT(ter == expTer);
600 if (sizeof...(expSteps) != 0)
601 BEAST_EXPECT(equal(strand, std::forward<decltype(expSteps)>(expSteps)...));
602 };
603
604 {
605 Env env(*this, features);
606 env.fund(XRP(10000), alice, bob, gw);
607 env.trust(USD(1000), alice, bob);
608 env.trust(EUR(1000), alice, bob);
609 env(pay(gw, alice, EUR(100)));
610
611 {
612 STPath const path = STPath({ipe(bob["USD"]), cpe(EUR.currency)});
613 auto [ter, _] = toStrand(
614 *env.current(),
615 alice,
616 alice,
617 /*deliver*/ xrpIssue(),
618 /*limitQuality*/ std::nullopt,
619 /*sendMaxIssue*/ EUR.issue(),
620 path,
621 true,
623 ammContext,
625 env.app().logs().journal("Flow"));
626 (void)_;
627 BEAST_EXPECT(ter == tesSUCCESS);
628 }
629 {
630 STPath const path = STPath({ipe(USD), cpe(xrpCurrency())});
631 auto [ter, _] = toStrand(
632 *env.current(),
633 alice,
634 alice,
635 /*deliver*/ xrpIssue(),
636 /*limitQuality*/ std::nullopt,
637 /*sendMaxIssue*/ EUR.issue(),
638 path,
639 true,
641 ammContext,
643 env.app().logs().journal("Flow"));
644 (void)_;
645 BEAST_EXPECT(ter == tesSUCCESS);
646 }
647 }
648
649 {
650 Env env(*this, features);
651 env.fund(XRP(10000), alice, bob, carol, gw);
652
653 test(env, USD, std::nullopt, STPath(), terNO_LINE);
654
655 env.trust(USD(1000), alice, bob, carol);
656 test(env, USD, std::nullopt, STPath(), tecPATH_DRY);
657
658 env(pay(gw, alice, USD(100)));
659 env(pay(gw, carol, USD(100)));
660
661 // Insert implied account
662 test(env, USD, std::nullopt, STPath(), tesSUCCESS, D{alice, gw, usdC}, D{gw, bob, usdC});
663 env.trust(EUR(1000), alice, bob);
664
665 // Insert implied offer
666 test(
667 env,
668 EUR,
669 USD.issue(),
670 STPath(),
672 D{alice, gw, usdC},
673 B{USD, EUR, std::nullopt},
674 D{gw, bob, eurC});
675
676 // Path with explicit offer
677 test(
678 env,
679 EUR,
680 USD.issue(),
681 STPath({ipe(EUR)}),
683 D{alice, gw, usdC},
684 B{USD, EUR, std::nullopt},
685 D{gw, bob, eurC});
686
687 // Path with offer that changes issuer only
688 env.trust(carol["USD"](1000), bob);
689 test(
690 env,
691 carol["USD"],
692 USD.issue(),
693 STPath({iape(carol)}),
695 D{alice, gw, usdC},
696 B{USD, carol["USD"], std::nullopt},
697 D{carol, bob, usdC});
698
699 // Path with XRP src currency
700 test(
701 env,
702 USD,
703 xrpIssue(),
704 STPath({ipe(USD)}),
706 XRPS{alice},
707 B{XRP, USD, std::nullopt},
708 D{gw, bob, usdC});
709
710 // Path with XRP dst currency.
711 test(
712 env,
713 xrpIssue(),
714 USD.issue(),
717 D{alice, gw, usdC},
718 B{USD, XRP, std::nullopt},
719 XRPS{bob});
720
721 // Path with XRP cross currency bridged payment
722 test(
723 env,
724 EUR,
725 USD.issue(),
726 STPath({cpe(xrpCurrency())}),
728 D{alice, gw, usdC},
729 B{USD, XRP, std::nullopt},
730 B{XRP, EUR, std::nullopt},
731 D{gw, bob, eurC});
732
733 // XRP -> XRP transaction can't include a path
734 test(env, XRP, std::nullopt, STPath({ape(carol)}), temBAD_PATH);
735
736 {
737 // The root account can't be the src or dst
738 auto flowJournal = env.app().logs().journal("Flow");
739 {
740 // The root account can't be the dst
741 auto r = toStrand(
742 *env.current(),
743 alice,
744 xrpAccount(),
745 XRP,
747 USD.issue(),
748 STPath(),
749 true,
751 ammContext,
753 flowJournal);
754 BEAST_EXPECT(r.first == temBAD_PATH);
755 }
756 {
757 // The root account can't be the src
758 auto r = toStrand(
759 *env.current(),
760 xrpAccount(),
761 alice,
762 XRP,
765 STPath(),
766 true,
768 ammContext,
770 flowJournal);
771 BEAST_EXPECT(r.first == temBAD_PATH);
772 }
773 {
774 // The root account can't be the src.
775 auto r = toStrand(
776 *env.current(),
777 noAccount(),
778 bob,
779 USD,
782 STPath(),
783 true,
785 ammContext,
787 flowJournal);
788 BEAST_EXPECT(r.first == temBAD_PATH);
789 }
790 }
791
792 // Create an offer with the same in/out issue
793 test(env, EUR, USD.issue(), STPath({ipe(USD), ipe(EUR)}), temBAD_PATH);
794
795 // Path element with type zero
796 test(
797 env,
798 USD,
802
803 // The same account can't appear more than once on a path
804 // `gw` will be used from alice->carol and implied between carol
805 // and bob
806 test(env, USD, std::nullopt, STPath({ape(gw), ape(carol)}), temBAD_PATH_LOOP);
807
808 // The same offer can't appear more than once on a path
809 test(env, EUR, USD.issue(), STPath({ipe(EUR), ipe(USD), ipe(EUR)}), temBAD_PATH_LOOP);
810 }
811
812 {
813 // cannot have more than one offer with the same output issue
814
815 using namespace jtx;
816 Env env(*this, features);
817
818 env.fund(XRP(10000), alice, bob, carol, gw);
819 env.trust(USD(10000), alice, bob, carol);
820 env.trust(EUR(10000), alice, bob, carol);
821
822 env(pay(gw, bob, USD(100)));
823 env(pay(gw, bob, EUR(100)));
824
825 env(offer(bob, XRP(100), USD(100)));
826 env(offer(bob, USD(100), EUR(100)), txflags(tfPassive));
827 env(offer(bob, EUR(100), USD(100)), txflags(tfPassive));
828
829 // payment path: XRP -> XRP/USD -> USD/EUR -> EUR/USD
830 env(pay(alice, carol, USD(100)),
831 path(~USD, ~EUR, ~USD),
832 sendmax(XRP(200)),
835 }
836
837 {
838 Env env(*this, features);
839 env.fund(XRP(10000), alice, bob, noripple(gw));
840 env.trust(USD(1000), alice, bob);
841 env(pay(gw, alice, USD(100)));
842 test(env, USD, std::nullopt, STPath(), terNO_RIPPLE);
843 }
844
845 {
846 // check global freeze
847 Env env(*this, features);
848 env.fund(XRP(10000), alice, bob, gw);
849 env.trust(USD(1000), alice, bob);
850 env(pay(gw, alice, USD(100)));
851
852 // Account can still issue payments
853 env(fset(alice, asfGlobalFreeze));
854 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
855 env(fclear(alice, asfGlobalFreeze));
856 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
857
858 // Account can not issue funds
859 env(fset(gw, asfGlobalFreeze));
860 test(env, USD, std::nullopt, STPath(), terNO_LINE);
861 env(fclear(gw, asfGlobalFreeze));
862 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
863
864 // Account can not receive funds
865 env(fset(bob, asfGlobalFreeze));
866 test(env, USD, std::nullopt, STPath(), terNO_LINE);
867 env(fclear(bob, asfGlobalFreeze));
868 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
869 }
870 {
871 // Freeze between gw and alice
872 Env env(*this, features);
873 env.fund(XRP(10000), alice, bob, gw);
874 env.trust(USD(1000), alice, bob);
875 env(pay(gw, alice, USD(100)));
876 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
877 env(trust(gw, alice["USD"](0), tfSetFreeze));
878 BEAST_EXPECT(getTrustFlag(env, gw, alice, usdC, TrustFlag::freeze));
879 test(env, USD, std::nullopt, STPath(), terNO_LINE);
880 }
881 {
882 // check no auth
883 // An account may require authorization to receive IOUs from an
884 // issuer
885 Env env(*this, features);
886 env.fund(XRP(10000), alice, bob, gw);
887 env(fset(gw, asfRequireAuth));
888 env.trust(USD(1000), alice, bob);
889 // Authorize alice but not bob
890 env(trust(gw, alice["USD"](1000), tfSetfAuth));
891 BEAST_EXPECT(getTrustFlag(env, gw, alice, usdC, TrustFlag::auth));
892 env(pay(gw, alice, USD(100)));
893 env.require(balance(alice, USD(100)));
894 test(env, USD, std::nullopt, STPath(), terNO_AUTH);
895
896 // Check pure issue redeem still works
897 auto [ter, strand] = toStrand(
898 *env.current(),
899 alice,
900 gw,
901 USD,
904 STPath(),
905 true,
907 ammContext,
909 env.app().logs().journal("Flow"));
910 BEAST_EXPECT(ter == tesSUCCESS);
911 BEAST_EXPECT(equal(strand, D{alice, gw, usdC}));
912 }
913
914 {
915 // last step xrp from offer
916 Env env(*this, features);
917 env.fund(XRP(10000), alice, bob, gw);
918 env.trust(USD(1000), alice, bob);
919 env(pay(gw, alice, USD(100)));
920
921 // alice -> USD/XRP -> bob
922 STPath path;
923 path.emplace_back(std::nullopt, xrpCurrency(), std::nullopt);
924
925 auto [ter, strand] = toStrand(
926 *env.current(),
927 alice,
928 bob,
929 XRP,
931 USD.issue(),
932 path,
933 false,
935 ammContext,
937 env.app().logs().journal("Flow"));
938 BEAST_EXPECT(ter == tesSUCCESS);
939 BEAST_EXPECT(equal(strand, D{alice, gw, usdC}, B{USD.issue(), xrpIssue(), std::nullopt}, XRPS{bob}));
940 }
941 }
942
943 void
945 {
946 using namespace jtx;
947 testcase("RIPD1373");
948
949 auto const alice = Account("alice");
950 auto const bob = Account("bob");
951 auto const carol = Account("carol");
952 auto const gw = Account("gw");
953 auto const USD = gw["USD"];
954 auto const EUR = gw["EUR"];
955
956 {
957 Env env(*this, features);
958 env.fund(XRP(10000), alice, bob, gw);
959
960 env.trust(USD(1000), alice, bob);
961 env.trust(EUR(1000), alice, bob);
962 env.trust(bob["USD"](1000), alice, gw);
963 env.trust(bob["EUR"](1000), alice, gw);
964
965 env(offer(bob, XRP(100), bob["USD"](100)), txflags(tfPassive));
966 env(offer(gw, XRP(100), USD(100)), txflags(tfPassive));
967
968 env(offer(bob, bob["USD"](100), bob["EUR"](100)), txflags(tfPassive));
969 env(offer(gw, USD(100), EUR(100)), txflags(tfPassive));
970
971 Path const p = [&] {
972 Path result;
973 result.push_back(allpe(gw, bob["USD"]));
974 result.push_back(cpe(EUR.currency));
975 return result;
976 }();
977
978 PathSet paths(p);
979
980 env(pay(alice, alice, EUR(1)),
981 json(paths.json()),
982 sendmax(XRP(10)),
985 }
986
987 {
988 Env env(*this, features);
989
990 env.fund(XRP(10000), alice, bob, carol, gw);
991 env.trust(USD(10000), alice, bob, carol);
992
993 env(pay(gw, bob, USD(100)));
994
995 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
996 env(offer(bob, USD(100), XRP(100)), txflags(tfPassive));
997
998 // payment path: XRP -> XRP/USD -> USD/XRP
999 env(pay(alice, carol, XRP(100)), path(~USD, ~XRP), txflags(tfNoRippleDirect), ter(temBAD_SEND_XRP_PATHS));
1000 }
1001
1002 {
1003 Env env(*this, features);
1004
1005 env.fund(XRP(10000), alice, bob, carol, gw);
1006 env.trust(USD(10000), alice, bob, carol);
1007
1008 env(pay(gw, bob, USD(100)));
1009
1010 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
1011 env(offer(bob, USD(100), XRP(100)), txflags(tfPassive));
1012
1013 // payment path: XRP -> XRP/USD -> USD/XRP
1014 env(pay(alice, carol, XRP(100)),
1015 path(~USD, ~XRP),
1016 sendmax(XRP(200)),
1019 }
1020 }
1021
1022 void
1024 {
1025 testcase("test loop");
1026 using namespace jtx;
1027
1028 auto const alice = Account("alice");
1029 auto const bob = Account("bob");
1030 auto const carol = Account("carol");
1031 auto const gw = Account("gw");
1032 auto const USD = gw["USD"];
1033 auto const EUR = gw["EUR"];
1034 auto const CNY = gw["CNY"];
1035
1036 {
1037 Env env(*this, features);
1038
1039 env.fund(XRP(10000), alice, bob, carol, gw);
1040 env.trust(USD(10000), alice, bob, carol);
1041
1042 env(pay(gw, bob, USD(100)));
1043 env(pay(gw, alice, USD(100)));
1044
1045 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
1046 env(offer(bob, USD(100), XRP(100)), txflags(tfPassive));
1047
1048 // payment path: USD -> USD/XRP -> XRP/USD
1049 env(pay(alice, carol, USD(100)),
1050 sendmax(USD(100)),
1051 path(~XRP, ~USD),
1054 }
1055 {
1056 Env env(*this, features);
1057
1058 env.fund(XRP(10000), alice, bob, carol, gw);
1059 env.trust(USD(10000), alice, bob, carol);
1060 env.trust(EUR(10000), alice, bob, carol);
1061 env.trust(CNY(10000), alice, bob, carol);
1062
1063 env(pay(gw, bob, USD(100)));
1064 env(pay(gw, bob, EUR(100)));
1065 env(pay(gw, bob, CNY(100)));
1066
1067 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
1068 env(offer(bob, USD(100), EUR(100)), txflags(tfPassive));
1069 env(offer(bob, EUR(100), CNY(100)), txflags(tfPassive));
1070
1071 // payment path: XRP->XRP/USD->USD/EUR->USD/CNY
1072 env(pay(alice, carol, CNY(100)),
1073 sendmax(XRP(100)),
1074 path(~USD, ~EUR, ~USD, ~CNY),
1077 }
1078 }
1079
1080 void
1082 {
1083 testcase("test no account");
1084 using namespace jtx;
1085
1086 auto const alice = Account("alice");
1087 auto const bob = Account("bob");
1088 auto const gw = Account("gw");
1089 auto const USD = gw["USD"];
1090
1091 Env env(*this, features);
1092 env.fund(XRP(10000), alice, bob, gw);
1093
1094 STAmount sendMax{USD.issue(), 100, 1};
1095 STAmount noAccountAmount{Issue{USD.currency, noAccount()}, 100, 1};
1096 STAmount deliver;
1097 AccountID const srcAcc = alice.id();
1098 AccountID dstAcc = bob.id();
1099 STPathSet pathSet;
1101 inputs.defaultPathsAllowed = true;
1102 try
1103 {
1104 PaymentSandbox sb{env.current().get(), tapNONE};
1105 {
1107 sb, sendMax, deliver, dstAcc, noAccount(), pathSet, std::nullopt, env.app().logs(), &inputs);
1108 BEAST_EXPECT(r.result() == temBAD_PATH);
1109 }
1110 {
1112 sb, sendMax, deliver, noAccount(), srcAcc, pathSet, std::nullopt, env.app().logs(), &inputs);
1113 BEAST_EXPECT(r.result() == temBAD_PATH);
1114 }
1115 {
1117 sb, noAccountAmount, deliver, dstAcc, srcAcc, pathSet, std::nullopt, env.app().logs(), &inputs);
1118 BEAST_EXPECT(r.result() == temBAD_PATH);
1119 }
1120 {
1122 sb, sendMax, noAccountAmount, dstAcc, srcAcc, pathSet, std::nullopt, env.app().logs(), &inputs);
1123 BEAST_EXPECT(r.result() == temBAD_PATH);
1124 }
1125 }
1126 catch (...)
1127 {
1128 this->fail();
1129 }
1130 }
1131
1132 void
1133 run() override
1134 {
1135 using namespace jtx;
1136 auto const sa = testable_amendments();
1137 testToStrand(sa - featurePermissionedDEX);
1138 testToStrand(sa);
1139
1140 testRIPD1373(sa - featurePermissionedDEX);
1141 testRIPD1373(sa);
1142
1143 testLoop(sa - featurePermissionedDEX);
1144 testLoop(sa);
1145
1146 testNoAccount(sa);
1147 }
1148};
1149
1150BEAST_DEFINE_TESTSUITE(PayStrand, app, xrpl);
1151
1152} // namespace test
1153} // namespace xrpl
T back(T... args)
T begin(T... args)
A testsuite class.
Definition suite.h:52
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:148
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:517
Maintains AMM info per overall payment engine execution and individual iteration.
Definition AMMContext.h:17
virtual Logs & logs()=0
Specifies an order book.
Definition Book.h:17
A currency issued by an account.
Definition Issue.h:14
Currency currency
Definition Issue.h:16
AccountID account
Definition Issue.h:17
beast::Journal journal(std::string const &name)
Definition Log.cpp:134
A wrapper which makes credits unavailable to balances.
A view into a ledger.
Definition ReadView.h:32
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
Issue const & issue() const
Definition STAmount.h:455
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, Logs &l, Input const *const pInputs=nullptr)
ElementComboIter(STPathElement const *prev=nullptr)
void emplace_into(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
Path & push_back(Issue const &iss)
Definition PathSet.h:95
Immutable cryptographic account descriptor.
Definition Account.h:20
AccountID id() const
Returns the Account ID.
Definition Account.h:88
A transaction testing environment.
Definition Env.h:98
Application & app()
Definition Env.h:230
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:97
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition Env.cpp:248
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:260
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:283
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:512
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:298
A balance matches.
Definition balance.h:20
Inject raw JSON.
Definition jtx_json.h:14
Add a path.
Definition paths.h:38
Set Paths, SendMax on a JTx.
Definition paths.h:16
Sets the SendMax on a JTx.
Definition sendmax.h:14
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:16
Set the flags on a JTx.
Definition txflags.h:12
T clear(T... args)
T emplace_back(T... args)
T empty(T... args)
T end(T... args)
T forward(T... args)
T insert(T... args)
T is_same_v
T make_tuple(T... args)
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:214
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:160
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:13
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:90
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
STPathElement allpe(AccountID const &a, Issue const &iss)
Json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition flags.h:102
STPathElement cpe(Currency const &c)
FeatureBitset testable_amendments()
Definition Env.h:55
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:10
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:10
STPathElement ape(AccountID const &a)
bool bookStepEqual(Step const &step, xrpl::Book const &book)
STPathElement ipe(Issue const &iss)
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)
bool strandEqualHelper(Iter i)
bool xrpEndpointStepEqual(Step const &step, AccountID const &acc)
bool directStepEqual(Step const &step, AccountID const &src, AccountID const &dst, Currency const &currency)
bool equal(std::unique_ptr< Step > const &s1, DirectStepInfo const &dsi)
STPathElement iape(AccountID const &account)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
@ terNO_LINE
Definition TER.h:200
@ terNO_AUTH
Definition TER.h:199
@ terNO_RIPPLE
Definition TER.h:205
constexpr std::uint32_t asfGlobalFreeze
Definition TxFlags.h:64
constexpr std::uint32_t tfPassive
Definition TxFlags.h:79
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:98
Currency const & xrpCurrency()
XRP currency.
Definition UintTypes.cpp:96
constexpr std::uint32_t tfNoRippleDirect
Definition TxFlags.h:88
constexpr std::uint32_t tfSetfAuth
Definition TxFlags.h:96
@ tapNONE
Definition ApplyView.h:12
constexpr std::uint32_t asfRequireAuth
Definition TxFlags.h:59
AccountID const & noAccount()
A placeholder for empty accounts.
@ temBAD_PATH
Definition TER.h:77
@ temBAD_SEND_XRP_PATHS
Definition TER.h:84
@ temBAD_SEND_XRP_MAX
Definition TER.h:81
@ temBAD_PATH_LOOP
Definition TER.h:78
AccountID const & xrpAccount()
Compute AccountID from public key.
@ tecPATH_DRY
Definition TER.h:276
@ lsfLowNoRipple
@ lsfLowFreeze
@ lsfHighFreeze
@ lsfHighNoRipple
@ lsfHighAuth
constexpr std::uint32_t tfPartialPayment
Definition TxFlags.h:89
@ no
Definition Steps.h:26
bool to_currency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Definition UintTypes.cpp:62
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:99
@ tesSUCCESS
Definition TER.h:226
std::pair< TER, Strand > toStrand(ReadView const &view, AccountID const &src, AccountID const &dst, Issue const &deliver, std::optional< Quality > const &limitQuality, std::optional< Issue > const &sendMaxIssue, 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:100
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 for_each_element_pair(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)
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)