rippled
Loading...
Searching...
No Matches
PayStrand_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2012, 2013 Ripple Labs Inc.
5 Permission to use, copy, modify, and/or distribute this software for any
6 purpose with or without fee is hereby granted, provided that the above
7 copyright notice and this permission notice appear in all copies.
8 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15*/
16//==============================================================================
17
18#include <test/jtx.h>
19#include <test/jtx/PathSet.h>
20
21#include <xrpld/app/paths/AMMContext.h>
22#include <xrpld/app/paths/RippleCalc.h>
23#include <xrpld/app/paths/detail/Steps.h>
24#include <xrpld/core/Config.h>
25
26#include <xrpl/basics/contract.h>
27#include <xrpl/basics/safe_cast.h>
28#include <xrpl/ledger/PaymentSandbox.h>
29#include <xrpl/protocol/Feature.h>
30#include <xrpl/protocol/jss.h>
31
32#include <optional>
33
34namespace ripple {
35namespace test {
36
43
48
49enum class TrustFlag { freeze, auth, noripple };
50
51/*constexpr*/ std::uint32_t
52trustFlag(TrustFlag f, bool useHigh)
53{
54 switch (f)
55 {
57 if (useHigh)
58 return lsfHighFreeze;
59 return lsfLowFreeze;
60 case TrustFlag::auth:
61 if (useHigh)
62 return lsfHighAuth;
63 return lsfLowAuth;
65 if (useHigh)
66 return lsfHighNoRipple;
67 return lsfLowNoRipple;
68 }
69 return 0; // Silence warning about end of non-void function
70}
71
72bool
74 jtx::Env const& env,
75 jtx::Account const& src,
76 jtx::Account const& dst,
77 Currency const& cur,
78 TrustFlag flag)
79{
80 if (auto sle = env.le(keylet::line(src, dst, cur)))
81 {
82 auto const useHigh = src.id() > dst.id();
83 return sle->isFlag(trustFlag(flag, useHigh));
84 }
85 Throw<std::runtime_error>("No line in getTrustFlag");
86 return false; // silence warning
87}
88
89bool
91{
92 if (!s1)
93 return false;
94 return test::directStepEqual(*s1, dsi.src, dsi.dst, dsi.currency);
95}
96
97bool
99{
100 if (!s1)
101 return false;
102 return test::xrpEndpointStepEqual(*s1, xrpsi.acc);
103}
104
105bool
107{
108 if (!s1)
109 return false;
110 return bookStepEqual(*s1, bsi);
111}
112
113template <class Iter>
114bool
116{
117 // base case. all args processed and found equal.
118 return true;
119}
120
121template <class Iter, class StepInfo, class... Args>
122bool
123strandEqualHelper(Iter i, StepInfo&& si, Args&&... args)
124{
125 if (!equal(*i, std::forward<StepInfo>(si)))
126 return false;
127 return strandEqualHelper(++i, std::forward<Args>(args)...);
128}
129
130template <class... Args>
131bool
132equal(Strand const& strand, Args&&... args)
133{
134 if (strand.size() != sizeof...(Args))
135 return false;
136 if (strand.empty())
137 return true;
138 return strandEqualHelper(strand.begin(), std::forward<Args>(args)...);
139}
140
142ape(AccountID const& a)
143{
144 return STPathElement(
146};
147
148// Issue path element
150ipe(Issue const& iss)
151{
152 return STPathElement(
154 xrpAccount(),
155 iss.currency,
156 iss.account);
157};
158
159// Issuer path element
161iape(AccountID const& account)
162{
163 return STPathElement(
165};
166
168{
169 enum class SB /*state bit*/
170 : std::uint16_t {
171 acc,
172 iss,
173 cur,
174 rootAcc,
175 rootIss,
176 xrp,
181 prevAcc,
182 prevCur,
183 prevIss,
184 boundary,
185 last
186 };
187
189 static_assert(
190 safe_cast<size_t>(SB::last) <= sizeof(decltype(state_)) * 8,
191 "");
192 STPathElement const* prev_ = nullptr;
193 // disallow iss and cur to be specified with acc is specified (simplifies
194 // some tests)
195 bool const allowCompound_ = false;
196
197 bool
198 has(SB s) const
199 {
200 return state_ & (1 << safe_cast<int>(s));
201 }
202
203 bool
205 {
206 for (auto const s : sb)
207 if (has(s))
208 return true;
209 return false;
210 }
211
212 size_t
214 {
215 size_t result = 0;
216
217 for (auto const s : sb)
218 if (has(s))
219 result++;
220 return result;
221 }
222
223public:
224 explicit ElementComboIter(STPathElement const* prev = nullptr) : prev_(prev)
225 {
226 }
227
228 bool
229 valid() const
230 {
231 return (allowCompound_ ||
232 !(has(SB::acc) && hasAny({SB::cur, SB::iss}))) &&
234 (!hasAny(
236 has(SB::acc)) &&
237 (!hasAny(
239 has(SB::iss)) &&
241 has(SB::cur)) &&
242 // These will be duplicates
246 }
247 bool
249 {
250 if (!(has(SB::last)))
251 {
252 do
253 {
254 ++state_;
255 } while (!valid());
256 }
257 return !has(SB::last);
258 }
259
260 template <
261 class Col,
262 class AccFactory,
263 class IssFactory,
264 class CurrencyFactory>
265 void
267 Col& col,
268 AccFactory&& accF,
269 IssFactory&& issF,
270 CurrencyFactory&& currencyF,
274 {
275 assert(!has(SB::last));
276
277 auto const acc = [&]() -> std::optional<AccountID> {
278 if (!has(SB::acc))
279 return std::nullopt;
280 if (has(SB::rootAcc))
281 return xrpAccount();
283 return existingAcc;
284 return accF().id();
285 }();
286 auto const iss = [&]() -> std::optional<AccountID> {
287 if (!has(SB::iss))
288 return std::nullopt;
289 if (has(SB::rootIss))
290 return xrpAccount();
291 if (has(SB::sameAccIss))
292 return acc;
294 return *existingIss;
295 return issF().id();
296 }();
297 auto const cur = [&]() -> std::optional<Currency> {
298 if (!has(SB::cur))
299 return std::nullopt;
300 if (has(SB::xrp))
301 return xrpCurrency();
303 return *existingCur;
304 return currencyF();
305 }();
306 if (!has(SB::boundary))
307 col.emplace_back(acc, cur, iss);
308 else
309 col.emplace_back(
311 acc.value_or(AccountID{}),
312 cur.value_or(Currency{}),
313 iss.value_or(AccountID{}));
314 }
315};
316
318{
322
324 getAccount(size_t id)
325 {
326 assert(id < accounts.size());
327 return accounts[id];
328 }
329
331 getCurrency(size_t id)
332 {
333 assert(id < currencies.size());
334 return currencies[id];
335 }
336
337 // ids from 0 through (nextAvail -1) have already been used in the
338 // path
341
348
349 void
354
356 {
359
361 : p_{p}, state_{p.getResetState()}
362 {
363 }
365 {
367 }
368 };
369
370 // Create the given number of accounts, and add trust lines so every
371 // account trusts every other with every currency
372 // Create an offer from every currency/account to every other
373 // currency/account; the offer owner is either the specified
374 // account or the issuer of the "taker gets" account
375 void
377 jtx::Env& env,
378 size_t numAct,
379 size_t numCur,
380 std::optional<size_t> const& offererIndex)
381 {
382 using namespace jtx;
383
384 assert(!offererIndex || offererIndex < numAct);
385
386 accounts.clear();
387 accounts.reserve(numAct);
388 currencies.clear();
389 currencies.reserve(numCur);
391 currencyNames.reserve(numCur);
392
393 constexpr size_t bufSize = 32;
394 char buf[bufSize];
395
396 for (size_t id = 0; id < numAct; ++id)
397 {
398 snprintf(buf, bufSize, "A%zu", id);
399 accounts.emplace_back(buf);
400 }
401
402 for (size_t id = 0; id < numCur; ++id)
403 {
404 if (id < 10)
405 snprintf(buf, bufSize, "CC%zu", id);
406 else if (id < 100)
407 snprintf(buf, bufSize, "C%zu", id);
408 else
409 snprintf(buf, bufSize, "%zu", id);
410 currencies.emplace_back(to_currency(buf));
412 }
413
414 for (auto const& a : accounts)
415 env.fund(XRP(100000), a);
416
417 // Every account trusts every other account with every currency
418 for (auto ai1 = accounts.begin(), aie = accounts.end(); ai1 != aie;
419 ++ai1)
420 {
421 for (auto ai2 = accounts.begin(); ai2 != aie; ++ai2)
422 {
423 if (ai1 == ai2)
424 continue;
425 for (auto const& cn : currencyNames)
426 {
427 env.trust((*ai1)[cn](1'000'000), *ai2);
428 if (ai1 > ai2)
429 {
430 // accounts with lower indexes hold balances from
431 // accounts
432 // with higher indexes
433 auto const& src = *ai1;
434 auto const& dst = *ai2;
435 env(pay(src, dst, src[cn](500000)));
436 }
437 }
438 env.close();
439 }
440 }
441
442 std::vector<IOU> ious;
443 ious.reserve(numAct * numCur);
444 for (auto const& a : accounts)
445 for (auto const& cn : currencyNames)
446 ious.emplace_back(a[cn]);
447
448 // create offers from every currency to every other currency
449 for (auto takerPays = ious.begin(), ie = ious.end(); takerPays != ie;
450 ++takerPays)
451 {
452 for (auto takerGets = ious.begin(); takerGets != ie; ++takerGets)
453 {
454 if (takerPays == takerGets)
455 continue;
456 auto const owner =
457 offererIndex ? accounts[*offererIndex] : takerGets->account;
458 if (owner.id() != takerGets->account.id())
459 env(pay(takerGets->account, owner, (*takerGets)(1000)));
460
461 env(offer(owner, (*takerPays)(1000), (*takerGets)(1000)),
463 }
464 env.close();
465 }
466
467 // create offers to/from xrp to every other ious
468 for (auto const& iou : ious)
469 {
470 auto const owner =
471 offererIndex ? accounts[*offererIndex] : iou.account;
472 env(offer(owner, iou(1000), XRP(1000)), txflags(tfPassive));
473 env(offer(owner, XRP(1000), iou(1000)), txflags(tfPassive));
474 env.close();
475 }
476 }
477
479 totalXRP(ReadView const& v, bool incRoot)
480 {
482 auto add = [&](auto const& a) {
483 // XRP balance
484 auto const sle = v.read(keylet::account(a));
485 if (!sle)
486 return;
487 auto const b = (*sle)[sfBalance];
488 totalXRP += b.mantissa();
489 };
490 for (auto const& a : accounts)
491 add(a);
492 if (incRoot)
493 add(xrpAccount());
494 return totalXRP;
495 }
496
497 // Check that the balances for all accounts for all currencies & XRP are the
498 // same
499 bool
500 checkBalances(ReadView const& v1, ReadView const& v2)
501 {
503
504 auto xrpBalance = [](ReadView const& v, ripple::Keylet const& k) {
505 auto const sle = v.read(k);
506 if (!sle)
507 return STAmount{};
508 return (*sle)[sfBalance];
509 };
510 auto lineBalance = [](ReadView const& v, ripple::Keylet const& k) {
511 auto const sle = v.read(k);
512 if (!sle)
513 return STAmount{};
514 return (*sle)[sfBalance];
515 };
517 for (auto ai1 = accounts.begin(), aie = accounts.end(); ai1 != aie;
518 ++ai1)
519 {
520 {
521 // XRP balance
522 auto const ak = keylet::account(*ai1);
523 auto const b1 = xrpBalance(v1, ak);
524 auto const b2 = xrpBalance(v2, ak);
525 totalXRP[0] += b1.mantissa();
526 totalXRP[1] += b2.mantissa();
527 if (b1 != b2)
528 diffs.emplace_back(b1, b2, xrpAccount(), *ai1);
529 }
530 for (auto ai2 = accounts.begin(); ai2 != aie; ++ai2)
531 {
532 if (ai1 >= ai2)
533 continue;
534 for (auto const& c : currencies)
535 {
536 // Line balance
537 auto const lk = keylet::line(*ai1, *ai2, c);
538 auto const b1 = lineBalance(v1, lk);
539 auto const b2 = lineBalance(v2, lk);
540 if (b1 != b2)
541 diffs.emplace_back(b1, b2, *ai1, *ai2);
542 }
543 }
544 }
545 return diffs.empty();
546 }
547
550 {
552 }
553
556 {
558 }
559
560 template <class F>
561 void
563 STAmount const& sendMax,
564 STAmount const& deliver,
565 std::vector<STPathElement> const& prefix,
566 std::vector<STPathElement> const& suffix,
567 std::optional<AccountID> const& existingAcc,
568 std::optional<Currency> const& existingCur,
569 std::optional<AccountID> const& existingIss,
570 F&& f)
571 {
572 auto accF = [&] { return this->getAvailAccount(); };
573 auto issF = [&] { return this->getAvailAccount(); };
574 auto currencyF = [&] { return this->getAvailCurrency(); };
575
576 STPathElement const* prevOuter =
577 prefix.empty() ? nullptr : &prefix.back();
578 ElementComboIter outer(prevOuter);
579
580 std::vector<STPathElement> outerResult;
582 auto const resultSize = prefix.size() + suffix.size() + 2;
583 outerResult.reserve(resultSize);
584 result.reserve(resultSize);
585 while (outer.next())
586 {
587 StateGuard og{*this};
588 outerResult = prefix;
589 outer.emplace_into(
590 outerResult,
591 accF,
592 issF,
593 currencyF,
594 existingAcc,
595 existingCur,
596 existingIss);
597 STPathElement const* prevInner = &outerResult.back();
598 ElementComboIter inner(prevInner);
599 while (inner.next())
600 {
601 StateGuard ig{*this};
602 result = outerResult;
603 inner.emplace_into(
604 result,
605 accF,
606 issF,
607 currencyF,
608 existingAcc,
609 existingCur,
610 existingIss);
611 result.insert(result.end(), suffix.begin(), suffix.end());
612 f(sendMax, deliver, result);
613 }
614 };
615 }
616};
617
619{
620 void
622 {
623 testcase("To Strand");
624
625 using namespace jtx;
626
627 auto const alice = Account("alice");
628 auto const bob = Account("bob");
629 auto const carol = Account("carol");
630 auto const gw = Account("gw");
631
632 auto const USD = gw["USD"];
633 auto const EUR = gw["EUR"];
634
635 auto const eurC = EUR.currency;
636 auto const usdC = USD.currency;
637
638 using D = DirectStepInfo;
639 using B = ripple::Book;
640 using XRPS = XRPEndpointStepInfo;
641
642 AMMContext ammContext(alice, false);
643
644 auto test = [&, this](
645 jtx::Env& env,
646 Issue const& deliver,
647 std::optional<Issue> const& sendMaxIssue,
648 STPath const& path,
649 TER expTer,
650 auto&&... expSteps) {
651 auto [ter, strand] = toStrand(
652 *env.current(),
653 alice,
654 bob,
655 deliver,
657 sendMaxIssue,
658 path,
659 true,
661 ammContext,
663 env.app().logs().journal("Flow"));
664 BEAST_EXPECT(ter == expTer);
665 if (sizeof...(expSteps) != 0)
666 BEAST_EXPECT(equal(
667 strand, std::forward<decltype(expSteps)>(expSteps)...));
668 };
669
670 {
671 Env env(*this, features);
672 env.fund(XRP(10000), alice, bob, gw);
673 env.trust(USD(1000), alice, bob);
674 env.trust(EUR(1000), alice, bob);
675 env(pay(gw, alice, EUR(100)));
676
677 {
678 STPath const path =
679 STPath({ipe(bob["USD"]), cpe(EUR.currency)});
680 auto [ter, _] = toStrand(
681 *env.current(),
682 alice,
683 alice,
684 /*deliver*/ xrpIssue(),
685 /*limitQuality*/ std::nullopt,
686 /*sendMaxIssue*/ EUR.issue(),
687 path,
688 true,
690 ammContext,
692 env.app().logs().journal("Flow"));
693 (void)_;
694 BEAST_EXPECT(ter == tesSUCCESS);
695 }
696 {
697 STPath const path = STPath({ipe(USD), cpe(xrpCurrency())});
698 auto [ter, _] = toStrand(
699 *env.current(),
700 alice,
701 alice,
702 /*deliver*/ xrpIssue(),
703 /*limitQuality*/ std::nullopt,
704 /*sendMaxIssue*/ EUR.issue(),
705 path,
706 true,
708 ammContext,
710 env.app().logs().journal("Flow"));
711 (void)_;
712 BEAST_EXPECT(ter == tesSUCCESS);
713 }
714 }
715
716 {
717 Env env(*this, features);
718 env.fund(XRP(10000), alice, bob, carol, gw);
719
720 test(env, USD, std::nullopt, STPath(), terNO_LINE);
721
722 env.trust(USD(1000), alice, bob, carol);
723 test(env, USD, std::nullopt, STPath(), tecPATH_DRY);
724
725 env(pay(gw, alice, USD(100)));
726 env(pay(gw, carol, USD(100)));
727
728 // Insert implied account
729 test(
730 env,
731 USD,
733 STPath(),
735 D{alice, gw, usdC},
736 D{gw, bob, usdC});
737 env.trust(EUR(1000), alice, bob);
738
739 // Insert implied offer
740 test(
741 env,
742 EUR,
743 USD.issue(),
744 STPath(),
746 D{alice, gw, usdC},
747 B{USD, EUR, std::nullopt},
748 D{gw, bob, eurC});
749
750 // Path with explicit offer
751 test(
752 env,
753 EUR,
754 USD.issue(),
755 STPath({ipe(EUR)}),
757 D{alice, gw, usdC},
758 B{USD, EUR, std::nullopt},
759 D{gw, bob, eurC});
760
761 // Path with offer that changes issuer only
762 env.trust(carol["USD"](1000), bob);
763 test(
764 env,
765 carol["USD"],
766 USD.issue(),
767 STPath({iape(carol)}),
769 D{alice, gw, usdC},
770 B{USD, carol["USD"], std::nullopt},
771 D{carol, bob, usdC});
772
773 // Path with XRP src currency
774 test(
775 env,
776 USD,
777 xrpIssue(),
778 STPath({ipe(USD)}),
780 XRPS{alice},
781 B{XRP, USD, std::nullopt},
782 D{gw, bob, usdC});
783
784 // Path with XRP dst currency.
785 test(
786 env,
787 xrpIssue(),
788 USD.issue(),
791 xrpAccount(),
792 xrpCurrency(),
793 xrpAccount()}}),
795 D{alice, gw, usdC},
796 B{USD, XRP, std::nullopt},
797 XRPS{bob});
798
799 // Path with XRP cross currency bridged payment
800 test(
801 env,
802 EUR,
803 USD.issue(),
804 STPath({cpe(xrpCurrency())}),
806 D{alice, gw, usdC},
807 B{USD, XRP, std::nullopt},
808 B{XRP, EUR, std::nullopt},
809 D{gw, bob, eurC});
810
811 // XRP -> XRP transaction can't include a path
812 test(env, XRP, std::nullopt, STPath({ape(carol)}), temBAD_PATH);
813
814 {
815 // The root account can't be the src or dst
816 auto flowJournal = env.app().logs().journal("Flow");
817 {
818 // The root account can't be the dst
819 auto r = toStrand(
820 *env.current(),
821 alice,
822 xrpAccount(),
823 XRP,
825 USD.issue(),
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 xrpAccount(),
839 alice,
840 XRP,
843 STPath(),
844 true,
846 ammContext,
848 flowJournal);
849 BEAST_EXPECT(r.first == temBAD_PATH);
850 }
851 {
852 // The root account can't be the src.
853 auto r = toStrand(
854 *env.current(),
855 noAccount(),
856 bob,
857 USD,
860 STPath(),
861 true,
863 ammContext,
865 flowJournal);
866 BEAST_EXPECT(r.first == temBAD_PATH);
867 }
868 }
869
870 // Create an offer with the same in/out issue
871 test(
872 env,
873 EUR,
874 USD.issue(),
875 STPath({ipe(USD), ipe(EUR)}),
877
878 // Path element with type zero
879 test(
880 env,
881 USD,
884 0, xrpAccount(), xrpCurrency(), xrpAccount())}),
886
887 // The same account can't appear more than once on a path
888 // `gw` will be used from alice->carol and implied between carol
889 // and bob
890 test(
891 env,
892 USD,
894 STPath({ape(gw), ape(carol)}),
896
897 // The same offer can't appear more than once on a path
898 test(
899 env,
900 EUR,
901 USD.issue(),
902 STPath({ipe(EUR), ipe(USD), ipe(EUR)}),
904 }
905
906 {
907 // cannot have more than one offer with the same output issue
908
909 using namespace jtx;
910 Env env(*this, features);
911
912 env.fund(XRP(10000), alice, bob, carol, gw);
913 env.trust(USD(10000), alice, bob, carol);
914 env.trust(EUR(10000), alice, bob, carol);
915
916 env(pay(gw, bob, USD(100)));
917 env(pay(gw, bob, EUR(100)));
918
919 env(offer(bob, XRP(100), USD(100)));
920 env(offer(bob, USD(100), EUR(100)), txflags(tfPassive));
921 env(offer(bob, EUR(100), USD(100)), txflags(tfPassive));
922
923 // payment path: XRP -> XRP/USD -> USD/EUR -> EUR/USD
924 env(pay(alice, carol, USD(100)),
925 path(~USD, ~EUR, ~USD),
926 sendmax(XRP(200)),
929 }
930
931 {
932 Env env(*this, features);
933 env.fund(XRP(10000), alice, bob, noripple(gw));
934 env.trust(USD(1000), alice, bob);
935 env(pay(gw, alice, USD(100)));
936 test(env, USD, std::nullopt, STPath(), terNO_RIPPLE);
937 }
938
939 {
940 // check global freeze
941 Env env(*this, features);
942 env.fund(XRP(10000), alice, bob, gw);
943 env.trust(USD(1000), alice, bob);
944 env(pay(gw, alice, USD(100)));
945
946 // Account can still issue payments
947 env(fset(alice, asfGlobalFreeze));
948 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
949 env(fclear(alice, asfGlobalFreeze));
950 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
951
952 // Account can not issue funds
953 env(fset(gw, asfGlobalFreeze));
954 test(env, USD, std::nullopt, STPath(), terNO_LINE);
955 env(fclear(gw, asfGlobalFreeze));
956 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
957
958 // Account can not receive funds
959 env(fset(bob, asfGlobalFreeze));
960 test(env, USD, std::nullopt, STPath(), terNO_LINE);
961 env(fclear(bob, asfGlobalFreeze));
962 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
963 }
964 {
965 // Freeze between gw and alice
966 Env env(*this, features);
967 env.fund(XRP(10000), alice, bob, gw);
968 env.trust(USD(1000), alice, bob);
969 env(pay(gw, alice, USD(100)));
970 test(env, USD, std::nullopt, STPath(), tesSUCCESS);
971 env(trust(gw, alice["USD"](0), tfSetFreeze));
972 BEAST_EXPECT(getTrustFlag(env, gw, alice, usdC, TrustFlag::freeze));
973 test(env, USD, std::nullopt, STPath(), terNO_LINE);
974 }
975 {
976 // check no auth
977 // An account may require authorization to receive IOUs from an
978 // issuer
979 Env env(*this, features);
980 env.fund(XRP(10000), alice, bob, gw);
981 env(fset(gw, asfRequireAuth));
982 env.trust(USD(1000), alice, bob);
983 // Authorize alice but not bob
984 env(trust(gw, alice["USD"](1000), tfSetfAuth));
985 BEAST_EXPECT(getTrustFlag(env, gw, alice, usdC, TrustFlag::auth));
986 env(pay(gw, alice, USD(100)));
987 env.require(balance(alice, USD(100)));
988 test(env, USD, std::nullopt, STPath(), terNO_AUTH);
989
990 // Check pure issue redeem still works
991 auto [ter, strand] = toStrand(
992 *env.current(),
993 alice,
994 gw,
995 USD,
998 STPath(),
999 true,
1001 ammContext,
1003 env.app().logs().journal("Flow"));
1004 BEAST_EXPECT(ter == tesSUCCESS);
1005 BEAST_EXPECT(equal(strand, D{alice, gw, usdC}));
1006 }
1007
1008 {
1009 // last step xrp from offer
1010 Env env(*this, features);
1011 env.fund(XRP(10000), alice, bob, gw);
1012 env.trust(USD(1000), alice, bob);
1013 env(pay(gw, alice, USD(100)));
1014
1015 // alice -> USD/XRP -> bob
1016 STPath path;
1017 path.emplace_back(std::nullopt, xrpCurrency(), std::nullopt);
1018
1019 auto [ter, strand] = toStrand(
1020 *env.current(),
1021 alice,
1022 bob,
1023 XRP,
1025 USD.issue(),
1026 path,
1027 false,
1029 ammContext,
1031 env.app().logs().journal("Flow"));
1032 BEAST_EXPECT(ter == tesSUCCESS);
1033 BEAST_EXPECT(equal(
1034 strand,
1035 D{alice, gw, usdC},
1036 B{USD.issue(), xrpIssue(), std::nullopt},
1037 XRPS{bob}));
1038 }
1039 }
1040
1041 void
1043 {
1044 using namespace jtx;
1045 testcase("RIPD1373");
1046
1047 auto const alice = Account("alice");
1048 auto const bob = Account("bob");
1049 auto const carol = Account("carol");
1050 auto const gw = Account("gw");
1051 auto const USD = gw["USD"];
1052 auto const EUR = gw["EUR"];
1053
1054 {
1055 Env env(*this, features);
1056 env.fund(XRP(10000), alice, bob, gw);
1057
1058 env.trust(USD(1000), alice, bob);
1059 env.trust(EUR(1000), alice, bob);
1060 env.trust(bob["USD"](1000), alice, gw);
1061 env.trust(bob["EUR"](1000), alice, gw);
1062
1063 env(offer(bob, XRP(100), bob["USD"](100)), txflags(tfPassive));
1064 env(offer(gw, XRP(100), USD(100)), txflags(tfPassive));
1065
1066 env(offer(bob, bob["USD"](100), bob["EUR"](100)),
1068 env(offer(gw, USD(100), EUR(100)), txflags(tfPassive));
1069
1070 Path const p = [&] {
1071 Path result;
1072 result.push_back(allpe(gw, bob["USD"]));
1073 result.push_back(cpe(EUR.currency));
1074 return result;
1075 }();
1076
1077 PathSet paths(p);
1078
1079 env(pay(alice, alice, EUR(1)),
1080 json(paths.json()),
1081 sendmax(XRP(10)),
1083 ter(temBAD_PATH));
1084 }
1085
1086 {
1087 Env env(*this, features);
1088
1089 env.fund(XRP(10000), alice, bob, carol, gw);
1090 env.trust(USD(10000), alice, bob, carol);
1091
1092 env(pay(gw, bob, USD(100)));
1093
1094 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
1095 env(offer(bob, USD(100), XRP(100)), txflags(tfPassive));
1096
1097 // payment path: XRP -> XRP/USD -> USD/XRP
1098 env(pay(alice, carol, XRP(100)),
1099 path(~USD, ~XRP),
1102 }
1103
1104 {
1105 Env env(*this, features);
1106
1107 env.fund(XRP(10000), alice, bob, carol, gw);
1108 env.trust(USD(10000), alice, bob, carol);
1109
1110 env(pay(gw, bob, USD(100)));
1111
1112 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
1113 env(offer(bob, USD(100), XRP(100)), txflags(tfPassive));
1114
1115 // payment path: XRP -> XRP/USD -> USD/XRP
1116 env(pay(alice, carol, XRP(100)),
1117 path(~USD, ~XRP),
1118 sendmax(XRP(200)),
1121 }
1122 }
1123
1124 void
1126 {
1127 testcase("test loop");
1128 using namespace jtx;
1129
1130 auto const alice = Account("alice");
1131 auto const bob = Account("bob");
1132 auto const carol = Account("carol");
1133 auto const gw = Account("gw");
1134 auto const USD = gw["USD"];
1135 auto const EUR = gw["EUR"];
1136 auto const CNY = gw["CNY"];
1137
1138 {
1139 Env env(*this, features);
1140
1141 env.fund(XRP(10000), alice, bob, carol, gw);
1142 env.trust(USD(10000), alice, bob, carol);
1143
1144 env(pay(gw, bob, USD(100)));
1145 env(pay(gw, alice, USD(100)));
1146
1147 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
1148 env(offer(bob, USD(100), XRP(100)), txflags(tfPassive));
1149
1150 // payment path: USD -> USD/XRP -> XRP/USD
1151 env(pay(alice, carol, USD(100)),
1152 sendmax(USD(100)),
1153 path(~XRP, ~USD),
1156 }
1157 {
1158 Env env(*this, features);
1159
1160 env.fund(XRP(10000), alice, bob, carol, gw);
1161 env.trust(USD(10000), alice, bob, carol);
1162 env.trust(EUR(10000), alice, bob, carol);
1163 env.trust(CNY(10000), alice, bob, carol);
1164
1165 env(pay(gw, bob, USD(100)));
1166 env(pay(gw, bob, EUR(100)));
1167 env(pay(gw, bob, CNY(100)));
1168
1169 env(offer(bob, XRP(100), USD(100)), txflags(tfPassive));
1170 env(offer(bob, USD(100), EUR(100)), txflags(tfPassive));
1171 env(offer(bob, EUR(100), CNY(100)), txflags(tfPassive));
1172
1173 // payment path: XRP->XRP/USD->USD/EUR->USD/CNY
1174 env(pay(alice, carol, CNY(100)),
1175 sendmax(XRP(100)),
1176 path(~USD, ~EUR, ~USD, ~CNY),
1179 }
1180 }
1181
1182 void
1184 {
1185 testcase("test no account");
1186 using namespace jtx;
1187
1188 auto const alice = Account("alice");
1189 auto const bob = Account("bob");
1190 auto const gw = Account("gw");
1191 auto const USD = gw["USD"];
1192
1193 Env env(*this, features);
1194 env.fund(XRP(10000), alice, bob, gw);
1195
1196 STAmount sendMax{USD.issue(), 100, 1};
1197 STAmount noAccountAmount{Issue{USD.currency, noAccount()}, 100, 1};
1198 STAmount deliver;
1199 AccountID const srcAcc = alice.id();
1200 AccountID dstAcc = bob.id();
1201 STPathSet pathSet;
1203 inputs.defaultPathsAllowed = true;
1204 try
1205 {
1206 PaymentSandbox sb{env.current().get(), tapNONE};
1207 {
1209 sb,
1210 sendMax,
1211 deliver,
1212 dstAcc,
1213 noAccount(),
1214 pathSet,
1216 env.app().logs(),
1217 &inputs);
1218 BEAST_EXPECT(r.result() == temBAD_PATH);
1219 }
1220 {
1222 sb,
1223 sendMax,
1224 deliver,
1225 noAccount(),
1226 srcAcc,
1227 pathSet,
1229 env.app().logs(),
1230 &inputs);
1231 BEAST_EXPECT(r.result() == temBAD_PATH);
1232 }
1233 {
1235 sb,
1236 noAccountAmount,
1237 deliver,
1238 dstAcc,
1239 srcAcc,
1240 pathSet,
1242 env.app().logs(),
1243 &inputs);
1244 BEAST_EXPECT(r.result() == temBAD_PATH);
1245 }
1246 {
1248 sb,
1249 sendMax,
1250 noAccountAmount,
1251 dstAcc,
1252 srcAcc,
1253 pathSet,
1255 env.app().logs(),
1256 &inputs);
1257 BEAST_EXPECT(r.result() == temBAD_PATH);
1258 }
1259 }
1260 catch (...)
1261 {
1262 this->fail();
1263 }
1264 }
1265
1266 void
1267 run() override
1268 {
1269 using namespace jtx;
1270 auto const sa = testable_amendments();
1271 testToStrand(sa - featurePermissionedDEX);
1272 testToStrand(sa);
1273
1274 testRIPD1373(sa - featurePermissionedDEX);
1275 testRIPD1373(sa);
1276
1277 testLoop(sa - featurePermissionedDEX);
1278 testLoop(sa);
1279
1280 testNoAccount(sa);
1281 }
1282};
1283
1284BEAST_DEFINE_TESTSUITE(PayStrand, app, ripple);
1285
1286} // namespace test
1287} // namespace ripple
T back(T... args)
T begin(T... args)
A testsuite class.
Definition suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:155
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:533
Maintains AMM info per overall payment engine execution and individual iteration.
Definition AMMContext.h:36
virtual Logs & logs()=0
Specifies an order book.
Definition Book.h:36
A currency issued by an account.
Definition Issue.h:33
AccountID account
Definition Issue.h:36
Currency currency
Definition Issue.h:35
beast::Journal journal(std::string const &name)
Definition Log.cpp:160
A wrapper which makes credits unavailable to balances.
A view into a ledger.
Definition ReadView.h:51
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:496
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:134
Immutable cryptographic account descriptor.
Definition Account.h:39
AccountID id() const
Returns the Account ID.
Definition Account.h:111
A transaction testing environment.
Definition Env.h:121
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:547
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:331
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:121
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:320
Application & app()
Definition Env.h:261
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:289
std::shared_ptr< SLE const > le(Account const &account) const
Return an account root.
Definition Env.cpp:277
A balance matches.
Definition balance.h:39
Inject raw JSON.
Definition jtx_json.h:33
Add a path.
Definition paths.h:58
Set Paths, SendMax on a JTx.
Definition paths.h:35
Sets the SendMax on a JTx.
Definition sendmax.h:33
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:35
Set the flags on a JTx.
Definition txflags.h:31
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:244
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:184
Json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition flags.h:121
Json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:32
Json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:29
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:30
FeatureBitset testable_amendments()
Definition Env.h:74
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:29
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:111
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:25
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:115
AccountID const & noAccount()
A placeholder for empty accounts.
constexpr std::uint32_t asfGlobalFreeze
Definition TxFlags.h:83
AccountID const & xrpAccount()
Compute AccountID from public key.
@ lsfHighNoRipple
constexpr std::uint32_t tfPassive
Definition TxFlags.h:98
@ no
Definition Steps.h:45
constexpr std::uint32_t tfPartialPayment
Definition TxFlags.h:108
constexpr std::uint32_t tfSetfAuth
Definition TxFlags.h:115
Currency const & xrpCurrency()
XRP currency.
@ tecPATH_DRY
Definition TER.h:294
constexpr std::uint32_t tfNoRippleDirect
Definition TxFlags.h:107
@ tesSUCCESS
Definition TER.h:244
@ tapNONE
Definition ApplyView.h:31
constexpr std::uint32_t asfRequireAuth
Definition TxFlags.h:78
@ terNO_RIPPLE
Definition TER.h:224
@ terNO_AUTH
Definition TER.h:218
@ terNO_LINE
Definition TER.h:219
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:118
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:134
bool to_currency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Definition UintTypes.cpp:84
@ temBAD_PATH_LOOP
Definition TER.h:97
@ temBAD_PATH
Definition TER.h:96
@ temBAD_SEND_XRP_PATHS
Definition TER.h:103
@ temBAD_SEND_XRP_MAX
Definition TER.h:100
T reserve(T... args)
T size(T... args)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:39
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)