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