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 ripple {
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
57 jtx::Env const& env,
58 jtx::Account const& src,
59 jtx::Account const& dst,
60 Currency const& cur,
61 TrustFlag flag)
62{
63 if (auto sle = env.le(keylet::line(src, dst, cur)))
64 {
65 auto const useHigh = src.id() > dst.id();
66 return sle->isFlag(trustFlag(flag, useHigh));
67 }
68 Throw<std::runtime_error>("No line in getTrustFlag");
69 return false; // silence warning
70}
71
72bool
74{
75 if (!s1)
76 return false;
77 return test::directStepEqual(*s1, dsi.src, dsi.dst, dsi.currency);
78}
79
80bool
82{
83 if (!s1)
84 return false;
85 return test::xrpEndpointStepEqual(*s1, xrpsi.acc);
86}
87
88bool
90{
91 if (!s1)
92 return false;
93 return bookStepEqual(*s1, bsi);
94}
95
96template <class Iter>
97bool
99{
100 // base case. all args processed and found equal.
101 return true;
102}
103
104template <class Iter, class StepInfo, class... Args>
105bool
106strandEqualHelper(Iter i, StepInfo&& si, Args&&... args)
107{
108 if (!equal(*i, std::forward<StepInfo>(si)))
109 return false;
110 return strandEqualHelper(++i, std::forward<Args>(args)...);
111}
112
113template <class... Args>
114bool
115equal(Strand const& strand, Args&&... args)
116{
117 if (strand.size() != sizeof...(Args))
118 return false;
119 if (strand.empty())
120 return true;
121 return strandEqualHelper(strand.begin(), std::forward<Args>(args)...);
122}
123
125ape(AccountID const& a)
126{
127 return STPathElement(
129};
130
131// Issue path element
133ipe(Issue const& iss)
134{
135 return STPathElement(
137 xrpAccount(),
138 iss.currency,
139 iss.account);
140};
141
142// Issuer path element
144iape(AccountID const& account)
145{
146 return STPathElement(
148};
149
151{
152 enum class SB /*state bit*/
153 : std::uint16_t {
154 acc,
155 iss,
156 cur,
157 rootAcc,
158 rootIss,
159 xrp,
164 prevAcc,
165 prevCur,
166 prevIss,
167 boundary,
168 last
169 };
170
172 static_assert(
173 safe_cast<size_t>(SB::last) <= sizeof(decltype(state_)) * 8,
174 "");
175 STPathElement const* prev_ = nullptr;
176 // disallow iss and cur to be specified with acc is specified (simplifies
177 // some tests)
178 bool const allowCompound_ = false;
179
180 bool
181 has(SB s) const
182 {
183 return state_ & (1 << safe_cast<int>(s));
184 }
185
186 bool
188 {
189 for (auto const s : sb)
190 if (has(s))
191 return true;
192 return false;
193 }
194
195 size_t
197 {
198 size_t result = 0;
199
200 for (auto const s : sb)
201 if (has(s))
202 result++;
203 return result;
204 }
205
206public:
207 explicit ElementComboIter(STPathElement const* prev = nullptr) : prev_(prev)
208 {
209 }
210
211 bool
212 valid() const
213 {
214 return (allowCompound_ ||
215 !(has(SB::acc) && hasAny({SB::cur, SB::iss}))) &&
217 (!hasAny(
219 has(SB::acc)) &&
220 (!hasAny(
222 has(SB::iss)) &&
224 has(SB::cur)) &&
225 // These will be duplicates
229 }
230 bool
232 {
233 if (!(has(SB::last)))
234 {
235 do
236 {
237 ++state_;
238 } while (!valid());
239 }
240 return !has(SB::last);
241 }
242
243 template <
244 class Col,
245 class AccFactory,
246 class IssFactory,
247 class CurrencyFactory>
248 void
250 Col& col,
251 AccFactory&& accF,
252 IssFactory&& issF,
253 CurrencyFactory&& currencyF,
257 {
258 assert(!has(SB::last));
259
260 auto const acc = [&]() -> std::optional<AccountID> {
261 if (!has(SB::acc))
262 return std::nullopt;
263 if (has(SB::rootAcc))
264 return xrpAccount();
266 return existingAcc;
267 return accF().id();
268 }();
269 auto const iss = [&]() -> std::optional<AccountID> {
270 if (!has(SB::iss))
271 return std::nullopt;
272 if (has(SB::rootIss))
273 return xrpAccount();
274 if (has(SB::sameAccIss))
275 return acc;
277 return *existingIss;
278 return issF().id();
279 }();
280 auto const cur = [&]() -> std::optional<Currency> {
281 if (!has(SB::cur))
282 return std::nullopt;
283 if (has(SB::xrp))
284 return xrpCurrency();
286 return *existingCur;
287 return currencyF();
288 }();
289 if (!has(SB::boundary))
290 col.emplace_back(acc, cur, iss);
291 else
292 col.emplace_back(
294 acc.value_or(AccountID{}),
295 cur.value_or(Currency{}),
296 iss.value_or(AccountID{}));
297 }
298};
299
301{
305
307 getAccount(size_t id)
308 {
309 assert(id < accounts.size());
310 return accounts[id];
311 }
312
314 getCurrency(size_t id)
315 {
316 assert(id < currencies.size());
317 return currencies[id];
318 }
319
320 // ids from 0 through (nextAvail -1) have already been used in the
321 // path
324
331
332 void
337
339 {
342
344 : p_{p}, state_{p.getResetState()}
345 {
346 }
348 {
350 }
351 };
352
353 // Create the given number of accounts, and add trust lines so every
354 // account trusts every other with every currency
355 // Create an offer from every currency/account to every other
356 // currency/account; the offer owner is either the specified
357 // account or the issuer of the "taker gets" account
358 void
360 jtx::Env& env,
361 size_t numAct,
362 size_t numCur,
363 std::optional<size_t> const& offererIndex)
364 {
365 using namespace jtx;
366
367 assert(!offererIndex || offererIndex < numAct);
368
369 accounts.clear();
370 accounts.reserve(numAct);
371 currencies.clear();
372 currencies.reserve(numCur);
374 currencyNames.reserve(numCur);
375
376 constexpr size_t bufSize = 32;
377 char buf[bufSize];
378
379 for (size_t id = 0; id < numAct; ++id)
380 {
381 snprintf(buf, bufSize, "A%zu", id);
382 accounts.emplace_back(buf);
383 }
384
385 for (size_t id = 0; id < numCur; ++id)
386 {
387 if (id < 10)
388 snprintf(buf, bufSize, "CC%zu", id);
389 else if (id < 100)
390 snprintf(buf, bufSize, "C%zu", id);
391 else
392 snprintf(buf, bufSize, "%zu", id);
393 currencies.emplace_back(to_currency(buf));
395 }
396
397 for (auto const& a : accounts)
398 env.fund(XRP(100000), a);
399
400 // Every account trusts every other account with every currency
401 for (auto ai1 = accounts.begin(), aie = accounts.end(); ai1 != aie;
402 ++ai1)
403 {
404 for (auto ai2 = accounts.begin(); ai2 != aie; ++ai2)
405 {
406 if (ai1 == ai2)
407 continue;
408 for (auto const& cn : currencyNames)
409 {
410 env.trust((*ai1)[cn](1'000'000), *ai2);
411 if (ai1 > ai2)
412 {
413 // accounts with lower indexes hold balances from
414 // accounts
415 // with higher indexes
416 auto const& src = *ai1;
417 auto const& dst = *ai2;
418 env(pay(src, dst, src[cn](500000)));
419 }
420 }
421 env.close();
422 }
423 }
424
425 std::vector<IOU> ious;
426 ious.reserve(numAct * numCur);
427 for (auto const& a : accounts)
428 for (auto const& cn : currencyNames)
429 ious.emplace_back(a[cn]);
430
431 // create offers from every currency to every other currency
432 for (auto takerPays = ious.begin(), ie = ious.end(); takerPays != ie;
433 ++takerPays)
434 {
435 for (auto takerGets = ious.begin(); takerGets != ie; ++takerGets)
436 {
437 if (takerPays == takerGets)
438 continue;
439 auto const owner =
440 offererIndex ? accounts[*offererIndex] : takerGets->account;
441 if (owner.id() != takerGets->account.id())
442 env(pay(takerGets->account, owner, (*takerGets)(1000)));
443
444 env(offer(owner, (*takerPays)(1000), (*takerGets)(1000)),
446 }
447 env.close();
448 }
449
450 // create offers to/from xrp to every other ious
451 for (auto const& iou : ious)
452 {
453 auto const owner =
454 offererIndex ? accounts[*offererIndex] : iou.account;
455 env(offer(owner, iou(1000), XRP(1000)), txflags(tfPassive));
456 env(offer(owner, XRP(1000), iou(1000)), txflags(tfPassive));
457 env.close();
458 }
459 }
460
462 totalXRP(ReadView const& v, bool incRoot)
463 {
465 auto add = [&](auto const& a) {
466 // XRP balance
467 auto const sle = v.read(keylet::account(a));
468 if (!sle)
469 return;
470 auto const b = (*sle)[sfBalance];
471 totalXRP += b.mantissa();
472 };
473 for (auto const& a : accounts)
474 add(a);
475 if (incRoot)
476 add(xrpAccount());
477 return totalXRP;
478 }
479
480 // Check that the balances for all accounts for all currencies & XRP are the
481 // same
482 bool
483 checkBalances(ReadView const& v1, ReadView const& v2)
484 {
486
487 auto xrpBalance = [](ReadView const& v, ripple::Keylet const& k) {
488 auto const sle = v.read(k);
489 if (!sle)
490 return STAmount{};
491 return (*sle)[sfBalance];
492 };
493 auto lineBalance = [](ReadView const& v, ripple::Keylet const& k) {
494 auto const sle = v.read(k);
495 if (!sle)
496 return STAmount{};
497 return (*sle)[sfBalance];
498 };
500 for (auto ai1 = accounts.begin(), aie = accounts.end(); ai1 != aie;
501 ++ai1)
502 {
503 {
504 // XRP balance
505 auto const ak = keylet::account(*ai1);
506 auto const b1 = xrpBalance(v1, ak);
507 auto const b2 = xrpBalance(v2, ak);
508 totalXRP[0] += b1.mantissa();
509 totalXRP[1] += b2.mantissa();
510 if (b1 != b2)
511 diffs.emplace_back(b1, b2, xrpAccount(), *ai1);
512 }
513 for (auto ai2 = accounts.begin(); ai2 != aie; ++ai2)
514 {
515 if (ai1 >= ai2)
516 continue;
517 for (auto const& c : currencies)
518 {
519 // Line balance
520 auto const lk = keylet::line(*ai1, *ai2, c);
521 auto const b1 = lineBalance(v1, lk);
522 auto const b2 = lineBalance(v2, lk);
523 if (b1 != b2)
524 diffs.emplace_back(b1, b2, *ai1, *ai2);
525 }
526 }
527 }
528 return diffs.empty();
529 }
530
533 {
535 }
536
539 {
541 }
542
543 template <class F>
544 void
546 STAmount const& sendMax,
547 STAmount const& deliver,
548 std::vector<STPathElement> const& prefix,
549 std::vector<STPathElement> const& suffix,
550 std::optional<AccountID> const& existingAcc,
551 std::optional<Currency> const& existingCur,
552 std::optional<AccountID> const& existingIss,
553 F&& f)
554 {
555 auto accF = [&] { return this->getAvailAccount(); };
556 auto issF = [&] { return this->getAvailAccount(); };
557 auto currencyF = [&] { return this->getAvailCurrency(); };
558
559 STPathElement const* prevOuter =
560 prefix.empty() ? nullptr : &prefix.back();
561 ElementComboIter outer(prevOuter);
562
563 std::vector<STPathElement> outerResult;
565 auto const resultSize = prefix.size() + suffix.size() + 2;
566 outerResult.reserve(resultSize);
567 result.reserve(resultSize);
568 while (outer.next())
569 {
570 StateGuard og{*this};
571 outerResult = prefix;
572 outer.emplace_into(
573 outerResult,
574 accF,
575 issF,
576 currencyF,
577 existingAcc,
578 existingCur,
579 existingIss);
580 STPathElement const* prevInner = &outerResult.back();
581 ElementComboIter inner(prevInner);
582 while (inner.next())
583 {
584 StateGuard ig{*this};
585 result = outerResult;
586 inner.emplace_into(
587 result,
588 accF,
589 issF,
590 currencyF,
591 existingAcc,
592 existingCur,
593 existingIss);
594 result.insert(result.end(), suffix.begin(), suffix.end());
595 f(sendMax, deliver, result);
596 }
597 };
598 }
599};
600
602{
603 void
605 {
606 testcase("To Strand");
607
608 using namespace jtx;
609
610 auto const alice = Account("alice");
611 auto const bob = Account("bob");
612 auto const carol = Account("carol");
613 auto const gw = Account("gw");
614
615 auto const USD = gw["USD"];
616 auto const EUR = gw["EUR"];
617
618 auto const eurC = EUR.currency;
619 auto const usdC = USD.currency;
620
621 using D = DirectStepInfo;
622 using B = ripple::Book;
623 using XRPS = XRPEndpointStepInfo;
624
625 AMMContext ammContext(alice, false);
626
627 auto test = [&, this](
628 jtx::Env& env,
629 Issue const& deliver,
630 std::optional<Issue> const& sendMaxIssue,
631 STPath const& path,
632 TER expTer,
633 auto&&... expSteps) {
634 auto [ter, strand] = toStrand(
635 *env.current(),
636 alice,
637 bob,
638 deliver,
640 sendMaxIssue,
641 path,
642 true,
644 ammContext,
646 env.app().logs().journal("Flow"));
647 BEAST_EXPECT(ter == expTer);
648 if (sizeof...(expSteps) != 0)
649 BEAST_EXPECT(equal(
650 strand, std::forward<decltype(expSteps)>(expSteps)...));
651 };
652
653 {
654 Env env(*this, features);
655 env.fund(XRP(10000), alice, bob, gw);
656 env.trust(USD(1000), alice, bob);
657 env.trust(EUR(1000), alice, bob);
658 env(pay(gw, alice, EUR(100)));
659
660 {
661 STPath const path =
662 STPath({ipe(bob["USD"]), cpe(EUR.currency)});
663 auto [ter, _] = toStrand(
664 *env.current(),
665 alice,
666 alice,
667 /*deliver*/ xrpIssue(),
668 /*limitQuality*/ std::nullopt,
669 /*sendMaxIssue*/ EUR.issue(),
670 path,
671 true,
673 ammContext,
675 env.app().logs().journal("Flow"));
676 (void)_;
677 BEAST_EXPECT(ter == tesSUCCESS);
678 }
679 {
680 STPath const path = STPath({ipe(USD), cpe(xrpCurrency())});
681 auto [ter, _] = toStrand(
682 *env.current(),
683 alice,
684 alice,
685 /*deliver*/ xrpIssue(),
686 /*limitQuality*/ std::nullopt,
687 /*sendMaxIssue*/ EUR.issue(),
688 path,
689 true,
691 ammContext,
693 env.app().logs().journal("Flow"));
694 (void)_;
695 BEAST_EXPECT(ter == tesSUCCESS);
696 }
697 }
698
699 {
700 Env env(*this, features);
701 env.fund(XRP(10000), alice, bob, carol, gw);
702
703 test(env, USD, std::nullopt, STPath(), terNO_LINE);
704
705 env.trust(USD(1000), alice, bob, carol);
706 test(env, USD, std::nullopt, STPath(), tecPATH_DRY);
707
708 env(pay(gw, alice, USD(100)));
709 env(pay(gw, carol, USD(100)));
710
711 // Insert implied account
712 test(
713 env,
714 USD,
716 STPath(),
718 D{alice, gw, usdC},
719 D{gw, bob, usdC});
720 env.trust(EUR(1000), alice, bob);
721
722 // Insert implied offer
723 test(
724 env,
725 EUR,
726 USD.issue(),
727 STPath(),
729 D{alice, gw, usdC},
730 B{USD, EUR, std::nullopt},
731 D{gw, bob, eurC});
732
733 // Path with explicit offer
734 test(
735 env,
736 EUR,
737 USD.issue(),
738 STPath({ipe(EUR)}),
740 D{alice, gw, usdC},
741 B{USD, EUR, std::nullopt},
742 D{gw, bob, eurC});
743
744 // Path with offer that changes issuer only
745 env.trust(carol["USD"](1000), bob);
746 test(
747 env,
748 carol["USD"],
749 USD.issue(),
750 STPath({iape(carol)}),
752 D{alice, gw, usdC},
753 B{USD, carol["USD"], std::nullopt},
754 D{carol, bob, usdC});
755
756 // Path with XRP src currency
757 test(
758 env,
759 USD,
760 xrpIssue(),
761 STPath({ipe(USD)}),
763 XRPS{alice},
764 B{XRP, USD, std::nullopt},
765 D{gw, bob, usdC});
766
767 // Path with XRP dst currency.
768 test(
769 env,
770 xrpIssue(),
771 USD.issue(),
774 xrpAccount(),
775 xrpCurrency(),
776 xrpAccount()}}),
778 D{alice, gw, usdC},
779 B{USD, XRP, std::nullopt},
780 XRPS{bob});
781
782 // Path with XRP cross currency bridged payment
783 test(
784 env,
785 EUR,
786 USD.issue(),
787 STPath({cpe(xrpCurrency())}),
789 D{alice, gw, usdC},
790 B{USD, XRP, std::nullopt},
791 B{XRP, EUR, std::nullopt},
792 D{gw, bob, eurC});
793
794 // XRP -> XRP transaction can't include a path
795 test(env, XRP, std::nullopt, STPath({ape(carol)}), temBAD_PATH);
796
797 {
798 // The root account can't be the src or dst
799 auto flowJournal = env.app().logs().journal("Flow");
800 {
801 // The root account can't be the dst
802 auto r = toStrand(
803 *env.current(),
804 alice,
805 xrpAccount(),
806 XRP,
808 USD.issue(),
809 STPath(),
810 true,
812 ammContext,
814 flowJournal);
815 BEAST_EXPECT(r.first == temBAD_PATH);
816 }
817 {
818 // The root account can't be the src
819 auto r = toStrand(
820 *env.current(),
821 xrpAccount(),
822 alice,
823 XRP,
826 STPath(),
827 true,
829 ammContext,
831 flowJournal);
832 BEAST_EXPECT(r.first == temBAD_PATH);
833 }
834 {
835 // The root account can't be the src.
836 auto r = toStrand(
837 *env.current(),
838 noAccount(),
839 bob,
840 USD,
843 STPath(),
844 true,
846 ammContext,
848 flowJournal);
849 BEAST_EXPECT(r.first == temBAD_PATH);
850 }
851 }
852
853 // Create an offer with the same in/out issue
854 test(
855 env,
856 EUR,
857 USD.issue(),
858 STPath({ipe(USD), ipe(EUR)}),
860
861 // Path element with type zero
862 test(
863 env,
864 USD,
867 0, xrpAccount(), xrpCurrency(), xrpAccount())}),
869
870 // The same account can't appear more than once on a path
871 // `gw` will be used from alice->carol and implied between carol
872 // and bob
873 test(
874 env,
875 USD,
877 STPath({ape(gw), ape(carol)}),
879
880 // The same offer can't appear more than once on a path
881 test(
882 env,
883 EUR,
884 USD.issue(),
885 STPath({ipe(EUR), ipe(USD), ipe(EUR)}),
887 }
888
889 {
890 // cannot have more than one offer with the same output issue
891
892 using namespace jtx;
893 Env env(*this, features);
894
895 env.fund(XRP(10000), alice, bob, carol, gw);
896 env.trust(USD(10000), alice, bob, carol);
897 env.trust(EUR(10000), alice, bob, carol);
898
899 env(pay(gw, bob, USD(100)));
900 env(pay(gw, bob, EUR(100)));
901
902 env(offer(bob, XRP(100), USD(100)));
903 env(offer(bob, USD(100), EUR(100)), txflags(tfPassive));
904 env(offer(bob, EUR(100), USD(100)), txflags(tfPassive));
905
906 // payment path: XRP -> XRP/USD -> USD/EUR -> EUR/USD
907 env(pay(alice, carol, USD(100)),
908 path(~USD, ~EUR, ~USD),
909 sendmax(XRP(200)),
912 }
913
914 {
915 Env env(*this, features);
916 env.fund(XRP(10000), alice, bob, noripple(gw));
917 env.trust(USD(1000), alice, bob);
918 env(pay(gw, alice, USD(100)));
919 test(env, USD, std::nullopt, STPath(), terNO_RIPPLE);
920 }
921
922 {
923 // check global freeze
924 Env env(*this, features);
925 env.fund(XRP(10000), alice, bob, gw);
926 env.trust(USD(1000), alice, bob);
927 env(pay(gw, alice, USD(100)));
928
929 // Account can still issue payments
930 env(fset(alice, asfGlobalFreeze));
931 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
932 env(fclear(alice, asfGlobalFreeze));
933 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
934
935 // Account can not issue funds
936 env(fset(gw, asfGlobalFreeze));
937 test(env, USD, std::nullopt, STPath(), terNO_LINE);
938 env(fclear(gw, asfGlobalFreeze));
939 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
940
941 // Account can not receive funds
942 env(fset(bob, asfGlobalFreeze));
943 test(env, USD, std::nullopt, STPath(), terNO_LINE);
944 env(fclear(bob, asfGlobalFreeze));
945 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
946 }
947 {
948 // Freeze between gw and alice
949 Env env(*this, features);
950 env.fund(XRP(10000), alice, bob, gw);
951 env.trust(USD(1000), alice, bob);
952 env(pay(gw, alice, USD(100)));
953 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
954 env(trust(gw, alice["USD"](0), tfSetFreeze));
955 BEAST_EXPECT(getTrustFlag(env, gw, alice, usdC, TrustFlag::freeze));
956 test(env, USD, std::nullopt, STPath(), terNO_LINE);
957 }
958 {
959 // check no auth
960 // An account may require authorization to receive IOUs from an
961 // issuer
962 Env env(*this, features);
963 env.fund(XRP(10000), alice, bob, gw);
964 env(fset(gw, asfRequireAuth));
965 env.trust(USD(1000), alice, bob);
966 // Authorize alice but not bob
967 env(trust(gw, alice["USD"](1000), tfSetfAuth));
968 BEAST_EXPECT(getTrustFlag(env, gw, alice, usdC, TrustFlag::auth));
969 env(pay(gw, alice, USD(100)));
970 env.require(balance(alice, USD(100)));
971 test(env, USD, std::nullopt, STPath(), terNO_AUTH);
972
973 // Check pure issue redeem still works
974 auto [ter, strand] = toStrand(
975 *env.current(),
976 alice,
977 gw,
978 USD,
981 STPath(),
982 true,
984 ammContext,
986 env.app().logs().journal("Flow"));
987 BEAST_EXPECT(ter == tesSUCCESS);
988 BEAST_EXPECT(equal(strand, D{alice, gw, usdC}));
989 }
990
991 {
992 // last step xrp from offer
993 Env env(*this, features);
994 env.fund(XRP(10000), alice, bob, gw);
995 env.trust(USD(1000), alice, bob);
996 env(pay(gw, alice, USD(100)));
997
998 // alice -> USD/XRP -> bob
999 STPath path;
1000 path.emplace_back(std::nullopt, xrpCurrency(), std::nullopt);
1001
1002 auto [ter, strand] = toStrand(
1003 *env.current(),
1004 alice,
1005 bob,
1006 XRP,
1008 USD.issue(),
1009 path,
1010 false,
1012 ammContext,
1014 env.app().logs().journal("Flow"));
1015 BEAST_EXPECT(ter == tesSUCCESS);
1016 BEAST_EXPECT(equal(
1017 strand,
1018 D{alice, gw, usdC},
1019 B{USD.issue(), xrpIssue(), std::nullopt},
1020 XRPS{bob}));
1021 }
1022 }
1023
1024 void
1026 {
1027 using namespace jtx;
1028 testcase("RIPD1373");
1029
1030 auto const alice = Account("alice");
1031 auto const bob = Account("bob");
1032 auto const carol = Account("carol");
1033 auto const gw = Account("gw");
1034 auto const USD = gw["USD"];
1035 auto const EUR = gw["EUR"];
1036
1037 {
1038 Env env(*this, features);
1039 env.fund(XRP(10000), alice, bob, gw);
1040
1041 env.trust(USD(1000), alice, bob);
1042 env.trust(EUR(1000), alice, bob);
1043 env.trust(bob["USD"](1000), alice, gw);
1044 env.trust(bob["EUR"](1000), alice, gw);
1045
1046 env(offer(bob, XRP(100), bob["USD"](100)), txflags(tfPassive));
1047 env(offer(gw, XRP(100), USD(100)), txflags(tfPassive));
1048
1049 env(offer(bob, bob["USD"](100), bob["EUR"](100)),
1051 env(offer(gw, USD(100), EUR(100)), txflags(tfPassive));
1052
1053 Path const p = [&] {
1054 Path result;
1055 result.push_back(allpe(gw, bob["USD"]));
1056 result.push_back(cpe(EUR.currency));
1057 return result;
1058 }();
1059
1060 PathSet paths(p);
1061
1062 env(pay(alice, alice, EUR(1)),
1063 json(paths.json()),
1064 sendmax(XRP(10)),
1066 ter(temBAD_PATH));
1067 }
1068
1069 {
1070 Env env(*this, features);
1071
1072 env.fund(XRP(10000), alice, bob, carol, gw);
1073 env.trust(USD(10000), alice, bob, carol);
1074
1075 env(pay(gw, bob, USD(100)));
1076
1077 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
1078 env(offer(bob, USD(100), XRP(100)), txflags(tfPassive));
1079
1080 // payment path: XRP -> XRP/USD -> USD/XRP
1081 env(pay(alice, carol, XRP(100)),
1082 path(~USD, ~XRP),
1085 }
1086
1087 {
1088 Env env(*this, features);
1089
1090 env.fund(XRP(10000), alice, bob, carol, gw);
1091 env.trust(USD(10000), alice, bob, carol);
1092
1093 env(pay(gw, bob, USD(100)));
1094
1095 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
1096 env(offer(bob, USD(100), XRP(100)), txflags(tfPassive));
1097
1098 // payment path: XRP -> XRP/USD -> USD/XRP
1099 env(pay(alice, carol, XRP(100)),
1100 path(~USD, ~XRP),
1101 sendmax(XRP(200)),
1104 }
1105 }
1106
1107 void
1109 {
1110 testcase("test loop");
1111 using namespace jtx;
1112
1113 auto const alice = Account("alice");
1114 auto const bob = Account("bob");
1115 auto const carol = Account("carol");
1116 auto const gw = Account("gw");
1117 auto const USD = gw["USD"];
1118 auto const EUR = gw["EUR"];
1119 auto const CNY = gw["CNY"];
1120
1121 {
1122 Env env(*this, features);
1123
1124 env.fund(XRP(10000), alice, bob, carol, gw);
1125 env.trust(USD(10000), alice, bob, carol);
1126
1127 env(pay(gw, bob, USD(100)));
1128 env(pay(gw, alice, USD(100)));
1129
1130 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
1131 env(offer(bob, USD(100), XRP(100)), txflags(tfPassive));
1132
1133 // payment path: USD -> USD/XRP -> XRP/USD
1134 env(pay(alice, carol, USD(100)),
1135 sendmax(USD(100)),
1136 path(~XRP, ~USD),
1139 }
1140 {
1141 Env env(*this, features);
1142
1143 env.fund(XRP(10000), alice, bob, carol, gw);
1144 env.trust(USD(10000), alice, bob, carol);
1145 env.trust(EUR(10000), alice, bob, carol);
1146 env.trust(CNY(10000), alice, bob, carol);
1147
1148 env(pay(gw, bob, USD(100)));
1149 env(pay(gw, bob, EUR(100)));
1150 env(pay(gw, bob, CNY(100)));
1151
1152 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
1153 env(offer(bob, USD(100), EUR(100)), txflags(tfPassive));
1154 env(offer(bob, EUR(100), CNY(100)), txflags(tfPassive));
1155
1156 // payment path: XRP->XRP/USD->USD/EUR->USD/CNY
1157 env(pay(alice, carol, CNY(100)),
1158 sendmax(XRP(100)),
1159 path(~USD, ~EUR, ~USD, ~CNY),
1162 }
1163 }
1164
1165 void
1167 {
1168 testcase("test no account");
1169 using namespace jtx;
1170
1171 auto const alice = Account("alice");
1172 auto const bob = Account("bob");
1173 auto const gw = Account("gw");
1174 auto const USD = gw["USD"];
1175
1176 Env env(*this, features);
1177 env.fund(XRP(10000), alice, bob, gw);
1178
1179 STAmount sendMax{USD.issue(), 100, 1};
1180 STAmount noAccountAmount{Issue{USD.currency, noAccount()}, 100, 1};
1181 STAmount deliver;
1182 AccountID const srcAcc = alice.id();
1183 AccountID dstAcc = bob.id();
1184 STPathSet pathSet;
1186 inputs.defaultPathsAllowed = true;
1187 try
1188 {
1189 PaymentSandbox sb{env.current().get(), tapNONE};
1190 {
1192 sb,
1193 sendMax,
1194 deliver,
1195 dstAcc,
1196 noAccount(),
1197 pathSet,
1199 env.app().logs(),
1200 &inputs);
1201 BEAST_EXPECT(r.result() == temBAD_PATH);
1202 }
1203 {
1205 sb,
1206 sendMax,
1207 deliver,
1208 noAccount(),
1209 srcAcc,
1210 pathSet,
1212 env.app().logs(),
1213 &inputs);
1214 BEAST_EXPECT(r.result() == temBAD_PATH);
1215 }
1216 {
1218 sb,
1219 noAccountAmount,
1220 deliver,
1221 dstAcc,
1222 srcAcc,
1223 pathSet,
1225 env.app().logs(),
1226 &inputs);
1227 BEAST_EXPECT(r.result() == temBAD_PATH);
1228 }
1229 {
1231 sb,
1232 sendMax,
1233 noAccountAmount,
1234 dstAcc,
1235 srcAcc,
1236 pathSet,
1238 env.app().logs(),
1239 &inputs);
1240 BEAST_EXPECT(r.result() == temBAD_PATH);
1241 }
1242 }
1243 catch (...)
1244 {
1245 this->fail();
1246 }
1247 }
1248
1249 void
1250 run() override
1251 {
1252 using namespace jtx;
1253 auto const sa = testable_amendments();
1254 testToStrand(sa - featurePermissionedDEX);
1255 testToStrand(sa);
1256
1257 testRIPD1373(sa - featurePermissionedDEX);
1258 testRIPD1373(sa);
1259
1260 testLoop(sa - featurePermissionedDEX);
1261 testLoop(sa);
1262
1263 testNoAccount(sa);
1264 }
1265};
1266
1267BEAST_DEFINE_TESTSUITE(PayStrand, app, ripple);
1268
1269} // namespace test
1270} // namespace ripple
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:152
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:530
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
AccountID account
Definition Issue.h:17
Currency currency
Definition Issue.h:16
beast::Journal journal(std::string const &name)
Definition Log.cpp:141
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:477
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)
bool hasAny(std::initializer_list< SB > sb) const
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)
ElementComboIter(STPathElement const *prev=nullptr)
size_t count(std::initializer_list< SB > sb) const
Path & push_back(Issue const &iss)
Definition PathSet.h:115
Immutable cryptographic account descriptor.
Definition Account.h:20
AccountID id() const
Returns the Account ID.
Definition Account.h:92
A transaction testing environment.
Definition Env.h:102
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:528
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:312
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:103
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:302
Application & app()
Definition Env.h:242
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:271
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition Env.cpp:259
A balance matches.
Definition balance.h:20
Inject raw JSON.
Definition jtx_json.h:14
Add a path.
Definition paths.h:39
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:225
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
Json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition flags.h:102
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:13
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 pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
FeatureBitset testable_amendments()
Definition Env.h:55
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:10
STPathElement cpe(Currency const &c)
STPathElement allpe(AccountID const &a, Issue const &iss)
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
bool xrpEndpointStepEqual(Step const &step, AccountID const &acc)
bool getTrustFlag(jtx::Env const &env, jtx::Account const &src, jtx::Account const &dst, Currency const &cur, TrustFlag flag)
STPathElement iape(AccountID const &account)
STPathElement ape(AccountID const &a)
std::uint32_t trustFlag(TrustFlag f, bool useHigh)
bool bookStepEqual(Step const &step, ripple::Book const &book)
bool strandEqualHelper(Iter i)
bool equal(std::unique_ptr< Step > const &s1, DirectStepInfo const &dsi)
STPathElement ipe(Issue const &iss)
bool directStepEqual(Step const &step, AccountID const &src, AccountID const &dst, Currency const &currency)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:6
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:96
AccountID const & noAccount()
A placeholder for empty accounts.
constexpr std::uint32_t asfGlobalFreeze
Definition TxFlags.h:64
AccountID const & xrpAccount()
Compute AccountID from public key.
@ lsfHighNoRipple
constexpr std::uint32_t tfPassive
Definition TxFlags.h:79
@ no
Definition Steps.h:26
constexpr std::uint32_t tfPartialPayment
Definition TxFlags.h:89
constexpr std::uint32_t tfSetfAuth
Definition TxFlags.h:96
Currency const & xrpCurrency()
XRP currency.
@ tecPATH_DRY
Definition TER.h:276
constexpr std::uint32_t tfNoRippleDirect
Definition TxFlags.h:88
@ tesSUCCESS
Definition TER.h:226
@ tapNONE
Definition ApplyView.h:12
constexpr std::uint32_t asfRequireAuth
Definition TxFlags.h:59
@ terNO_RIPPLE
Definition TER.h:205
@ terNO_AUTH
Definition TER.h:199
@ terNO_LINE
Definition TER.h:200
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:99
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:117
bool to_currency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Definition UintTypes.cpp:65
@ temBAD_PATH_LOOP
Definition TER.h:78
@ temBAD_PATH
Definition TER.h:77
@ temBAD_SEND_XRP_PATHS
Definition TER.h:84
@ temBAD_SEND_XRP_MAX
Definition TER.h:81
T reserve(T... args)
T size(T... args)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:20
std::int64_t totalXRP(ReadView const &v, bool incRoot)
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)
ripple::Currency getCurrency(size_t id)
bool checkBalances(ReadView const &v1, ReadView const &v2)
std::vector< jtx::Account > accounts
std::vector< std::string > currencyNames
std::vector< ripple::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 testToStrand(FeatureBitset features)
void testNoAccount(FeatureBitset features)
void run() override
Runs the suite.
void testLoop(FeatureBitset features)
void testRIPD1373(FeatureBitset features)
T tie(T... args)