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