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