xrpld
Loading...
Searching...
No Matches
PayStrand_test.cpp
1#include <test/jtx/Account.h>
2#include <test/jtx/Env.h>
3#include <test/jtx/PathSet.h>
4#include <test/jtx/TestHelpers.h>
5#include <test/jtx/amount.h>
6#include <test/jtx/balance.h>
7#include <test/jtx/flags.h>
8#include <test/jtx/jtx_json.h>
9#include <test/jtx/offer.h>
10#include <test/jtx/owners.h> // IWYU pragma: keep
11#include <test/jtx/pay.h>
12#include <test/jtx/sendmax.h>
13#include <test/jtx/ter.h>
14#include <test/jtx/trust.h>
15#include <test/jtx/txflags.h>
16
17#include <xrpl/basics/contract.h>
18#include <xrpl/basics/safe_cast.h>
19#include <xrpl/beast/unit_test/suite.h>
20#include <xrpl/ledger/ApplyView.h>
21#include <xrpl/ledger/PaymentSandbox.h>
22#include <xrpl/protocol/AccountID.h>
23#include <xrpl/protocol/Book.h>
24#include <xrpl/protocol/Feature.h>
25#include <xrpl/protocol/Indexes.h>
26#include <xrpl/protocol/Issue.h>
27#include <xrpl/protocol/Keylet.h>
28#include <xrpl/protocol/LedgerFormats.h>
29#include <xrpl/protocol/SField.h>
30#include <xrpl/protocol/STAmount.h>
31#include <xrpl/protocol/STPathSet.h>
32#include <xrpl/protocol/TER.h>
33#include <xrpl/protocol/TxFlags.h>
34#include <xrpl/protocol/UintTypes.h>
35#include <xrpl/tx/paths/RippleCalc.h>
36#include <xrpl/tx/paths/detail/Steps.h>
37#include <xrpl/tx/transactors/dex/AMMContext.h>
38
39#include <cassert>
40#include <cstddef>
41#include <cstdint>
42#include <cstdio>
43#include <initializer_list>
44#include <optional>
45#include <stdexcept>
46#include <string>
47#include <tuple>
48#include <vector>
49
50namespace xrpl::test {
51
52enum class TrustFlag { Freeze, Auth, Noripple };
53
54/*constexpr*/ std::uint32_t
55trustFlag(TrustFlag f, bool useHigh)
56{
57 switch (f)
58 {
60 if (useHigh)
61 return lsfHighFreeze;
62 return lsfLowFreeze;
63 case TrustFlag::Auth:
64 if (useHigh)
65 return lsfHighAuth;
66 return lsfLowAuth;
68 if (useHigh)
69 return lsfHighNoRipple;
70 return lsfLowNoRipple;
71 }
72 return 0; // Silence warning about end of non-void function
73}
74
75bool
77 jtx::Env const& env,
78 jtx::Account const& src,
79 jtx::Account const& dst,
80 Currency const& cur,
81 TrustFlag flag)
82{
83 if (auto sle = env.le(keylet::trustLine(src, dst, cur)))
84 {
85 auto const useHigh = src.id() > dst.id();
86 return sle->isFlag(trustFlag(flag, useHigh));
87 }
88 Throw<std::runtime_error>("No line in getTrustFlag");
89 return false; // silence warning
90}
91
93{
112
114 static_assert(safeCast<size_t>(SB::Last) <= sizeof(decltype(state_)) * 8, "");
115 STPathElement const* prev_ = nullptr;
116 // disallow iss and cur to be specified with acc is specified (simplifies
117 // some tests)
118 bool const allowCompound_ = false;
119
120 [[nodiscard]] bool
121 has(SB s) const
122 {
123 return (state_ & (1 << safeCast<int>(s))) != 0;
124 }
125
126 [[nodiscard]] bool
128 {
129 for (auto const s : sb)
130 {
131 if (has(s))
132 return true;
133 }
134 return false;
135 }
136
137 [[nodiscard]] size_t
139 {
140 size_t result = 0;
141
142 for (auto const s : sb)
143 {
144 if (has(s))
145 result++;
146 }
147 return result;
148 }
149
150public:
151 explicit ElementComboIter(STPathElement const* prev = nullptr) : prev_(prev)
152 {
153 }
154
155 [[nodiscard]] bool
156 valid() const
157 {
158 return (allowCompound_ || !(has(SB::Acc) && hasAny({SB::Cur, SB::Iss}))) &&
159 (!hasAny({SB::PrevAcc, SB::PrevCur, SB::PrevIss}) || (prev_ != nullptr)) &&
161 has(SB::Acc)) &&
163 has(SB::Iss)) &&
165 // These will be duplicates
169 }
170 bool
172 {
173 if (!(has(SB::Last)))
174 {
175 do
176 {
177 ++state_;
178 } while (!valid());
179 }
180 return !has(SB::Last);
181 }
182
183 template <class Col, class AccFactory, class IssFactory, class CurrencyFactory>
184 void
186 Col& col,
187 AccFactory&& accF,
188 IssFactory&& issF,
189 CurrencyFactory&& currencyF,
190 std::optional<AccountID> const& existingAcc,
191 std::optional<Currency> const& existingCur,
192 std::optional<AccountID> const& existingIss)
193 {
194 assert(!has(SB::Last));
195
196 auto const acc = [&]() -> std::optional<AccountID> {
197 if (!has(SB::Acc))
198 return std::nullopt;
199 if (has(SB::RootAcc))
200 return xrpAccount();
201 if (has(SB::ExistingAcc) && existingAcc)
202 return existingAcc;
203 return accF().id();
204 }();
205 auto const iss = [&]() -> std::optional<AccountID> {
206 if (!has(SB::Iss))
207 return std::nullopt;
208 if (has(SB::RootIss))
209 return xrpAccount();
210 if (has(SB::SameAccIss))
211 return acc;
212 if (has(SB::ExistingIss) && existingIss)
213 return existingIss;
214 return issF().id();
215 }();
216 auto const cur = [&]() -> std::optional<Currency> {
217 if (!has(SB::Cur))
218 return std::nullopt;
219 if (has(SB::Xrp))
220 return xrpCurrency();
221 if (has(SB::ExistingCur) && existingCur)
222 return existingCur;
223 return currencyF();
224 }();
225 if (!has(SB::Boundary))
226 {
227 col.emplace_back(acc, cur, iss);
228 }
229 else
230 {
231 col.emplace_back(
233 acc.value_or(AccountID{}),
234 cur.value_or(Currency{}),
235 iss.value_or(AccountID{}));
236 }
237 }
238};
239
241{
245
247 getAccount(size_t id)
248 {
249 assert(id < accounts.size());
250 return accounts[id];
251 }
252
254 getCurrency(size_t id)
255 {
256 assert(id < currencies.size());
257 return currencies[id];
258 }
259
260 // ids from 0 through (nextAvail -1) have already been used in the
261 // path
264
266 [[nodiscard]] ResetState
271
272 void
277
279 {
282
284 {
285 }
287 {
288 p.resetTo(state);
289 }
290 };
291
292 // Create the given number of accounts, and add trust lines so every
293 // account trusts every other with every currency
294 // Create an offer from every currency/account to every other
295 // currency/account; the offer owner is either the specified
296 // account or the issuer of the "taker gets" account
297 void
298 setupEnv(jtx::Env& env, size_t numAct, size_t numCur, std::optional<size_t> const& offererIndex)
299 {
300 using namespace jtx;
301
302 assert(!offererIndex || offererIndex < numAct);
303
304 accounts.clear();
305 accounts.reserve(numAct);
306 currencies.clear();
307 currencies.reserve(numCur);
308 currencyNames.clear();
309 currencyNames.reserve(numCur);
310
311 static constexpr size_t kBufSize = 32;
312 char buf[kBufSize];
313
314 for (size_t id = 0; id < numAct; ++id)
315 {
316 snprintf(buf, kBufSize, "A%zu", id);
317 accounts.emplace_back(buf);
318 }
319
320 for (size_t id = 0; id < numCur; ++id)
321 {
322 if (id < 10)
323 {
324 snprintf(buf, kBufSize, "CC%zu", id);
325 }
326 else if (id < 100)
327 {
328 snprintf(buf, kBufSize, "C%zu", id);
329 }
330 else
331 {
332 snprintf(buf, kBufSize, "%zu", id);
333 }
334 currencies.emplace_back(toCurrency(buf));
335 currencyNames.emplace_back(buf);
336 }
337
338 for (auto const& a : accounts)
339 env.fund(XRP(100000), a);
340
341 // Every account trusts every other account with every currency
342 for (auto ai1 = accounts.begin(), aie = accounts.end(); ai1 != aie; ++ai1)
343 {
344 for (auto ai2 = accounts.begin(); ai2 != aie; ++ai2)
345 {
346 if (ai1 == ai2)
347 continue;
348 for (auto const& cn : currencyNames)
349 {
350 env.trust((*ai1)[cn](1'000'000), *ai2);
351 if (ai1 > ai2)
352 {
353 // accounts with lower indexes hold balances from
354 // accounts
355 // with higher indexes
356 auto const& src = *ai1;
357 auto const& dst = *ai2;
358 env(pay(src, dst, src[cn](500000)));
359 }
360 }
361 env.close();
362 }
363 }
364
365 std::vector<IOU> ious;
366 ious.reserve(numAct * numCur);
367 for (auto const& a : accounts)
368 {
369 for (auto const& cn : currencyNames)
370 ious.emplace_back(a[cn]);
371 }
372
373 // create offers from every currency to every other currency
374 for (auto takerPays = ious.begin(), ie = ious.end(); takerPays != ie; ++takerPays)
375 {
376 for (auto takerGets = ious.begin(); takerGets != ie; ++takerGets)
377 {
378 if (takerPays == takerGets)
379 continue;
380 auto const owner = offererIndex ? accounts[*offererIndex] : takerGets->account;
381 if (owner.id() != takerGets->account.id())
382 env(pay(takerGets->account, owner, (*takerGets)(1000)));
383
384 env(offer(owner, (*takerPays)(1000), (*takerGets)(1000)), Txflags(tfPassive));
385 }
386 env.close();
387 }
388
389 // create offers to/from xrp to every other ious
390 for (auto const& iou : ious)
391 {
392 auto const owner = offererIndex ? accounts[*offererIndex] : iou.account;
393 env(offer(owner, iou(1000), XRP(1000)), Txflags(tfPassive));
394 env(offer(owner, XRP(1000), iou(1000)), Txflags(tfPassive));
395 env.close();
396 }
397 }
398
400 totalXRP(ReadView const& v, bool incRoot)
401 {
403 auto add = [&](auto const& a) {
404 // XRP balance
405 auto const sle = v.read(keylet::account(a));
406 if (!sle)
407 return;
408 auto const b = (*sle)[sfBalance];
409 totalXRP += b.mantissa();
410 };
411 for (auto const& a : accounts)
412 add(a);
413 if (incRoot)
414 add(xrpAccount());
415 return totalXRP;
416 }
417
418 // Check that the balances for all accounts for all currencies & XRP are the
419 // same
420 bool
421 checkBalances(ReadView const& v1, ReadView const& v2)
422 {
424
425 auto xrpBalance = [](ReadView const& v, xrpl::Keylet const& k) {
426 auto const sle = v.read(k);
427 if (!sle)
428 return STAmount{};
429 return (*sle)[sfBalance];
430 };
431 auto lineBalance = [](ReadView const& v, xrpl::Keylet const& k) {
432 auto const sle = v.read(k);
433 if (!sle)
434 return STAmount{};
435 return (*sle)[sfBalance];
436 };
438 for (auto ai1 = accounts.begin(), aie = accounts.end(); ai1 != aie; ++ai1)
439 {
440 {
441 // XRP balance
442 auto const ak = keylet::account(*ai1);
443 auto const b1 = xrpBalance(v1, ak);
444 auto const b2 = xrpBalance(v2, ak);
445 totalXRP[0] += b1.mantissa();
446 totalXRP[1] += b2.mantissa();
447 if (b1 != b2)
448 diffs.emplace_back(b1, b2, xrpAccount(), *ai1);
449 }
450 for (auto ai2 = accounts.begin(); ai2 != aie; ++ai2)
451 {
452 if (ai1 >= ai2)
453 continue;
454 for (auto const& c : currencies)
455 {
456 // Line balance
457 auto const lk = keylet::trustLine(*ai1, *ai2, c);
458 auto const b1 = lineBalance(v1, lk);
459 auto const b2 = lineBalance(v2, lk);
460 if (b1 != b2)
461 diffs.emplace_back(b1, b2, *ai1, *ai2);
462 }
463 }
464 }
465 return diffs.empty();
466 }
467
470 {
472 }
473
476 {
478 }
479
480 template <class F>
481 void
483 STAmount const& sendMax,
484 STAmount const& deliver,
485 std::vector<STPathElement> const& prefix,
486 std::vector<STPathElement> const& suffix,
487 std::optional<AccountID> const& existingAcc,
488 std::optional<Currency> const& existingCur,
489 std::optional<AccountID> const& existingIss,
490 F&& f)
491 {
492 auto accF = [&] { return this->getAvailAccount(); };
493 auto issF = [&] { return this->getAvailAccount(); };
494 auto currencyF = [&] { return this->getAvailCurrency(); };
495
496 STPathElement const* prevOuter = prefix.empty() ? nullptr : &prefix.back();
497 ElementComboIter outer(prevOuter);
498
499 std::vector<STPathElement> outerResult;
501 auto const resultSize = prefix.size() + suffix.size() + 2;
502 outerResult.reserve(resultSize);
503 result.reserve(resultSize);
504 while (outer.next())
505 {
506 StateGuard const og{*this};
507 outerResult = prefix;
508 outer.emplaceInto(
509 outerResult, accF, issF, currencyF, existingAcc, existingCur, existingIss);
510 STPathElement const* prevInner = &outerResult.back();
511 ElementComboIter inner(prevInner);
512 while (inner.next())
513 {
514 StateGuard const ig{*this};
515 result = outerResult;
516 inner.emplaceInto(
517 result, accF, issF, currencyF, existingAcc, existingCur, existingIss);
518 result.insert(result.end(), suffix.begin(), suffix.end());
519 f(sendMax, deliver, result);
520 }
521 };
522 }
523};
524
526{
527 void
529 {
530 testcase("To Strand");
531
532 using namespace jtx;
533
534 auto const alice = Account("alice");
535 auto const bob = Account("bob");
536 auto const carol = Account("carol");
537 auto const gw = Account("gw");
538
539 auto const usd = gw["USD"];
540 auto const eur = gw["EUR"];
541
542 auto const eurC = eur.currency;
543 auto const usdC = usd.currency;
544
545 using D = DirectStepInfo;
546 using B = xrpl::Book;
547 using XRPS = XRPEndpointStepInfo;
548
549 AMMContext ammContext(alice, false);
550
551 auto test = [&, this](
552 jtx::Env& env,
553 Issue const& deliver,
554 std::optional<Issue> const& sendMaxIssue,
555 STPath const& path,
556 TER expTer,
557 auto&&... expSteps) {
558 auto [ter, strand] = toStrand(
559 *env.current(),
560 alice,
561 bob,
562 deliver,
563 std::nullopt,
564 sendMaxIssue,
565 path,
566 true,
568 ammContext,
569 std::nullopt,
570 env.app().getJournal("Flow"));
571 BEAST_EXPECT(ter == expTer);
572 if (sizeof...(expSteps) != 0)
573 BEAST_EXPECT(equal(strand, std::forward<decltype(expSteps)>(expSteps)...));
574 };
575
576 {
577 Env env(*this, features);
578 env.fund(XRP(10000), alice, bob, gw);
579 env.trust(usd(1000), alice, bob);
580 env.trust(eur(1000), alice, bob);
581 env(pay(gw, alice, eur(100)));
582
583 {
584 STPath const path = STPath({ipe(bob["USD"]), cpe(eur.currency)});
585 auto [ter, _] = toStrand(
586 *env.current(),
587 alice,
588 alice,
589 /*deliver*/ xrpIssue(),
590 /*limitQuality*/ std::nullopt,
591 /*sendMaxIssue*/ eur,
592 path,
593 true,
595 ammContext,
596 std::nullopt,
597 env.app().getJournal("Flow"));
598 (void)_;
599 BEAST_EXPECT(isTesSuccess(ter));
600 }
601 {
602 STPath const path = STPath({ipe(usd), cpe(xrpCurrency())});
603 auto [ter, _] = toStrand(
604 *env.current(),
605 alice,
606 alice,
607 /*deliver*/ xrpIssue(),
608 /*limitQuality*/ std::nullopt,
609 /*sendMaxIssue*/ eur,
610 path,
611 true,
613 ammContext,
614 std::nullopt,
615 env.app().getJournal("Flow"));
616 (void)_;
617 BEAST_EXPECT(isTesSuccess(ter));
618 }
619 }
620
621 {
622 Env env(*this, features);
623 env.fund(XRP(10000), alice, bob, carol, gw);
624
625 test(env, usd, std::nullopt, STPath(), terNO_LINE);
626
627 env.trust(usd(1000), alice, bob, carol);
628 test(env, usd, std::nullopt, STPath(), tecPATH_DRY);
629
630 env(pay(gw, alice, usd(100)));
631 env(pay(gw, carol, usd(100)));
632
633 // Insert implied account
634 test(
635 env,
636 usd,
637 std::nullopt,
638 STPath(),
640 D{.src = alice, .dst = gw, .currency = usdC},
641 D{.src = gw, .dst = bob, .currency = usdC});
642 env.trust(eur(1000), alice, bob);
643
644 // Insert implied offer
645 test(
646 env,
647 eur,
648 usd,
649 STPath(),
651 D{.src = alice, .dst = gw, .currency = usdC},
652 B{usd, eur, std::nullopt},
653 D{.src = gw, .dst = bob, .currency = eurC});
654
655 // Path with explicit offer
656 test(
657 env,
658 eur,
659 usd,
660 STPath({ipe(eur)}),
662 D{.src = alice, .dst = gw, .currency = usdC},
663 B{usd, eur, std::nullopt},
664 D{.src = gw, .dst = bob, .currency = eurC});
665
666 // Path with offer that changes issuer only
667 env.trust(carol["USD"](1000), bob);
668 test(
669 env,
670 carol["USD"],
671 usd,
672 STPath({iape(carol)}),
674 D{.src = alice, .dst = gw, .currency = usdC},
675 B{usd, carol["USD"], std::nullopt},
676 D{.src = carol, .dst = bob, .currency = usdC});
677
678 // Path with XRP src currency
679 test(
680 env,
681 usd,
682 xrpIssue(),
683 STPath({ipe(usd)}),
685 XRPS{alice},
686 B{XRP, usd, std::nullopt},
687 D{.src = gw, .dst = bob, .currency = usdC});
688
689 // Path with XRP dst currency.
690 test(
691 env,
692 xrpIssue(),
693 usd,
697 D{.src = alice, .dst = gw, .currency = usdC},
698 B{usd, XRP, std::nullopt},
699 XRPS{bob});
700
701 // Path with XRP cross currency bridged payment
702 test(
703 env,
704 eur,
705 usd,
706 STPath({cpe(xrpCurrency())}),
708 D{.src = alice, .dst = gw, .currency = usdC},
709 B{usd, XRP, std::nullopt},
710 B{XRP, eur, std::nullopt},
711 D{.src = gw, .dst = bob, .currency = eurC});
712
713 // XRP -> XRP transaction can't include a path
714 test(env, XRP, std::nullopt, STPath({ape(carol)}), temBAD_PATH);
715
716 {
717 // The root account can't be the src or dst
718 auto flowJournal = env.app().getJournal("Flow");
719 {
720 // The root account can't be the dst
721 auto r = toStrand(
722 *env.current(),
723 alice,
724 xrpAccount(),
725 XRP,
726 std::nullopt,
727 usd,
728 STPath(),
729 true,
731 ammContext,
732 std::nullopt,
733 flowJournal);
734 BEAST_EXPECT(r.first == temBAD_PATH);
735 }
736 {
737 // The root account can't be the src
738 auto r = toStrand(
739 *env.current(),
740 xrpAccount(),
741 alice,
742 XRP,
743 std::nullopt,
744 std::nullopt,
745 STPath(),
746 true,
748 ammContext,
749 std::nullopt,
750 flowJournal);
751 BEAST_EXPECT(r.first == temBAD_PATH);
752 }
753 {
754 // The root account can't be the src.
755 auto r = toStrand(
756 *env.current(),
757 noAccount(),
758 bob,
759 usd,
760 std::nullopt,
761 std::nullopt,
762 STPath(),
763 true,
765 ammContext,
766 std::nullopt,
767 flowJournal);
768 BEAST_EXPECT(r.first == temBAD_PATH);
769 }
770 }
771
772 // Create an offer with the same in/out issue
773 test(env, eur, usd, STPath({ipe(usd), ipe(eur)}), temBAD_PATH);
774
775 // Path element with type zero
776 test(
777 env,
778 usd,
779 std::nullopt,
782
783 // The same account can't appear more than once on a path
784 // `gw` will be used from alice->carol and implied between carol
785 // and bob
786 test(env, usd, std::nullopt, STPath({ape(gw), ape(carol)}), temBAD_PATH_LOOP);
787
788 // The same offer can't appear more than once on a path
789 test(env, eur, usd, STPath({ipe(eur), ipe(usd), ipe(eur)}), temBAD_PATH_LOOP);
790 }
791
792 {
793 // cannot have more than one offer with the same output issue
794
795 using namespace jtx;
796 Env env(*this, features);
797
798 env.fund(XRP(10000), alice, bob, carol, gw);
799 env.trust(usd(10000), alice, bob, carol);
800 env.trust(eur(10000), alice, bob, carol);
801
802 env(pay(gw, bob, usd(100)));
803 env(pay(gw, bob, eur(100)));
804
805 env(offer(bob, XRP(100), usd(100)));
806 env(offer(bob, usd(100), eur(100)), Txflags(tfPassive));
807 env(offer(bob, eur(100), usd(100)), Txflags(tfPassive));
808
809 // payment path: XRP -> XRP/USD -> USD/EUR -> EUR/USD
810 env(pay(alice, carol, usd(100)),
811 Path(~usd, ~eur, ~usd),
812 Sendmax(XRP(200)),
813 Txflags(tfNoRippleDirect),
815 }
816
817 {
818 Env env(*this, features);
819 env.fund(XRP(10000), alice, bob, noripple(gw));
820 env.trust(usd(1000), alice, bob);
821 env(pay(gw, alice, usd(100)));
822 test(env, usd, std::nullopt, STPath(), terNO_RIPPLE);
823 }
824
825 {
826 // check global freeze
827 Env env(*this, features);
828 env.fund(XRP(10000), alice, bob, gw);
829 env.trust(usd(1000), alice, bob);
830 env(pay(gw, alice, usd(100)));
831
832 // Account can still issue payments
833 env(fset(alice, asfGlobalFreeze));
834 test(env, usd, std::nullopt, STPath(), tesSUCCESS);
835 env(fclear(alice, asfGlobalFreeze));
836 test(env, usd, std::nullopt, STPath(), tesSUCCESS);
837
838 // Account can not issue funds
839 env(fset(gw, asfGlobalFreeze));
840 test(env, usd, std::nullopt, STPath(), terNO_LINE);
841 env(fclear(gw, asfGlobalFreeze));
842 test(env, usd, std::nullopt, STPath(), tesSUCCESS);
843
844 // Account can not receive funds
845 env(fset(bob, asfGlobalFreeze));
846 test(env, usd, std::nullopt, STPath(), terNO_LINE);
847 env(fclear(bob, asfGlobalFreeze));
848 test(env, usd, std::nullopt, STPath(), tesSUCCESS);
849 }
850 {
851 // Freeze between gw and alice
852 Env env(*this, features);
853 env.fund(XRP(10000), alice, bob, gw);
854 env.trust(usd(1000), alice, bob);
855 env(pay(gw, alice, usd(100)));
856 test(env, usd, std::nullopt, STPath(), tesSUCCESS);
857 env(trust(gw, alice["USD"](0), tfSetFreeze));
858 BEAST_EXPECT(getTrustFlag(env, gw, alice, usdC, TrustFlag::Freeze));
859 test(env, usd, std::nullopt, STPath(), terNO_LINE);
860 }
861 {
862 // check no auth
863 // An account may require authorization to receive IOUs from an
864 // issuer
865 Env env(*this, features);
866 env.fund(XRP(10000), alice, bob, gw);
867 env(fset(gw, asfRequireAuth));
868 env.trust(usd(1000), alice, bob);
869 // Authorize alice but not bob
870 env(trust(gw, alice["USD"](1000), tfSetfAuth));
871 BEAST_EXPECT(getTrustFlag(env, gw, alice, usdC, TrustFlag::Auth));
872 env(pay(gw, alice, usd(100)));
873 env.require(Balance(alice, usd(100)));
874 test(env, usd, std::nullopt, STPath(), terNO_AUTH);
875
876 // Check pure issue redeem still works
877 auto [ter, strand] = toStrand(
878 *env.current(),
879 alice,
880 gw,
881 usd,
882 std::nullopt,
883 std::nullopt,
884 STPath(),
885 true,
887 ammContext,
888 std::nullopt,
889 env.app().getJournal("Flow"));
890 BEAST_EXPECT(isTesSuccess(ter));
891 BEAST_EXPECT(equal(strand, D{alice, gw, usdC}));
892 }
893
894 {
895 // last step xrp from offer
896 Env env(*this, features);
897 env.fund(XRP(10000), alice, bob, gw);
898 env.trust(usd(1000), alice, bob);
899 env(pay(gw, alice, usd(100)));
900
901 // alice -> USD/XRP -> bob
902 STPath path;
903 path.emplaceBack(std::nullopt, xrpCurrency(), std::nullopt);
904
905 auto [ter, strand] = toStrand(
906 *env.current(),
907 alice,
908 bob,
909 XRP,
910 std::nullopt,
911 usd,
912 path,
913 false,
915 ammContext,
916 std::nullopt,
917 env.app().getJournal("Flow"));
918 BEAST_EXPECT(isTesSuccess(ter));
919 BEAST_EXPECT(
920 equal(strand, D{alice, gw, usdC}, B{usd, xrpIssue(), std::nullopt}, XRPS{bob}));
921 }
922 }
923
924 void
926 {
927 using namespace jtx;
928 testcase("RIPD1373");
929
930 auto const alice = Account("alice");
931 auto const bob = Account("bob");
932 auto const carol = Account("carol");
933 auto const gw = Account("gw");
934 auto const usd = gw["USD"];
935 auto const eur = gw["EUR"];
936
937 {
938 Env env(*this, features);
939 env.fund(XRP(10000), alice, bob, gw);
940
941 env.trust(usd(1000), alice, bob);
942 env.trust(eur(1000), alice, bob);
943 env.trust(bob["USD"](1000), alice, gw);
944 env.trust(bob["EUR"](1000), alice, gw);
945
946 env(offer(bob, XRP(100), bob["USD"](100)), Txflags(tfPassive));
947 env(offer(gw, XRP(100), usd(100)), Txflags(tfPassive));
948
949 env(offer(bob, bob["USD"](100), bob["EUR"](100)), Txflags(tfPassive));
950 env(offer(gw, usd(100), eur(100)), Txflags(tfPassive));
951
952 TestPath const p = [&] {
953 TestPath result;
954 result.pushBack(allPathElements(gw, bob["USD"]));
955 result.pushBack(cpe(eur.currency));
956 return result;
957 }();
958
959 PathSet const paths(p);
960
961 env(pay(alice, alice, eur(1)),
962 Json(paths.json()),
963 Sendmax(XRP(10)),
964 Txflags(tfNoRippleDirect | tfPartialPayment),
966 }
967
968 {
969 Env env(*this, features);
970
971 env.fund(XRP(10000), alice, bob, carol, gw);
972 env.trust(usd(10000), alice, bob, carol);
973
974 env(pay(gw, bob, usd(100)));
975
976 env(offer(bob, XRP(100), usd(100)), Txflags(tfPassive));
977 env(offer(bob, usd(100), XRP(100)), Txflags(tfPassive));
978
979 // payment path: XRP -> XRP/USD -> USD/XRP
980 env(pay(alice, carol, XRP(100)),
981 Path(~usd, ~XRP),
982 Txflags(tfNoRippleDirect),
984 }
985
986 {
987 Env env(*this, features);
988
989 env.fund(XRP(10000), alice, bob, carol, gw);
990 env.trust(usd(10000), alice, bob, carol);
991
992 env(pay(gw, bob, usd(100)));
993
994 env(offer(bob, XRP(100), usd(100)), Txflags(tfPassive));
995 env(offer(bob, usd(100), XRP(100)), Txflags(tfPassive));
996
997 // payment path: XRP -> XRP/USD -> USD/XRP
998 env(pay(alice, carol, XRP(100)),
999 Path(~usd, ~XRP),
1000 Sendmax(XRP(200)),
1001 Txflags(tfNoRippleDirect),
1003 }
1004 }
1005
1006 void
1008 {
1009 testcase("test loop");
1010 using namespace jtx;
1011
1012 auto const alice = Account("alice");
1013 auto const bob = Account("bob");
1014 auto const carol = Account("carol");
1015 auto const gw = Account("gw");
1016 auto const usd = gw["USD"];
1017 auto const eur = gw["EUR"];
1018 auto const cny = gw["CNY"];
1019
1020 {
1021 Env env(*this, features);
1022
1023 env.fund(XRP(10000), alice, bob, carol, gw);
1024 env.trust(usd(10000), alice, bob, carol);
1025
1026 env(pay(gw, bob, usd(100)));
1027 env(pay(gw, alice, usd(100)));
1028
1029 env(offer(bob, XRP(100), usd(100)), Txflags(tfPassive));
1030 env(offer(bob, usd(100), XRP(100)), Txflags(tfPassive));
1031
1032 // payment path: USD -> USD/XRP -> XRP/USD
1033 env(pay(alice, carol, usd(100)),
1034 Sendmax(usd(100)),
1035 Path(~XRP, ~usd),
1036 Txflags(tfNoRippleDirect),
1038 }
1039 {
1040 Env env(*this, features);
1041
1042 env.fund(XRP(10000), alice, bob, carol, gw);
1043 env.trust(usd(10000), alice, bob, carol);
1044 env.trust(eur(10000), alice, bob, carol);
1045 env.trust(cny(10000), alice, bob, carol);
1046
1047 env(pay(gw, bob, usd(100)));
1048 env(pay(gw, bob, eur(100)));
1049 env(pay(gw, bob, cny(100)));
1050
1051 env(offer(bob, XRP(100), usd(100)), Txflags(tfPassive));
1052 env(offer(bob, usd(100), eur(100)), Txflags(tfPassive));
1053 env(offer(bob, eur(100), cny(100)), Txflags(tfPassive));
1054
1055 // payment path: XRP->XRP/USD->USD/EUR->USD/CNY
1056 env(pay(alice, carol, cny(100)),
1057 Sendmax(XRP(100)),
1058 Path(~usd, ~eur, ~usd, ~cny),
1059 Txflags(tfNoRippleDirect),
1061 }
1062 }
1063
1064 void
1066 {
1067 testcase("test no account");
1068 using namespace jtx;
1069
1070 auto const alice = Account("alice");
1071 auto const bob = Account("bob");
1072 auto const gw = Account("gw");
1073 auto const usd = gw["USD"];
1074
1075 Env env(*this, features);
1076 env.fund(XRP(10000), alice, bob, gw);
1077
1078 STAmount const sendMax{usd, 100, 1};
1079 STAmount const noAccountAmount{Issue{usd.currency, noAccount()}, 100, 1};
1080 STAmount const deliver;
1081 AccountID const srcAcc = alice.id();
1082 AccountID const dstAcc = bob.id();
1083 STPathSet const pathSet;
1085 inputs.defaultPathsAllowed = true;
1086 try
1087 {
1088 PaymentSandbox sb{env.current().get(), TapNone};
1089 {
1091 sb,
1092 sendMax,
1093 deliver,
1094 dstAcc,
1095 noAccount(),
1096 pathSet,
1097 std::nullopt,
1098 env.app(),
1099 &inputs);
1100 BEAST_EXPECT(r.result() == temBAD_PATH);
1101 }
1102 {
1104 sb,
1105 sendMax,
1106 deliver,
1107 noAccount(),
1108 srcAcc,
1109 pathSet,
1110 std::nullopt,
1111 env.app(),
1112 &inputs);
1113 BEAST_EXPECT(r.result() == temBAD_PATH);
1114 }
1115 {
1117 sb,
1118 noAccountAmount,
1119 deliver,
1120 dstAcc,
1121 srcAcc,
1122 pathSet,
1123 std::nullopt,
1124 env.app(),
1125 &inputs);
1126 BEAST_EXPECT(r.result() == temBAD_PATH);
1127 }
1128 {
1130 sb,
1131 sendMax,
1132 noAccountAmount,
1133 dstAcc,
1134 srcAcc,
1135 pathSet,
1136 std::nullopt,
1137 env.app(),
1138 &inputs);
1139 BEAST_EXPECT(r.result() == temBAD_PATH);
1140 }
1141 }
1142 catch (...)
1143 {
1144 this->fail();
1145 }
1146 }
1147
1148 void
1149 run() override
1150 {
1151 using namespace jtx;
1152 auto const sa = testableAmendments();
1153 testToStrand(sa - featurePermissionedDEX);
1154 testToStrand(sa);
1155
1156 testRIPD1373(sa - featurePermissionedDEX);
1157 testRIPD1373(sa);
1158
1159 testLoop(sa - featurePermissionedDEX);
1160 testLoop(sa);
1161
1162 testNoAccount(sa);
1163 }
1164};
1165
1167
1168} // namespace xrpl::test
T back(T... args)
T begin(T... args)
A testsuite class.
Definition suite.h:50
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:522
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
Maintains AMM info per overall payment engine execution and individual iteration.
Definition AMMContext.h:16
Specifies an order book.
Definition Book.h:16
A currency issued by an account.
Definition Issue.h:13
A wrapper which makes credits unavailable to balances.
A view into a ledger.
Definition ReadView.h:31
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
virtual beast::Journal getJournal(std::string const &name)=0
static Output rippleCalculate(PaymentSandbox &view, STAmount const &saMaxAmountReq, STAmount const &saDstAmountReq, AccountID const &uDstAccountID, AccountID const &uSrcAccountID, STPathSet const &spsPaths, std::optional< uint256 > const &domainID, ServiceRegistry &registry, Input const *const pInputs=nullptr)
ElementComboIter(STPathElement const *prev=nullptr)
void emplaceInto(Col &col, AccFactory &&accF, IssFactory &&issF, CurrencyFactory &&currencyF, std::optional< AccountID > const &existingAcc, std::optional< Currency > const &existingCur, std::optional< AccountID > const &existingIss)
bool hasAny(std::initializer_list< SB > sb) const
STPathElement const * prev_
size_t count(std::initializer_list< SB > sb) const
json::Value json() const
Definition PathSet.h:170
TestPath & pushBack(Issue const &iss)
Definition PathSet.h:108
Immutable cryptographic account descriptor.
Definition jtx/Account.h:17
AccountID id() const
Returns the Account ID.
Definition jtx/Account.h:85
A transaction testing environment.
Definition Env.h:143
Application & app()
Definition Env.h:280
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
SLE::const_pointer le(Account const &account) const
Return an account root.
Definition Env.cpp:284
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:296
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:327
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:605
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
Definition Env.h:353
Inject raw JSON.
Definition jtx_json.h:11
Add a path.
Definition paths.h:39
Sets the SendMax on a JTx.
Definition sendmax.h:13
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:13
Set the flags on a JTx.
Definition txflags.h:9
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 account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:186
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:241
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:14
STPathElement allPathElements(AccountID const &a, Asset const &asset)
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
json::Value fclear(Account const &account, std::uint32_t off)
Remove account flag.
Definition flags.h:102
FeatureBitset testableAmendments()
Definition Env.h:76
STPathElement cpe(PathAsset const &pa)
STPathElement ipe(Asset const &asset)
std::array< Account, 1+sizeof...(Args)> noripple(Account const &account, Args const &... args)
Designate accounts as no-ripple in Env::fund.
Definition Env.h:70
bool equal(STAmount const &sa1, STAmount const &sa2)
STPathElement iape(AccountID const &account)
json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:14
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:18
STPathElement ape(AccountID const &a)
json::Value fset(Account const &account, std::uint32_t on, std::uint32_t off=0)
Add and/or remove flag.
Definition flags.cpp:15
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
bool getTrustFlag(jtx::Env const &env, jtx::Account const &src, jtx::Account const &dst, Currency const &cur, TrustFlag flag)
std::uint32_t trustFlag(TrustFlag f, bool useHigh)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ terNO_LINE
Definition TER.h:211
@ terNO_AUTH
Definition TER.h:210
@ terNO_RIPPLE
Definition TER.h:216
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:97
std::pair< TER, Strand > toStrand(ReadView const &sb, AccountID const &src, AccountID const &dst, Asset const &deliver, std::optional< Quality > const &limitQuality, std::optional< Asset > const &sendMaxAsset, STPath const &path, bool ownerPaysTransferFee, OfferCrossing offerCrossing, AMMContext &ammContext, std::optional< uint256 > const &domainID, beast::Journal j)
Create a Strand for the specified path.
Definition PaySteps.cpp:170
constexpr std::enable_if_t< std::is_integral_v< Dest > &&std::is_integral_v< Src >, Dest > safeCast(Src s) noexcept
Definition safe_cast.h:21
BaseUInt< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition UintTypes.h:36
bool toCurrency(Currency &, std::string const &)
Tries to convert a string to a Currency, returns true on success.
Definition UintTypes.cpp:65
Currency const & xrpCurrency()
XRP currency.
Definition UintTypes.cpp:99
@ TapNone
Definition ApplyView.h:13
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:28
AccountID const & noAccount()
A placeholder for empty accounts.
@ temBAD_PATH
Definition TER.h:82
@ temBAD_SEND_XRP_PATHS
Definition TER.h:89
@ temBAD_SEND_XRP_MAX
Definition TER.h:86
@ temBAD_PATH_LOOP
Definition TER.h:83
bool isTesSuccess(TER x) noexcept
Definition TER.h:663
TERSubset< CanCvtToTER > TER
Definition TER.h:634
AccountID const & xrpAccount()
Compute AccountID from public key.
@ tecPATH_DRY
Definition TER.h:292
@ tesSUCCESS
Definition TER.h:240
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:49
T reserve(T... args)
T size(T... args)
A pair of SHAMap key and LedgerEntryType.
Definition Keylet.h:19
std::vector< jtx::Account > accounts
std::int64_t totalXRP(ReadView const &v, bool incRoot)
bool checkBalances(ReadView const &v1, ReadView const &v2)
std::vector< xrpl::Currency > currencies
void resetTo(ResetState const &s)
jtx::Account getAccount(size_t id)
void setupEnv(jtx::Env &env, size_t numAct, size_t numCur, std::optional< size_t > const &offererIndex)
void forEachElementPair(STAmount const &sendMax, STAmount const &deliver, std::vector< STPathElement > const &prefix, std::vector< STPathElement > const &suffix, std::optional< AccountID > const &existingAcc, std::optional< Currency > const &existingCur, std::optional< AccountID > const &existingIss, F &&f)
std::vector< std::string > currencyNames
xrpl::Currency getCurrency(size_t id)
std::tuple< size_t, size_t > ResetState
void testRIPD1373(FeatureBitset features)
void testLoop(FeatureBitset features)
void run() override
Runs the suite.
void testToStrand(FeatureBitset features)
void testNoAccount(FeatureBitset features)
T tie(T... args)