xrpld
Loading...
Searching...
No Matches
DirectStep.cpp
1#include <xrpl/basics/Log.h>
2#include <xrpl/basics/base_uint.h>
3#include <xrpl/beast/utility/Journal.h>
4#include <xrpl/beast/utility/Zero.h>
5#include <xrpl/beast/utility/instrumentation.h>
6#include <xrpl/ledger/PaymentSandbox.h>
7#include <xrpl/ledger/View.h>
8#include <xrpl/ledger/helpers/AccountRootHelpers.h>
9#include <xrpl/ledger/helpers/RippleStateHelpers.h>
10#include <xrpl/ledger/helpers/TokenHelpers.h>
11#include <xrpl/protocol/AccountID.h>
12#include <xrpl/protocol/AmountConversions.h>
13#include <xrpl/protocol/IOUAmount.h>
14#include <xrpl/protocol/Indexes.h>
15#include <xrpl/protocol/Issue.h>
16#include <xrpl/protocol/LedgerFormats.h>
17#include <xrpl/protocol/Quality.h>
18#include <xrpl/protocol/SField.h>
19#include <xrpl/protocol/STAmount.h>
20#include <xrpl/protocol/STLedgerEntry.h>
21#include <xrpl/protocol/TER.h>
22#include <xrpl/protocol/UintTypes.h>
23#include <xrpl/tx/paths/detail/EitherAmount.h>
24#include <xrpl/tx/paths/detail/StepChecks.h>
25#include <xrpl/tx/paths/detail/Steps.h>
26
27#include <boost/container/flat_set.hpp>
28
29#include <cstdint>
30#include <memory>
31#include <optional>
32#include <sstream>
33#include <string>
34#include <utility>
35
36namespace xrpl {
37
38template <class TDerived>
39class DirectStepI : public StepImp<IOUAmount, IOUAmount, DirectStepI<TDerived>>
40{
41protected:
45
46 // Charge transfer fees when the prev step redeems
47 Step const* const prevStep_ = nullptr;
48 bool const isLast_;
50
67
69
70 // Compute the maximum value that can flow from src->dst at
71 // the best available quality.
72 // return: first element is max amount that can flow,
73 // second is the debt direction of the source w.r.t. the dst
75 maxPaymentFlow(ReadView const& sb) const;
76
77 // Compute srcQOut and dstQIn when the source redeems.
79 qualitiesSrcRedeems(ReadView const& sb) const;
80
81 // Compute srcQOut and dstQIn when the source issues.
83 qualitiesSrcIssues(ReadView const& sb, DebtDirection prevStepDebtDirection) const;
84
85 // Returns srcQOut, dstQIn
87 qualities(ReadView const& sb, DebtDirection srcDebtDir, StrandDirection strandDir) const;
88
89private:
91 StrandContext const& ctx,
92 AccountID const& src,
93 AccountID const& dst,
94 Currency const& c)
95 : src_(src)
96 , dst_(dst)
97 , currency_(c)
98 , prevStep_(ctx.prevStep)
99 , isLast_(ctx.isLast)
100 , j_(ctx.j)
101 {
102 }
103
104public:
105 [[nodiscard]] AccountID const&
106 src() const
107 {
108 return src_;
109 }
110 [[nodiscard]] AccountID const&
111 dst() const
112 {
113 return dst_;
114 }
115 [[nodiscard]] Currency const&
116 currency() const
117 {
118 return currency_;
119 }
120
121 [[nodiscard]] std::optional<EitherAmount>
122 cachedIn() const override
123 {
124 if (!cache_)
125 return std::nullopt;
126 return EitherAmount(cache_->in);
127 }
128
129 [[nodiscard]] std::optional<EitherAmount>
130 cachedOut() const override
131 {
132 if (!cache_)
133 return std::nullopt;
134 return EitherAmount(cache_->out);
135 }
136
137 [[nodiscard]] std::optional<AccountID>
138 directStepSrcAcct() const override
139 {
140 return src_;
141 }
142
144 directStepAccts() const override
145 {
146 return std::make_pair(src_, dst_);
147 }
148
149 [[nodiscard]] DebtDirection
150 debtDirection(ReadView const& sb, StrandDirection dir) const override;
151
152 [[nodiscard]] std::uint32_t
153 lineQualityIn(ReadView const& v) const override;
154
156 qualityUpperBound(ReadView const& v, DebtDirection dir) const override;
157
160 PaymentSandbox& sb,
161 ApplyView& afView,
162 boost::container::flat_set<UInt256>& ofrsToRm,
163 IOUAmount const& out);
164
167 PaymentSandbox& sb,
168 ApplyView& afView,
169 boost::container::flat_set<UInt256>& ofrsToRm,
170 IOUAmount const& in);
171
173 validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in) override;
174
175 // Check for error, existing liquidity, and violations of auth/frozen
176 // constraints.
177 [[nodiscard]] TER
178 check(StrandContext const& ctx) const;
179
180 void
182 IOUAmount const& fwdIn,
183 IOUAmount const& fwdSrcToDst,
184 IOUAmount const& fwdOut,
185 DebtDirection srcDebtDir);
186
187 friend bool
188 operator==(DirectStepI const& lhs, DirectStepI const& rhs)
189 {
190 return lhs.src_ == rhs.src_ && lhs.dst_ == rhs.dst_ && lhs.currency_ == rhs.currency_;
191 }
192
193 friend bool
194 operator!=(DirectStepI const& lhs, DirectStepI const& rhs)
195 {
196 return !(lhs == rhs);
197 }
198
199protected:
201 logStringImpl(char const* name) const
202 {
204 ostr << name << ": "
205 << "\nSrc: " << src_ << "\nDst: " << dst_;
206 return ostr.str();
207 }
208
209private:
210 [[nodiscard]] bool
211 equal(Step const& rhs) const override
212 {
213 if (auto ds = dynamic_cast<DirectStepI const*>(&rhs))
214 {
215 return *this == *ds;
216 }
217 return false;
218 }
219
220 friend TDerived;
221};
222
223//------------------------------------------------------------------------------
224
225// Flow is used in two different circumstances for transferring funds:
226// o Payments, and
227// o Offer crossing.
228// The rules for handling funds in these two cases are almost, but not
229// quite, the same.
230
231// Payment DirectStep class (not offer crossing).
232class DirectIPaymentStep : public DirectStepI<DirectIPaymentStep>
233{
234public:
236 StrandContext const& ctx,
237 AccountID const& src,
238 AccountID const& dst,
239 Currency const& c)
241 {
242 }
243
244 using DirectStepI<DirectIPaymentStep>::check;
245
246 static bool
248 {
249 // A payment doesn't care whether or not prevStepRedeems.
250 return true;
251 }
252
253 static bool
255 {
256 // Payments have no particular expectations for what dstQIn will be.
257 return true;
258 }
259
260 [[nodiscard]] std::uint32_t
261 quality(ReadView const& sb, QualityDirection qDir) const;
262
263 // Compute the maximum value that can flow from src->dst at
264 // the best available quality.
265 // return: first element is max amount that can flow,
266 // second is the debt direction w.r.t. the source account
268 maxFlow(ReadView const& sb, IOUAmount const& desired) const;
269
270 // Verify the consistency of the step. These checks are specific to
271 // payments and assume that general checks were already performed.
272 [[nodiscard]] TER
273 check(StrandContext const& ctx, SLE::ConstRef sleSrc) const;
274
275 [[nodiscard]] std::string
276 logString() const override
277 {
278 return logStringImpl("DirectIPaymentStep");
279 }
280};
281
282// Offer crossing DirectStep class (not a payment).
283class DirectIOfferCrossingStep : public DirectStepI<DirectIOfferCrossingStep>
284{
285public:
287 StrandContext const& ctx,
288 AccountID const& src,
289 AccountID const& dst,
290 Currency const& c)
292 {
293 }
294
296
297 static bool
299 {
300 // During offer crossing we rely on the fact that prevStepRedeems
301 // will *always* issue. That's because:
302 // o If there's a prevStep_, it will always be a BookStep.
303 // o BookStep::debtDirection() always returns `issues` when offer
304 // crossing.
305 // An assert based on this return value will tell us if that
306 // behavior changes.
307 return issues(prevStepDir);
308 }
309
310 static bool
312 {
313 // Due to a couple of factors dstQIn is always QUALITY_ONE for
314 // offer crossing. If that changes we need to know.
315 return dstQIn == QUALITY_ONE;
316 }
317
318 static std::uint32_t
319 quality(ReadView const& sb, QualityDirection qDir);
320
321 // Compute the maximum value that can flow from src->dst at
322 // the best available quality.
323 // return: first element is max amount that can flow,
324 // second is the debt direction w.r.t the source
326 maxFlow(ReadView const& sb, IOUAmount const& desired) const;
327
328 // Verify the consistency of the step. These checks are specific to
329 // offer crossing and assume that general checks were already performed.
330 static TER
331 check(StrandContext const& ctx, SLE::ConstRef sleSrc);
332
333 [[nodiscard]] std::string
334 logString() const override
335 {
336 return logStringImpl("DirectIOfferCrossingStep");
337 }
338};
339
340//------------------------------------------------------------------------------
341
344{
345 if (src_ == dst_)
346 return QUALITY_ONE;
347
348 auto const sle = sb.read(keylet::trustLine(dst_, src_, currency_));
349
350 if (!sle)
351 return QUALITY_ONE;
352
353 auto const& field = [&, this]() -> SF_UINT32 const& {
354 if (qDir == QualityDirection::In)
355 {
356 // compute dst quality in
357 if (this->dst_ < this->src_)
358 {
359 return sfLowQualityIn;
360 }
361
362 return sfHighQualityIn;
363 }
364
365 // compute src quality out
366 if (this->src_ < this->dst_)
367 {
368 return sfLowQualityOut;
369 }
370
371 return sfHighQualityOut;
372 }();
373
374 if (!sle->isFieldPresent(field))
375 return QUALITY_ONE;
376
377 auto const q = (*sle)[field];
378 if (q == 0u)
379 return QUALITY_ONE;
380 return q;
381}
382
385{
386 // If offer crossing then ignore trust line Quality fields. This
387 // preserves a long-standing tradition.
388 return QUALITY_ONE;
389}
390
393{
394 return maxPaymentFlow(sb);
395}
396
399{
400 // When isLast and offer crossing then ignore trust line limits. Offer
401 // crossing has the ability to exceed the limit set by a trust line.
402 // We presume that if someone is creating an offer then they intend to
403 // fill as much of that offer as possible, even if the offer exceeds
404 // the limit that a trust line sets.
405 //
406 // A note on using "out" as the desired parameter for maxFlow. In some
407 // circumstances during payments we end up needing a value larger than
408 // "out" for "maxSrcToDst". But as of now (June 2016) that never happens
409 // during offer crossing. That's because, due to a couple of factors,
410 // "dstQIn" is always QUALITY_ONE for offer crossing.
411
412 if (isLast_)
413 return {desired, DebtDirection::Issues};
414
415 return maxPaymentFlow(sb);
416}
417
418TER
420{
421 // Since this is a payment a trust line must be present. Perform all
422 // trust line related checks.
423 {
424 auto const sleLine = ctx.view.read(keylet::trustLine(src_, dst_, currency_));
425 if (!sleLine)
426 {
427 JLOG(j_.trace()) << "DirectStepI: No credit line. " << *this;
428 return terNO_LINE;
429 }
430
431 auto const authField = (src_ > dst_) ? lsfHighAuth : lsfLowAuth;
432
433 if (sleSrc->isFlag(lsfRequireAuth) && !sleLine->isFlag(authField) &&
434 (*sleLine)[sfBalance] == beast::kZero)
435 {
436 JLOG(j_.debug()) << "DirectStepI: can't receive IOUs from issuer without auth."
437 << " src: " << src_;
438 return terNO_AUTH;
439 }
440
441 if (ctx.prevStep != nullptr)
442 {
443 if (ctx.prevStep->bookStepBook())
444 {
445 if (sleLine->isFlag((src_ > dst_) ? lsfHighNoRipple : lsfLowNoRipple))
446 return terNO_RIPPLE;
447 }
448 }
449 }
450
451 {
452 auto const owed = creditBalance(ctx.view, dst_, src_, currency_);
453 if (owed <= beast::kZero)
454 {
455 auto const limit = creditLimit(ctx.view, dst_, src_, currency_);
456 if (-owed >= limit)
457 {
458 JLOG(j_.debug()) << "DirectStepI: dry: owed: " << owed << " limit: " << limit;
459 return tecPATH_DRY;
460 }
461 }
462 }
463 return tesSUCCESS;
464}
465
466TER
468{
469 // The standard checks are all we can do because any remaining checks
470 // require the existence of a trust line. Offer crossing does not
471 // require a pre-existing trust line.
472 return tesSUCCESS;
473}
474
475//------------------------------------------------------------------------------
476
477template <class TDerived>
480{
481 auto const srcOwed = toAmount<IOUAmount>(
483
484 if (srcOwed.signum() > 0)
485 return {srcOwed, DebtDirection::Redeems};
486
487 // srcOwed is negative or zero
488 return {creditLimit2(sb, dst_, src_, currency_) + srcOwed, DebtDirection::Issues};
489}
490
491template <class TDerived>
494{
495 if (dir == StrandDirection::Forward && cache_)
496 return cache_->srcDebtDir;
497
498 auto const srcOwed = accountHolds(sb, src_, currency_, dst_, FreezeHandling::IgnoreFreeze, j_);
499 return srcOwed.signum() > 0 ? DebtDirection::Redeems : DebtDirection::Issues;
500}
501
502template <class TDerived>
505 PaymentSandbox& sb,
506 ApplyView& /*afView*/,
507 boost::container::flat_set<UInt256>& /*ofrsToRm*/,
508 IOUAmount const& out)
509{
510 cache_.reset();
511
512 auto const [maxSrcToDst, srcDebtDir] = static_cast<TDerived const*>(this)->maxFlow(sb, out);
513
514 auto const [srcQOut, dstQIn] = qualities(sb, srcDebtDir, StrandDirection::Reverse);
515 XRPL_ASSERT(
516 static_cast<TDerived const*>(this)->verifyDstQualityIn(dstQIn),
517 "xrpl::DirectStepI : valid destination quality");
518
519 Issue const srcToDstIss(currency_, redeems(srcDebtDir) ? dst_ : src_);
520
521 JLOG(j_.trace()) << "DirectStepI::rev"
522 << " srcRedeems: " << redeems(srcDebtDir) << " outReq: " << to_string(out)
523 << " maxSrcToDst: " << to_string(maxSrcToDst) << " srcQOut: " << srcQOut
524 << " dstQIn: " << dstQIn;
525
526 if (maxSrcToDst.signum() <= 0)
527 {
528 JLOG(j_.trace()) << "DirectStepI::rev: dry";
529 cache_.emplace(
531 return {beast::kZero, beast::kZero};
532 }
533
534 IOUAmount const srcToDst = mulRatio(out, QUALITY_ONE, dstQIn, /*roundUp*/ true);
535
536 if (srcToDst <= maxSrcToDst)
537 {
538 IOUAmount const in = mulRatio(srcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
539 cache_.emplace(in, srcToDst, out, srcDebtDir);
541 sb,
542 src_,
543 dst_,
544 toSTAmount(srcToDst, srcToDstIss),
545 /*checkIssuer*/ true,
546 j_);
547 JLOG(j_.trace()) << "DirectStepI::rev: Non-limiting"
548 << " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(in)
549 << " srcToDst: " << to_string(srcToDst) << " out: " << to_string(out);
550 return {in, out};
551 }
552
553 // limiting node
554 IOUAmount const in = mulRatio(maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
555 IOUAmount const actualOut = mulRatio(maxSrcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
556 cache_.emplace(in, maxSrcToDst, actualOut, srcDebtDir);
558 sb,
559 src_,
560 dst_,
561 toSTAmount(maxSrcToDst, srcToDstIss),
562 /*checkIssuer*/ true,
563 j_);
564 JLOG(j_.trace()) << "DirectStepI::rev: Limiting"
565 << " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(in)
566 << " srcToDst: " << to_string(maxSrcToDst) << " out: " << to_string(out);
567 return {in, actualOut};
568}
569
570// The forward pass should never have more liquidity than the reverse
571// pass. But sometimes rounding differences cause the forward pass to
572// deliver more liquidity. Use the cached values from the reverse pass
573// to prevent this.
574template <class TDerived>
575void
577 IOUAmount const& fwdIn,
578 IOUAmount const& fwdSrcToDst,
579 IOUAmount const& fwdOut,
580 DebtDirection srcDebtDir)
581{
582 // NOLINTBEGIN(bugprone-unchecked-optional-access) cache_ always set before setCacheLimiting is
583 // called
584 if (cache_->in < fwdIn)
585 {
586 IOUAmount const smallDiff(1, -9);
587 auto const diff = fwdIn - cache_->in;
588 if (diff > smallDiff)
589 {
590 if (fwdIn.exponent() != cache_->in.exponent() || !cache_->in.mantissa() ||
591 (double(fwdIn.mantissa()) / double(cache_->in.mantissa())) > 1.01)
592 {
593 // Detect large diffs on forward pass so they may be
594 // investigated
595 JLOG(j_.warn()) << "DirectStepI::fwd: setCacheLimiting"
596 << " fwdIn: " << to_string(fwdIn)
597 << " cacheIn: " << to_string(cache_->in)
598 << " fwdSrcToDst: " << to_string(fwdSrcToDst)
599 << " cacheSrcToDst: " << to_string(cache_->srcToDst)
600 << " fwdOut: " << to_string(fwdOut)
601 << " cacheOut: " << to_string(cache_->out);
602 cache_.emplace(fwdIn, fwdSrcToDst, fwdOut, srcDebtDir);
603 return;
604 }
605 }
606 }
607 cache_->in = fwdIn;
608 if (fwdSrcToDst < cache_->srcToDst)
609 cache_->srcToDst = fwdSrcToDst;
610 if (fwdOut < cache_->out)
611 cache_->out = fwdOut;
612 cache_->srcDebtDir = srcDebtDir;
613 // NOLINTEND(bugprone-unchecked-optional-access)
614};
615
616template <class TDerived>
619 PaymentSandbox& sb,
620 ApplyView& /*afView*/,
621 boost::container::flat_set<UInt256>& /*ofrsToRm*/,
622 IOUAmount const& in)
623{
624 XRPL_ASSERT(cache_, "xrpl::DirectStepI::fwdImp : cache is set");
625 // NOLINTBEGIN(bugprone-unchecked-optional-access) assert above
626
627 auto const [maxSrcToDst, srcDebtDir] =
628 static_cast<TDerived const*>(this)->maxFlow(sb, cache_->srcToDst);
629
630 auto const [srcQOut, dstQIn] = qualities(sb, srcDebtDir, StrandDirection::Forward);
631
632 Issue const srcToDstIss(currency_, redeems(srcDebtDir) ? dst_ : src_);
633
634 JLOG(j_.trace()) << "DirectStepI::fwd"
635 << " srcRedeems: " << redeems(srcDebtDir) << " inReq: " << to_string(in)
636 << " maxSrcToDst: " << to_string(maxSrcToDst) << " srcQOut: " << srcQOut
637 << " dstQIn: " << dstQIn;
638
639 if (maxSrcToDst.signum() <= 0)
640 {
641 JLOG(j_.trace()) << "DirectStepI::fwd: dry";
642 cache_.emplace(
644 return {beast::kZero, beast::kZero};
645 }
646
647 IOUAmount const srcToDst = mulRatio(in, QUALITY_ONE, srcQOut, /*roundUp*/ false);
648
649 if (srcToDst <= maxSrcToDst)
650 {
651 IOUAmount const out = mulRatio(srcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
652 setCacheLimiting(in, srcToDst, out, srcDebtDir);
654 sb,
655 src_,
656 dst_,
657 toSTAmount(cache_->srcToDst, srcToDstIss),
658 /*checkIssuer*/ true,
659 j_);
660 JLOG(j_.trace()) << "DirectStepI::fwd: Non-limiting"
661 << " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(in)
662 << " srcToDst: " << to_string(srcToDst) << " out: " << to_string(out);
663 }
664 else
665 {
666 // limiting node
667 IOUAmount const actualIn = mulRatio(maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
668 IOUAmount const out = mulRatio(maxSrcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
669 setCacheLimiting(actualIn, maxSrcToDst, out, srcDebtDir);
671 sb,
672 src_,
673 dst_,
674 toSTAmount(cache_->srcToDst, srcToDstIss),
675 /*checkIssuer*/ true,
676 j_);
677 JLOG(j_.trace()) << "DirectStepI::rev: Limiting"
678 << " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(actualIn)
679 << " srcToDst: " << to_string(srcToDst) << " out: " << to_string(out);
680 }
681 return {cache_->in, cache_->out};
682 // NOLINTEND(bugprone-unchecked-optional-access)
683}
684
685template <class TDerived>
688{
689 if (!cache_)
690 {
691 JLOG(j_.trace()) << "Expected valid cache in validFwd";
692 return {false, EitherAmount(IOUAmount(beast::kZero))};
693 }
694
695 auto const savCache = *cache_;
696
697 XRPL_ASSERT(in.holds<IOUAmount>(), "xrpl::DirectStepI::validFwd : input is IOU");
698
699 auto const [maxSrcToDst, srcDebtDir] =
700 static_cast<TDerived const*>(this)->maxFlow(sb, cache_->srcToDst);
701 (void)srcDebtDir;
702
703 try
704 {
705 boost::container::flat_set<UInt256> dummy;
706 fwdImp(sb, afView, dummy, in.get<IOUAmount>()); // changes cache
707 }
708 catch (FlowException const&)
709 {
710 return {false, EitherAmount(IOUAmount(beast::kZero))};
711 }
712
713 // NOLINTBEGIN(bugprone-unchecked-optional-access) fwdImp sets cache_ on success
714 if (maxSrcToDst < cache_->srcToDst)
715 {
716 JLOG(j_.warn()) << "DirectStepI: Strand re-execute check failed."
717 << " Exceeded max src->dst limit"
718 << " max src->dst: " << to_string(maxSrcToDst)
719 << " actual src->dst: " << to_string(cache_->srcToDst);
720 return {false, EitherAmount(cache_->out)};
721 }
722
723 if (!(checkNear(savCache.in, cache_->in) && checkNear(savCache.out, cache_->out)))
724 {
725 JLOG(j_.warn()) << "DirectStepI: Strand re-execute check failed."
726 << " ExpectedIn: " << to_string(savCache.in)
727 << " CachedIn: " << to_string(cache_->in)
728 << " ExpectedOut: " << to_string(savCache.out)
729 << " CachedOut: " << to_string(cache_->out);
730 return {false, EitherAmount(cache_->out)};
731 }
732 return {true, EitherAmount(cache_->out)};
733 // NOLINTEND(bugprone-unchecked-optional-access)
734}
735
736// Returns srcQOut, dstQIn
737template <class TDerived>
740{
741 if (prevStep_ == nullptr)
742 return {QUALITY_ONE, QUALITY_ONE};
743
744 auto const prevStepQIn = prevStep_->lineQualityIn(sb);
745 auto srcQOut = static_cast<TDerived const*>(this)->quality(sb, QualityDirection::Out);
746
747 if (prevStepQIn > srcQOut)
748 srcQOut = prevStepQIn;
749 return {srcQOut, QUALITY_ONE};
750}
751
752// Returns srcQOut, dstQIn
753template <class TDerived>
756 const
757{
758 // Charge a transfer rate when issuing and previous step redeems
759
760 XRPL_ASSERT(
761 static_cast<TDerived const*>(this)->verifyPrevStepDebtDirection(prevStepDebtDirection),
762 "xrpl::DirectStepI::qualitiesSrcIssues : will prevStepDebtDirection "
763 "issue");
764
765 std::uint32_t const srcQOut =
766 redeems(prevStepDebtDirection) ? transferRate(sb, src_).value : QUALITY_ONE;
767 auto dstQIn = static_cast<TDerived const*>(this)->quality(sb, QualityDirection::In);
768
769 if (isLast_ && dstQIn > QUALITY_ONE)
770 dstQIn = QUALITY_ONE;
771 return {srcQOut, dstQIn};
772}
773
774// Returns srcQOut, dstQIn
775template <class TDerived>
778 ReadView const& sb,
779 DebtDirection srcDebtDir,
780 StrandDirection strandDir) const
781{
782 if (redeems(srcDebtDir))
783 {
784 return qualitiesSrcRedeems(sb);
785 }
786
787 auto const prevStepDebtDirection = [&] {
788 if (prevStep_)
789 return prevStep_->debtDirection(sb, strandDir);
791 }();
792 return qualitiesSrcIssues(sb, prevStepDebtDirection);
793}
794
795template <class TDerived>
798{
799 // dst quality in
800 return static_cast<TDerived const*>(this)->quality(v, QualityDirection::In);
801}
802
803template <class TDerived>
806{
807 auto const dir = this->debtDirection(v, StrandDirection::Forward);
808
809 auto const [srcQOut, dstQIn] =
810 redeems(dir) ? qualitiesSrcRedeems(v) : qualitiesSrcIssues(v, prevStepDir);
811
812 Issue const iss{currency_, src_};
813 // Be careful not to switch the parameters to `getRate`. The
814 // `getRate(offerOut, offerIn)` function is usually used for offers. It
815 // returns offerIn/offerOut. For a direct step, the rate is srcQOut/dstQIn
816 // (Input*dstQIn/srcQOut = Output; So rate = srcQOut/dstQIn). Although the
817 // first parameter is called `offerOut`, it should take the `dstQIn`
818 // variable.
819 return {Quality(getRate(STAmount(iss, dstQIn), STAmount(iss, srcQOut))), dir};
820}
821
822template <class TDerived>
823TER
825{
826 // The following checks apply for both payments and offer crossing.
827 if (!src_ || !dst_)
828 {
829 JLOG(j_.debug()) << "DirectStepI: specified bad account.";
830 return temBAD_PATH;
831 }
832
833 if (src_ == dst_)
834 {
835 JLOG(j_.debug()) << "DirectStepI: same src and dst.";
836 return temBAD_PATH;
837 }
838
839 auto const sleSrc = ctx.view.read(keylet::account(src_));
840 if (!sleSrc)
841 {
842 JLOG(j_.warn()) << "DirectStepI: can't receive IOUs from non-existent issuer: " << src_;
843 return terNO_ACCOUNT;
844 }
845
846 // pure issue/redeem can't be frozen
847 if (!(ctx.isLast && ctx.isFirst))
848 {
849 if (auto const ter = checkFreeze(ctx.view, src_, dst_, currency_); !isTesSuccess(ter))
850 return ter;
851
852 // An LPToken redeemed against its AMM (dst_ is the LPToken issuer on
853 // this hop) cannot move if a pool asset is an MPT that forbids
854 // transfers between these accounts. A no-op unless dst_ is an AMM whose
855 // pool holds such an MPT (so it is implicitly gated by featureMPTokensV2).
856 if (auto const ter = canTransferLPToken(ctx.view, src_, dst_, dst_); !isTesSuccess(ter))
857 return ter;
858 }
859
860 // If previous step was a direct step then we need to check
861 // no ripple flags.
862 if (ctx.prevStep != nullptr)
863 {
864 if (auto prevSrc = ctx.prevStep->directStepSrcAcct())
865 {
866 auto const ter = checkNoRipple(ctx.view, *prevSrc, src_, dst_, currency_, j_);
867 if (!isTesSuccess(ter))
868 return ter;
869 }
870 }
871 {
872 Issue const srcIssue{currency_, src_};
873 Issue const dstIssue{currency_, dst_};
874
875 if (ctx.seenBookOuts.count(srcIssue) != 0u)
876 {
877 if (ctx.prevStep == nullptr)
878 {
879 // LCOV_EXCL_START
880 UNREACHABLE(
881 "xrpl::DirectStepI::check : prev seen book without a "
882 "prev step");
883 return temBAD_PATH_LOOP;
884 // LCOV_EXCL_STOP
885 }
886
887 // This is OK if the previous step is a book step that outputs this
888 // issue
889 if (auto book = ctx.prevStep->bookStepBook())
890 {
891 if (book->out.get<Issue>() != srcIssue)
892 return temBAD_PATH_LOOP;
893 }
894 }
895
896 if (!ctx.seenDirectAssets[0].insert(srcIssue).second ||
897 !ctx.seenDirectAssets[1].insert(dstIssue).second)
898 {
899 JLOG(j_.debug()) << "DirectStepI: loop detected: Index: " << ctx.strandSize << ' '
900 << *this;
901 return temBAD_PATH_LOOP;
902 }
903 }
904
905 return static_cast<TDerived const*>(this)->check(ctx, sleSrc);
906}
907
908//------------------------------------------------------------------------------
909
910namespace test {
911// Needed for testing
912bool
914 Step const& step,
915 AccountID const& src,
916 AccountID const& dst,
917 Currency const& currency)
918{
919 if (auto ds = dynamic_cast<DirectStepI<DirectIPaymentStep> const*>(&step))
920 {
921 return ds->src() == src && ds->dst() == dst && ds->currency() == currency;
922 }
923 return false;
924}
925} // namespace test
926
927//------------------------------------------------------------------------------
928
931 StrandContext const& ctx,
932 AccountID const& src,
933 AccountID const& dst,
934 Currency const& c)
935{
936 TER ter = tefINTERNAL;
939 {
940 auto offerCrossingStep = std::make_unique<DirectIOfferCrossingStep>(ctx, src, dst, c);
941 ter = offerCrossingStep->check(ctx);
942 r = std::move(offerCrossingStep);
943 }
944 else // payment
945 {
946 auto paymentStep = std::make_unique<DirectIPaymentStep>(ctx, src, dst, c);
947 ter = paymentStep->check(ctx);
948 r = std::move(paymentStep);
949 }
950 if (!isTesSuccess(ter))
951 return {ter, nullptr};
952
953 return {tesSUCCESS, std::move(r)};
954}
955
956} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:44
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:141
DirectIOfferCrossingStep(StrandContext const &ctx, AccountID const &src, AccountID const &dst, Currency const &c)
static TER check(StrandContext const &ctx, SLE::ConstRef sleSrc)
static bool verifyPrevStepDebtDirection(DebtDirection prevStepDir)
static std::uint32_t quality(ReadView const &sb, QualityDirection qDir)
static bool verifyDstQualityIn(std::uint32_t dstQIn)
std::pair< IOUAmount, DebtDirection > maxFlow(ReadView const &sb, IOUAmount const &desired) const
std::string logString() const override
DirectIPaymentStep(StrandContext const &ctx, AccountID const &src, AccountID const &dst, Currency const &c)
std::uint32_t quality(ReadView const &sb, QualityDirection qDir) const
std::pair< IOUAmount, DebtDirection > maxFlow(ReadView const &sb, IOUAmount const &desired) const
static bool verifyPrevStepDebtDirection(DebtDirection)
static bool verifyDstQualityIn(std::uint32_t dstQIn)
TER check(StrandContext const &ctx, SLE::ConstRef sleSrc) const
std::string logString() const override
Step const *const prevStep_
Currency const & currency() const
std::pair< std::uint32_t, std::uint32_t > qualitiesSrcIssues(ReadView const &sb, DebtDirection prevStepDebtDirection) const
std::pair< std::uint32_t, std::uint32_t > qualitiesSrcRedeems(ReadView const &sb) const
bool equal(Step const &rhs) const override
bool const isLast_
DirectStepI(StrandContext const &ctx, AccountID const &src, AccountID const &dst, Currency const &c)
void setCacheLimiting(IOUAmount const &fwdIn, IOUAmount const &fwdSrcToDst, IOUAmount const &fwdOut, DebtDirection srcDebtDir)
AccountID const & src() const
std::optional< EitherAmount > cachedOut() const override
std::optional< AccountID > directStepSrcAcct() const override
AccountID const & dst() const
friend bool operator==(DirectStepI const &lhs, DirectStepI const &rhs)
std::pair< std::optional< Quality >, DebtDirection > qualityUpperBound(ReadView const &v, DebtDirection dir) const override
beast::Journal const j_
std::string logStringImpl(char const *name) const
std::uint32_t lineQualityIn(ReadView const &v) const override
std::optional< Cache > cache_
std::optional< std::pair< AccountID, AccountID > > directStepAccts() const override
std::pair< IOUAmount, IOUAmount > fwdImp(PaymentSandbox &sb, ApplyView &afView, boost::container::flat_set< UInt256 > &ofrsToRm, IOUAmount const &in)
std::pair< bool, EitherAmount > validFwd(PaymentSandbox &sb, ApplyView &afView, EitherAmount const &in) override
std::pair< std::uint32_t, std::uint32_t > qualities(ReadView const &sb, DebtDirection srcDebtDir, StrandDirection strandDir) const
std::pair< IOUAmount, DebtDirection > maxPaymentFlow(ReadView const &sb) const
DebtDirection debtDirection(ReadView const &sb, StrandDirection dir) const override
std::pair< IOUAmount, IOUAmount > revImp(PaymentSandbox &sb, ApplyView &afView, boost::container::flat_set< UInt256 > &ofrsToRm, IOUAmount const &out)
TER check(StrandContext const &ctx) const
friend bool operator!=(DirectStepI const &lhs, DirectStepI const &rhs)
std::optional< EitherAmount > cachedIn() const override
Floating point representation of amounts with high dynamic range.
Definition IOUAmount.h:26
MantissaType mantissa() const noexcept
Definition IOUAmount.h:172
ExponentType exponent() const noexcept
Definition IOUAmount.h:166
A currency issued by an account.
Definition Issue.h:18
A wrapper which makes credits unavailable to balances.
Represents the logical ratio of output currency to input currency.
Definition Quality.h:90
A view into a ledger.
Definition ReadView.h:41
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
std::shared_ptr< STLedgerEntry const > const & ConstRef
A step in a payment path.
Definition Steps.h:80
virtual std::optional< AccountID > directStepSrcAcct() const
If this step is DirectStepI (IOU->IOU direct step), return the src account.
Definition Steps.h:139
virtual std::optional< Book > bookStepBook() const
If this step is a BookStep, return the book.
Definition Steps.h:217
T make_pair(T... args)
T make_unique(T... args)
constexpr Zero kZero
Definition Zero.h:30
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
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:275
bool directStepEqual(Step const &step, AccountID const &src, AccountID const &dst, Currency const &currency)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ terNO_LINE
Definition TER.h:220
@ terNO_AUTH
Definition TER.h:219
@ terNO_RIPPLE
Definition TER.h:225
@ terNO_ACCOUNT
Definition TER.h:218
std::pair< TER, std::unique_ptr< Step > > makeDirectStepI(StrandContext const &ctx, AccountID const &src, AccountID const &dst, Currency const &c)
bool issues(DebtDirection dir)
Definition Steps.h:47
@ tefINTERNAL
Definition TER.h:168
BaseUInt< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
Definition UintTypes.h:42
TypedField< STInteger< std::uint32_t > > SF_UINT32
Definition SField.h:344
STAmount creditLimit(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Calculate the maximum amount of IOUs that an account can hold.
DebtDirection
Definition Steps.h:35
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
StrandDirection
Definition Steps.h:37
IOUAmount creditLimit2(ReadView const &v, AccountID const &acc, AccountID const &iss, Currency const &cur)
TER canTransferLPToken(ReadView const &view, AccountID const &from, AccountID const &to, AccountID const &lpTokenIssuer)
Check whether an AMM LPToken may be transferred between from and to.
Definition View.cpp:186
QualityDirection
Definition Steps.h:36
IOUAmount mulRatio(IOUAmount const &amt, std::uint32_t num, std::uint32_t den, bool roundUp)
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
TER checkFreeze(ReadView const &view, AccountID const &src, AccountID const &dst, Currency const &currency)
Definition StepChecks.h:19
STAmount creditBalance(ReadView const &view, AccountID const &account, AccountID const &issuer, Currency const &currency)
Returns the amount of IOUs issued by issuer that are held by an account.
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition STAmount.cpp:423
TER directSendNoFee(ApplyView &view, AccountID const &uSenderID, AccountID const &uReceiverID, STAmount const &saAmount, bool bCheckIssuer, beast::Journal j)
Calls static directSendNoFeeIOU if saAmount represents Issue.
bool checkNear(IOUAmount const &expected, IOUAmount const &actual)
Definition PaySteps.cpp:36
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
@ temBAD_PATH
Definition TER.h:84
@ temBAD_PATH_LOOP
Definition TER.h:85
IOUAmount toAmount< IOUAmount >(STAmount const &amt)
bool isTesSuccess(TER x) noexcept
Definition TER.h:683
TERSubset< CanCvtToTER > TER
Definition TER.h:654
@ tecPATH_DRY
Definition TER.h:302
TER checkNoRipple(ReadView const &view, AccountID const &prev, AccountID const &cur, AccountID const &next, Currency const &currency, beast::Journal j)
Definition StepChecks.h:70
bool redeems(DebtDirection dir)
Definition Steps.h:41
STAmount accountHolds(ReadView const &view, AccountID const &account, Currency const &currency, AccountID const &issuer, FreezeHandling zeroIfFrozen, beast::Journal j, SpendableHandling includeFullBalance=SpendableHandling::SimpleBalance)
@ tesSUCCESS
Definition TER.h:250
STAmount toSTAmount(IOUAmount const &iou, Asset const &asset)
T str(T... args)
Cache(IOUAmount const &in, IOUAmount const &srcToDst, IOUAmount const &out, DebtDirection srcDebtDir)
bool holds() const
T const & get() const
std::uint32_t value
Definition Rate.h:22
Context needed to build Strand Steps and for error checking.
Definition Steps.h:525
size_t const strandSize
Length of Strand.
Definition Steps.h:536
ReadView const & view
Current ReadView.
Definition Steps.h:526
bool const isFirst
true if Step is first in Strand
Definition Steps.h:531
std::array< boost::container::flat_set< Asset >, 2 > & seenDirectAssets
A strand may not include the same account node more than once in the same currency.
Definition Steps.h:548
bool const isLast
true if Step is last in Strand
Definition Steps.h:532
boost::container::flat_set< Asset > & seenBookOuts
A strand may not include an offer that output the same issue more than once.
Definition Steps.h:553
Step const *const prevStep
The previous step in the strand.
Definition Steps.h:541
OfferCrossing const offerCrossing
Yes/Sell if offer crossing, not payment.
Definition Steps.h:534