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