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