rippled
Loading...
Searching...
No Matches
PayStrand_test.cpp
1#include <test/jtx.h>
2#include <test/jtx/PathSet.h>
3
4#include <xrpld/core/Config.h>
5
6#include <xrpl/basics/contract.h>
7#include <xrpl/basics/safe_cast.h>
8#include <xrpl/ledger/PaymentSandbox.h>
9#include <xrpl/protocol/Feature.h>
10#include <xrpl/protocol/jss.h>
11#include <xrpl/tx/paths/RippleCalc.h>
12#include <xrpl/tx/paths/detail/Steps.h>
13#include <xrpl/tx/transactors/dex/AMMContext.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
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
81equal(std::unique_ptr<Step> const& s1, XRPEndpointStepInfo const& xrpStepInfo)
82{
83 if (!s1)
84 return false;
85 return test::xrpEndpointStepEqual(*s1, xrpStepInfo.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
129
130// Issue path element
132ipe(Issue const& iss)
133{
134 return STPathElement(
136 xrpAccount(),
137 iss.currency,
138 iss.account);
139};
140
141// Issuer path element
143iape(AccountID const& account)
144{
146};
147
149{
150 enum class SB /*state bit*/
151 : std::uint16_t {
152 acc,
153 iss,
154 cur,
155 rootAcc,
156 rootIss,
157 xrp,
162 prevAcc,
163 prevCur,
164 prevIss,
165 boundary,
166 last
167 };
168
170 static_assert(safe_cast<size_t>(SB::last) <= sizeof(decltype(state_)) * 8, "");
171 STPathElement const* prev_ = nullptr;
172 // disallow iss and cur to be specified with acc is specified (simplifies
173 // some tests)
174 bool const allowCompound_ = false;
175
176 bool
177 has(SB s) const
178 {
179 return (state_ & (1 << safe_cast<int>(s))) != 0;
180 }
181
182 bool
184 {
185 for (auto const s : sb)
186 {
187 if (has(s))
188 return true;
189 }
190 return false;
191 }
192
193 size_t
195 {
196 size_t result = 0;
197
198 for (auto const s : sb)
199 {
200 if (has(s))
201 result++;
202 }
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_ || !(has(SB::acc) && hasAny({SB::cur, SB::iss}))) &&
215 (!hasAny({SB::prevAcc, SB::prevCur, SB::prevIss}) || (prev_ != nullptr)) &&
217 has(SB::acc)) &&
219 has(SB::iss)) &&
221 // These will be duplicates
225 }
226 bool
228 {
229 if (!(has(SB::last)))
230 {
231 do
232 {
233 ++state_;
234 } while (!valid());
235 }
236 return !has(SB::last);
237 }
238
239 template <class Col, class AccFactory, class IssFactory, class CurrencyFactory>
240 void
242 Col& col,
243 AccFactory&& accF,
244 IssFactory&& issF,
245 CurrencyFactory&& currencyF,
249 {
250 assert(!has(SB::last));
251
252 auto const acc = [&]() -> std::optional<AccountID> {
253 if (!has(SB::acc))
254 return std::nullopt;
255 if (has(SB::rootAcc))
256 return xrpAccount();
258 return existingAcc;
259 return accF().id();
260 }();
261 auto const iss = [&]() -> std::optional<AccountID> {
262 if (!has(SB::iss))
263 return std::nullopt;
264 if (has(SB::rootIss))
265 return xrpAccount();
266 if (has(SB::sameAccIss))
267 return acc;
269 return existingIss;
270 return issF().id();
271 }();
272 auto const cur = [&]() -> std::optional<Currency> {
273 if (!has(SB::cur))
274 return std::nullopt;
275 if (has(SB::xrp))
276 return xrpCurrency();
278 return existingCur;
279 return currencyF();
280 }();
281 if (!has(SB::boundary))
282 {
283 col.emplace_back(acc, cur, iss);
284 }
285 else
286 {
287 col.emplace_back(
289 acc.value_or(AccountID{}),
290 cur.value_or(Currency{}),
291 iss.value_or(AccountID{}));
292 }
293 }
294};
295
297{
301
303 getAccount(size_t id)
304 {
305 assert(id < accounts.size());
306 return accounts[id];
307 }
308
310 getCurrency(size_t id)
311 {
312 assert(id < currencies.size());
313 return currencies[id];
314 }
315
316 // ids from 0 through (nextAvail -1) have already been used in the
317 // path
320
327
328 void
333
335 {
338
340 {
341 }
343 {
345 }
346 };
347
348 // Create the given number of accounts, and add trust lines so every
349 // account trusts every other with every currency
350 // Create an offer from every currency/account to every other
351 // currency/account; the offer owner is either the specified
352 // account or the issuer of the "taker gets" account
353 void
354 setupEnv(jtx::Env& env, size_t numAct, size_t numCur, std::optional<size_t> const& offererIndex)
355 {
356 using namespace jtx;
357
358 assert(!offererIndex || offererIndex < numAct);
359
360 accounts.clear();
361 accounts.reserve(numAct);
362 currencies.clear();
363 currencies.reserve(numCur);
365 currencyNames.reserve(numCur);
366
367 constexpr size_t bufSize = 32;
368 char buf[bufSize];
369
370 for (size_t id = 0; id < numAct; ++id)
371 {
372 snprintf(buf, bufSize, "A%zu", id);
373 accounts.emplace_back(buf);
374 }
375
376 for (size_t id = 0; id < numCur; ++id)
377 {
378 if (id < 10)
379 {
380 snprintf(buf, bufSize, "CC%zu", id);
381 }
382 else if (id < 100)
383 {
384 snprintf(buf, bufSize, "C%zu", id);
385 }
386 else
387 {
388 snprintf(buf, bufSize, "%zu", id);
389 }
390 currencies.emplace_back(to_currency(buf));
392 }
393
394 for (auto const& a : accounts)
395 env.fund(XRP(100000), a);
396
397 // Every account trusts every other account with every currency
398 for (auto ai1 = accounts.begin(), aie = accounts.end(); ai1 != aie; ++ai1)
399 {
400 for (auto ai2 = accounts.begin(); ai2 != aie; ++ai2)
401 {
402 if (ai1 == ai2)
403 continue;
404 for (auto const& cn : currencyNames)
405 {
406 env.trust((*ai1)[cn](1'000'000), *ai2);
407 if (ai1 > ai2)
408 {
409 // accounts with lower indexes hold balances from
410 // accounts
411 // with higher indexes
412 auto const& src = *ai1;
413 auto const& dst = *ai2;
414 env(pay(src, dst, src[cn](500000)));
415 }
416 }
417 env.close();
418 }
419 }
420
421 std::vector<IOU> ious;
422 ious.reserve(numAct * numCur);
423 for (auto const& a : accounts)
424 {
425 for (auto const& cn : currencyNames)
426 ious.emplace_back(a[cn]);
427 }
428
429 // create offers from every currency to every other currency
430 for (auto takerPays = ious.begin(), ie = ious.end(); takerPays != ie; ++takerPays)
431 {
432 for (auto takerGets = ious.begin(); takerGets != ie; ++takerGets)
433 {
434 if (takerPays == takerGets)
435 continue;
436 auto const owner = offererIndex ? accounts[*offererIndex] : takerGets->account;
437 if (owner.id() != takerGets->account.id())
438 env(pay(takerGets->account, owner, (*takerGets)(1000)));
439
440 env(offer(owner, (*takerPays)(1000), (*takerGets)(1000)), txflags(tfPassive));
441 }
442 env.close();
443 }
444
445 // create offers to/from xrp to every other ious
446 for (auto const& iou : ious)
447 {
448 auto const owner = offererIndex ? accounts[*offererIndex] : iou.account;
449 env(offer(owner, iou(1000), XRP(1000)), txflags(tfPassive));
450 env(offer(owner, XRP(1000), iou(1000)), txflags(tfPassive));
451 env.close();
452 }
453 }
454
456 totalXRP(ReadView const& v, bool incRoot)
457 {
459 auto add = [&](auto const& a) {
460 // XRP balance
461 auto const sle = v.read(keylet::account(a));
462 if (!sle)
463 return;
464 auto const b = (*sle)[sfBalance];
465 totalXRP += b.mantissa();
466 };
467 for (auto const& a : accounts)
468 add(a);
469 if (incRoot)
470 add(xrpAccount());
471 return totalXRP;
472 }
473
474 // Check that the balances for all accounts for all currencies & XRP are the
475 // same
476 bool
477 checkBalances(ReadView const& v1, ReadView const& v2)
478 {
480
481 auto xrpBalance = [](ReadView const& v, xrpl::Keylet const& k) {
482 auto const sle = v.read(k);
483 if (!sle)
484 return STAmount{};
485 return (*sle)[sfBalance];
486 };
487 auto lineBalance = [](ReadView const& v, xrpl::Keylet const& k) {
488 auto const sle = v.read(k);
489 if (!sle)
490 return STAmount{};
491 return (*sle)[sfBalance];
492 };
494 for (auto ai1 = accounts.begin(), aie = accounts.end(); ai1 != aie; ++ai1)
495 {
496 {
497 // XRP balance
498 auto const ak = keylet::account(*ai1);
499 auto const b1 = xrpBalance(v1, ak);
500 auto const b2 = xrpBalance(v2, ak);
501 totalXRP[0] += b1.mantissa();
502 totalXRP[1] += b2.mantissa();
503 if (b1 != b2)
504 diffs.emplace_back(b1, b2, xrpAccount(), *ai1);
505 }
506 for (auto ai2 = accounts.begin(); ai2 != aie; ++ai2)
507 {
508 if (ai1 >= ai2)
509 continue;
510 for (auto const& c : currencies)
511 {
512 // Line balance
513 auto const lk = keylet::line(*ai1, *ai2, c);
514 auto const b1 = lineBalance(v1, lk);
515 auto const b2 = lineBalance(v2, lk);
516 if (b1 != b2)
517 diffs.emplace_back(b1, b2, *ai1, *ai2);
518 }
519 }
520 }
521 return diffs.empty();
522 }
523
526 {
528 }
529
532 {
534 }
535
536 template <class F>
537 void
539 STAmount const& sendMax,
540 STAmount const& deliver,
541 std::vector<STPathElement> const& prefix,
542 std::vector<STPathElement> const& suffix,
543 std::optional<AccountID> const& existingAcc,
544 std::optional<Currency> const& existingCur,
545 std::optional<AccountID> const& existingIss,
546 F&& f)
547 {
548 auto accF = [&] { return this->getAvailAccount(); };
549 auto issF = [&] { return this->getAvailAccount(); };
550 auto currencyF = [&] { return this->getAvailCurrency(); };
551
552 STPathElement const* prevOuter = prefix.empty() ? nullptr : &prefix.back();
553 ElementComboIter outer(prevOuter);
554
555 std::vector<STPathElement> outerResult;
557 auto const resultSize = prefix.size() + suffix.size() + 2;
558 outerResult.reserve(resultSize);
559 result.reserve(resultSize);
560 while (outer.next())
561 {
562 StateGuard const og{*this};
563 outerResult = prefix;
564 outer.emplace_into(
565 outerResult, accF, issF, currencyF, existingAcc, existingCur, existingIss);
566 STPathElement const* prevInner = &outerResult.back();
567 ElementComboIter inner(prevInner);
568 while (inner.next())
569 {
570 StateGuard const ig{*this};
571 result = outerResult;
572 inner.emplace_into(
573 result, accF, issF, currencyF, existingAcc, existingCur, existingIss);
574 result.insert(result.end(), suffix.begin(), suffix.end());
575 f(sendMax, deliver, result);
576 }
577 };
578 }
579};
580
582{
583 void
585 {
586 testcase("To Strand");
587
588 using namespace jtx;
589
590 auto const alice = Account("alice");
591 auto const bob = Account("bob");
592 auto const carol = Account("carol");
593 auto const gw = Account("gw");
594
595 auto const USD = gw["USD"];
596 auto const EUR = gw["EUR"];
597
598 auto const eurC = EUR.currency;
599 auto const usdC = USD.currency;
600
601 using D = DirectStepInfo;
602 using B = xrpl::Book;
603 using XRPS = XRPEndpointStepInfo;
604
605 AMMContext ammContext(alice, false);
606
607 auto test = [&, this](
608 jtx::Env& env,
609 Issue const& deliver,
610 std::optional<Issue> const& sendMaxIssue,
611 STPath const& path,
612 TER expTer,
613 auto&&... expSteps) {
614 auto [ter, strand] = toStrand(
615 *env.current(),
616 alice,
617 bob,
618 deliver,
620 sendMaxIssue,
621 path,
622 true,
624 ammContext,
626 env.app().getJournal("Flow"));
627 BEAST_EXPECT(ter == expTer);
628 if (sizeof...(expSteps) != 0)
629 BEAST_EXPECT(equal(strand, std::forward<decltype(expSteps)>(expSteps)...));
630 };
631
632 {
633 Env env(*this, features);
634 env.fund(XRP(10000), alice, bob, gw);
635 env.trust(USD(1000), alice, bob);
636 env.trust(EUR(1000), alice, bob);
637 env(pay(gw, alice, EUR(100)));
638
639 {
640 STPath const path = STPath({ipe(bob["USD"]), cpe(EUR.currency)});
641 auto [ter, _] = toStrand(
642 *env.current(),
643 alice,
644 alice,
645 /*deliver*/ xrpIssue(),
646 /*limitQuality*/ std::nullopt,
647 /*sendMaxIssue*/ EUR.issue(),
648 path,
649 true,
651 ammContext,
653 env.app().getJournal("Flow"));
654 (void)_;
655 BEAST_EXPECT(isTesSuccess(ter));
656 }
657 {
658 STPath const path = STPath({ipe(USD), cpe(xrpCurrency())});
659 auto [ter, _] = toStrand(
660 *env.current(),
661 alice,
662 alice,
663 /*deliver*/ xrpIssue(),
664 /*limitQuality*/ std::nullopt,
665 /*sendMaxIssue*/ EUR.issue(),
666 path,
667 true,
669 ammContext,
671 env.app().getJournal("Flow"));
672 (void)_;
673 BEAST_EXPECT(isTesSuccess(ter));
674 }
675 }
676
677 {
678 Env env(*this, features);
679 env.fund(XRP(10000), alice, bob, carol, gw);
680
681 test(env, USD, std::nullopt, STPath(), terNO_LINE);
682
683 env.trust(USD(1000), alice, bob, carol);
684 test(env, USD, std::nullopt, STPath(), tecPATH_DRY);
685
686 env(pay(gw, alice, USD(100)));
687 env(pay(gw, carol, USD(100)));
688
689 // Insert implied account
690 test(
691 env, USD, std::nullopt, STPath(), tesSUCCESS, D{alice, gw, usdC}, D{gw, bob, usdC});
692 env.trust(EUR(1000), alice, bob);
693
694 // Insert implied offer
695 test(
696 env,
697 EUR,
698 USD.issue(),
699 STPath(),
701 D{alice, gw, usdC},
702 B{USD, EUR, std::nullopt},
703 D{gw, bob, eurC});
704
705 // Path with explicit offer
706 test(
707 env,
708 EUR,
709 USD.issue(),
710 STPath({ipe(EUR)}),
712 D{alice, gw, usdC},
713 B{USD, EUR, std::nullopt},
714 D{gw, bob, eurC});
715
716 // Path with offer that changes issuer only
717 env.trust(carol["USD"](1000), bob);
718 test(
719 env,
720 carol["USD"],
721 USD.issue(),
722 STPath({iape(carol)}),
724 D{alice, gw, usdC},
725 B{USD, carol["USD"], std::nullopt},
726 D{carol, bob, usdC});
727
728 // Path with XRP src currency
729 test(
730 env,
731 USD,
732 xrpIssue(),
733 STPath({ipe(USD)}),
735 XRPS{alice},
736 B{XRP, USD, std::nullopt},
737 D{gw, bob, usdC});
738
739 // Path with XRP dst currency.
740 test(
741 env,
742 xrpIssue(),
743 USD.issue(),
747 D{alice, gw, usdC},
748 B{USD, XRP, std::nullopt},
749 XRPS{bob});
750
751 // Path with XRP cross currency bridged payment
752 test(
753 env,
754 EUR,
755 USD.issue(),
756 STPath({cpe(xrpCurrency())}),
758 D{alice, gw, usdC},
759 B{USD, XRP, std::nullopt},
760 B{XRP, EUR, std::nullopt},
761 D{gw, bob, eurC});
762
763 // XRP -> XRP transaction can't include a path
764 test(env, XRP, std::nullopt, STPath({ape(carol)}), temBAD_PATH);
765
766 {
767 // The root account can't be the src or dst
768 auto flowJournal = env.app().getJournal("Flow");
769 {
770 // The root account can't be the dst
771 auto r = toStrand(
772 *env.current(),
773 alice,
774 xrpAccount(),
775 XRP,
777 USD.issue(),
778 STPath(),
779 true,
781 ammContext,
783 flowJournal);
784 BEAST_EXPECT(r.first == temBAD_PATH);
785 }
786 {
787 // The root account can't be the src
788 auto r = toStrand(
789 *env.current(),
790 xrpAccount(),
791 alice,
792 XRP,
795 STPath(),
796 true,
798 ammContext,
800 flowJournal);
801 BEAST_EXPECT(r.first == temBAD_PATH);
802 }
803 {
804 // The root account can't be the src.
805 auto r = toStrand(
806 *env.current(),
807 noAccount(),
808 bob,
809 USD,
812 STPath(),
813 true,
815 ammContext,
817 flowJournal);
818 BEAST_EXPECT(r.first == temBAD_PATH);
819 }
820 }
821
822 // Create an offer with the same in/out issue
823 test(env, EUR, USD.issue(), STPath({ipe(USD), ipe(EUR)}), temBAD_PATH);
824
825 // Path element with type zero
826 test(
827 env,
828 USD,
832
833 // The same account can't appear more than once on a path
834 // `gw` will be used from alice->carol and implied between carol
835 // and bob
836 test(env, USD, std::nullopt, STPath({ape(gw), ape(carol)}), temBAD_PATH_LOOP);
837
838 // The same offer can't appear more than once on a path
839 test(env, EUR, USD.issue(), STPath({ipe(EUR), ipe(USD), ipe(EUR)}), temBAD_PATH_LOOP);
840 }
841
842 {
843 // cannot have more than one offer with the same output issue
844
845 using namespace jtx;
846 Env env(*this, features);
847
848 env.fund(XRP(10000), alice, bob, carol, gw);
849 env.trust(USD(10000), alice, bob, carol);
850 env.trust(EUR(10000), alice, bob, carol);
851
852 env(pay(gw, bob, USD(100)));
853 env(pay(gw, bob, EUR(100)));
854
855 env(offer(bob, XRP(100), USD(100)));
856 env(offer(bob, USD(100), EUR(100)), txflags(tfPassive));
857 env(offer(bob, EUR(100), USD(100)), txflags(tfPassive));
858
859 // payment path: XRP -> XRP/USD -> USD/EUR -> EUR/USD
860 env(pay(alice, carol, USD(100)),
861 path(~USD, ~EUR, ~USD),
862 sendmax(XRP(200)),
863 txflags(tfNoRippleDirect),
865 }
866
867 {
868 Env env(*this, features);
869 env.fund(XRP(10000), alice, bob, noripple(gw));
870 env.trust(USD(1000), alice, bob);
871 env(pay(gw, alice, USD(100)));
872 test(env, USD, std::nullopt, STPath(), terNO_RIPPLE);
873 }
874
875 {
876 // check global freeze
877 Env env(*this, features);
878 env.fund(XRP(10000), alice, bob, gw);
879 env.trust(USD(1000), alice, bob);
880 env(pay(gw, alice, USD(100)));
881
882 // Account can still issue payments
883 env(fset(alice, asfGlobalFreeze));
884 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
885 env(fclear(alice, asfGlobalFreeze));
886 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
887
888 // Account can not issue funds
889 env(fset(gw, asfGlobalFreeze));
890 test(env, USD, std::nullopt, STPath(), terNO_LINE);
891 env(fclear(gw, asfGlobalFreeze));
892 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
893
894 // Account can not receive funds
895 env(fset(bob, asfGlobalFreeze));
896 test(env, USD, std::nullopt, STPath(), terNO_LINE);
897 env(fclear(bob, asfGlobalFreeze));
898 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
899 }
900 {
901 // Freeze between gw and alice
902 Env env(*this, features);
903 env.fund(XRP(10000), alice, bob, gw);
904 env.trust(USD(1000), alice, bob);
905 env(pay(gw, alice, USD(100)));
906 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
907 env(trust(gw, alice["USD"](0), tfSetFreeze));
908 BEAST_EXPECT(getTrustFlag(env, gw, alice, usdC, TrustFlag::freeze));
909 test(env, USD, std::nullopt, STPath(), terNO_LINE);
910 }
911 {
912 // check no auth
913 // An account may require authorization to receive IOUs from an
914 // issuer
915 Env env(*this, features);
916 env.fund(XRP(10000), alice, bob, gw);
917 env(fset(gw, asfRequireAuth));
918 env.trust(USD(1000), alice, bob);
919 // Authorize alice but not bob
920 env(trust(gw, alice["USD"](1000), tfSetfAuth));
921 BEAST_EXPECT(getTrustFlag(env, gw, alice, usdC, TrustFlag::auth));
922 env(pay(gw, alice, USD(100)));
923 env.require(balance(alice, USD(100)));
924 test(env, USD, std::nullopt, STPath(), terNO_AUTH);
925
926 // Check pure issue redeem still works
927 auto [ter, strand] = toStrand(
928 *env.current(),
929 alice,
930 gw,
931 USD,
934 STPath(),
935 true,
937 ammContext,
939 env.app().getJournal("Flow"));
940 BEAST_EXPECT(isTesSuccess(ter));
941 BEAST_EXPECT(equal(strand, D{alice, gw, usdC}));
942 }
943
944 {
945 // last step xrp from offer
946 Env env(*this, features);
947 env.fund(XRP(10000), alice, bob, gw);
948 env.trust(USD(1000), alice, bob);
949 env(pay(gw, alice, USD(100)));
950
951 // alice -> USD/XRP -> bob
952 STPath path;
953 path.emplace_back(std::nullopt, xrpCurrency(), std::nullopt);
954
955 auto [ter, strand] = toStrand(
956 *env.current(),
957 alice,
958 bob,
959 XRP,
961 USD.issue(),
962 path,
963 false,
965 ammContext,
967 env.app().getJournal("Flow"));
968 BEAST_EXPECT(isTesSuccess(ter));
969 BEAST_EXPECT(equal(
970 strand, D{alice, gw, usdC}, B{USD.issue(), xrpIssue(), std::nullopt}, XRPS{bob}));
971 }
972 }
973
974 void
976 {
977 using namespace jtx;
978 testcase("RIPD1373");
979
980 auto const alice = Account("alice");
981 auto const bob = Account("bob");
982 auto const carol = Account("carol");
983 auto const gw = Account("gw");
984 auto const USD = gw["USD"];
985 auto const EUR = gw["EUR"];
986
987 {
988 Env env(*this, features);
989 env.fund(XRP(10000), alice, bob, gw);
990
991 env.trust(USD(1000), alice, bob);
992 env.trust(EUR(1000), alice, bob);
993 env.trust(bob["USD"](1000), alice, gw);
994 env.trust(bob["EUR"](1000), alice, gw);
995
996 env(offer(bob, XRP(100), bob["USD"](100)), txflags(tfPassive));
997 env(offer(gw, XRP(100), USD(100)), txflags(tfPassive));
998
999 env(offer(bob, bob["USD"](100), bob["EUR"](100)), txflags(tfPassive));
1000 env(offer(gw, USD(100), EUR(100)), txflags(tfPassive));
1001
1002 Path const p = [&] {
1003 Path result;
1004 result.push_back(allPathElements(gw, bob["USD"]));
1005 result.push_back(cpe(EUR.currency));
1006 return result;
1007 }();
1008
1009 PathSet const paths(p);
1010
1011 env(pay(alice, alice, EUR(1)),
1012 json(paths.json()),
1013 sendmax(XRP(10)),
1014 txflags(tfNoRippleDirect | tfPartialPayment),
1015 ter(temBAD_PATH));
1016 }
1017
1018 {
1019 Env env(*this, features);
1020
1021 env.fund(XRP(10000), alice, bob, carol, gw);
1022 env.trust(USD(10000), alice, bob, carol);
1023
1024 env(pay(gw, bob, USD(100)));
1025
1026 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
1027 env(offer(bob, USD(100), XRP(100)), txflags(tfPassive));
1028
1029 // payment path: XRP -> XRP/USD -> USD/XRP
1030 env(pay(alice, carol, XRP(100)),
1031 path(~USD, ~XRP),
1032 txflags(tfNoRippleDirect),
1034 }
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
1044 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
1045 env(offer(bob, USD(100), XRP(100)), txflags(tfPassive));
1046
1047 // payment path: XRP -> XRP/USD -> USD/XRP
1048 env(pay(alice, carol, XRP(100)),
1049 path(~USD, ~XRP),
1050 sendmax(XRP(200)),
1051 txflags(tfNoRippleDirect),
1053 }
1054 }
1055
1056 void
1058 {
1059 testcase("test loop");
1060 using namespace jtx;
1061
1062 auto const alice = Account("alice");
1063 auto const bob = Account("bob");
1064 auto const carol = Account("carol");
1065 auto const gw = Account("gw");
1066 auto const USD = gw["USD"];
1067 auto const EUR = gw["EUR"];
1068 auto const CNY = gw["CNY"];
1069
1070 {
1071 Env env(*this, features);
1072
1073 env.fund(XRP(10000), alice, bob, carol, gw);
1074 env.trust(USD(10000), alice, bob, carol);
1075
1076 env(pay(gw, bob, USD(100)));
1077 env(pay(gw, alice, USD(100)));
1078
1079 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
1080 env(offer(bob, USD(100), XRP(100)), txflags(tfPassive));
1081
1082 // payment path: USD -> USD/XRP -> XRP/USD
1083 env(pay(alice, carol, USD(100)),
1084 sendmax(USD(100)),
1085 path(~XRP, ~USD),
1086 txflags(tfNoRippleDirect),
1088 }
1089 {
1090 Env env(*this, features);
1091
1092 env.fund(XRP(10000), alice, bob, carol, gw);
1093 env.trust(USD(10000), alice, bob, carol);
1094 env.trust(EUR(10000), alice, bob, carol);
1095 env.trust(CNY(10000), alice, bob, carol);
1096
1097 env(pay(gw, bob, USD(100)));
1098 env(pay(gw, bob, EUR(100)));
1099 env(pay(gw, bob, CNY(100)));
1100
1101 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
1102 env(offer(bob, USD(100), EUR(100)), txflags(tfPassive));
1103 env(offer(bob, EUR(100), CNY(100)), txflags(tfPassive));
1104
1105 // payment path: XRP->XRP/USD->USD/EUR->USD/CNY
1106 env(pay(alice, carol, CNY(100)),
1107 sendmax(XRP(100)),
1108 path(~USD, ~EUR, ~USD, ~CNY),
1109 txflags(tfNoRippleDirect),
1111 }
1112 }
1113
1114 void
1116 {
1117 testcase("test no account");
1118 using namespace jtx;
1119
1120 auto const alice = Account("alice");
1121 auto const bob = Account("bob");
1122 auto const gw = Account("gw");
1123 auto const USD = gw["USD"];
1124
1125 Env env(*this, features);
1126 env.fund(XRP(10000), alice, bob, gw);
1127
1128 STAmount const sendMax{USD.issue(), 100, 1};
1129 STAmount const noAccountAmount{Issue{USD.currency, noAccount()}, 100, 1};
1130 STAmount const deliver;
1131 AccountID const srcAcc = alice.id();
1132 AccountID const dstAcc = bob.id();
1133 STPathSet const pathSet;
1135 inputs.defaultPathsAllowed = true;
1136 try
1137 {
1138 PaymentSandbox sb{env.current().get(), tapNONE};
1139 {
1141 sb,
1142 sendMax,
1143 deliver,
1144 dstAcc,
1145 noAccount(),
1146 pathSet,
1148 env.app(),
1149 &inputs);
1150 BEAST_EXPECT(r.result() == temBAD_PATH);
1151 }
1152 {
1154 sb,
1155 sendMax,
1156 deliver,
1157 noAccount(),
1158 srcAcc,
1159 pathSet,
1161 env.app(),
1162 &inputs);
1163 BEAST_EXPECT(r.result() == temBAD_PATH);
1164 }
1165 {
1167 sb,
1168 noAccountAmount,
1169 deliver,
1170 dstAcc,
1171 srcAcc,
1172 pathSet,
1174 env.app(),
1175 &inputs);
1176 BEAST_EXPECT(r.result() == temBAD_PATH);
1177 }
1178 {
1180 sb,
1181 sendMax,
1182 noAccountAmount,
1183 dstAcc,
1184 srcAcc,
1185 pathSet,
1187 env.app(),
1188 &inputs);
1189 BEAST_EXPECT(r.result() == temBAD_PATH);
1190 }
1191 }
1192 catch (...)
1193 {
1194 this->fail();
1195 }
1196 }
1197
1198 void
1199 run() override
1200 {
1201 using namespace jtx;
1202 auto const sa = testable_amendments();
1203 testToStrand(sa - featurePermissionedDEX);
1204 testToStrand(sa);
1205
1206 testRIPD1373(sa - featurePermissionedDEX);
1207 testRIPD1373(sa);
1208
1209 testLoop(sa - featurePermissionedDEX);
1210 testLoop(sa);
1211
1212 testNoAccount(sa);
1213 }
1214};
1215
1216BEAST_DEFINE_TESTSUITE(PayStrand, app, xrpl);
1217
1218} // namespace test
1219} // namespace xrpl
T back(T... args)
T begin(T... args)
A testsuite class.
Definition suite.h:51
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:150
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:519
Maintains AMM info per overall payment engine execution and individual iteration.
Definition AMMContext.h:16
Specifies an order book.
Definition Book.h:16
A currency issued by an account.
Definition Issue.h:13
Currency currency
Definition Issue.h:15
AccountID account
Definition Issue.h:16
A wrapper which makes credits unavailable to balances.
A view into a ledger.
Definition ReadView.h:31
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:470
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 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:107
Immutable cryptographic account descriptor.
Definition Account.h:19
AccountID id() const
Returns the Account ID.
Definition Account.h:87
A transaction testing environment.
Definition Env.h:122
Application & app()
Definition Env.h:259
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:100
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition Env.cpp:258
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:270
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:301
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:588
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:329
A balance matches.
Definition balance.h:19
Inject raw JSON.
Definition jtx_json.h:13
Add a path.
Definition paths.h:38
Set Paths, SendMax on a JTx.
Definition paths.h:15
Sets the SendMax on a JTx.
Definition sendmax.h:13
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:15
Set the flags on a JTx.
Definition txflags.h:11
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:220
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
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:95
STPathElement allPathElements(AccountID const &a, Issue const &iss)
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
Json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition flags.h:101
STPathElement cpe(Currency const &c)
FeatureBitset testable_amendments()
Definition Env.h:78
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:5
@ terNO_LINE
Definition TER.h:199
@ terNO_AUTH
Definition TER.h:198
@ terNO_RIPPLE
Definition TER.h:204
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:97
std::pair< TER, Strand > toStrand(ReadView const &sb, 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:111
Currency const & xrpCurrency()
XRP currency.
Definition UintTypes.cpp:98
@ tapNONE
Definition ApplyView.h:11
AccountID const & noAccount()
A placeholder for empty accounts.
@ temBAD_PATH
Definition TER.h:76
@ temBAD_SEND_XRP_PATHS
Definition TER.h:83
@ temBAD_SEND_XRP_MAX
Definition TER.h:80
@ temBAD_PATH_LOOP
Definition TER.h:77
bool isTesSuccess(TER x) noexcept
Definition TER.h:651
AccountID const & xrpAccount()
Compute AccountID from public key.
@ tecPATH_DRY
Definition TER.h:275
@ no
Definition Steps.h:24
bool to_currency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Definition UintTypes.cpp:64
@ tesSUCCESS
Definition TER.h:225
T reserve(T... args)
T size(T... args)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:19
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)