xrpld
Loading...
Searching...
No Matches
MPTEndpointStep.cpp
1#include <xrpl/basics/Log.h>
2#include <xrpl/basics/Number.h>
3#include <xrpl/basics/base_uint.h>
4#include <xrpl/beast/utility/Journal.h>
5#include <xrpl/beast/utility/Zero.h>
6#include <xrpl/beast/utility/instrumentation.h>
7#include <xrpl/core/ServiceRegistry.h>
8#include <xrpl/ledger/PaymentSandbox.h>
9#include <xrpl/ledger/helpers/MPTokenHelpers.h>
10#include <xrpl/ledger/helpers/TokenHelpers.h>
11#include <xrpl/protocol/AccountID.h>
12#include <xrpl/protocol/AmountConversions.h>
13#include <xrpl/protocol/Indexes.h>
14#include <xrpl/protocol/MPTAmount.h>
15#include <xrpl/protocol/MPTIssue.h>
16#include <xrpl/protocol/Protocol.h>
17#include <xrpl/protocol/Quality.h>
18#include <xrpl/protocol/STAmount.h>
19#include <xrpl/protocol/TER.h>
20#include <xrpl/protocol/UintTypes.h>
21#include <xrpl/tx/paths/detail/EitherAmount.h>
22#include <xrpl/tx/paths/detail/Steps.h>
23
24#include <boost/container/flat_set.hpp>
25
26#include <algorithm>
27#include <cstdint>
28#include <memory>
29#include <optional>
30#include <sstream>
31#include <string>
32#include <utility>
33
34namespace xrpl {
35
36template <class TDerived>
37class MPTEndpointStep : public StepImp<MPTAmount, MPTAmount, MPTEndpointStep<TDerived>>
38{
39protected:
43
44 // Charge transfer fees when the prev step redeems
45 Step const* const prevStep_ = nullptr;
46 bool const isLast_;
47 // Direct payment between the holders
48 // Used by maxFlow's last step.
49 bool const isDirectBetweenHolders_ = false;
51
68
70
71 // Compute the maximum value that can flow from src->dst at
72 // the best available quality.
73 // return: first element is max amount that can flow,
74 // second is the debt direction of the source w.r.t. the dst
76 maxPaymentFlow(ReadView const& sb) const;
77
78 // Compute srcQOut and dstQIn when the source redeems.
80 qualitiesSrcRedeems(ReadView const& sb) const;
81
82 // Compute srcQOut and dstQIn when the source issues.
84 qualitiesSrcIssues(ReadView const& sb, DebtDirection prevStepDebtDirection) const;
85
86 // Returns srcQOut, dstQIn
88 qualities(ReadView const& sb, DebtDirection srcDebtDir, StrandDirection strandDir) const;
89
90 void
92
93 [[nodiscard]] TER
95 ApplyView& view,
96 AccountID const& src,
97 AccountID const& dst,
98 MPTAmount const& amount);
99
100private:
102 StrandContext const& ctx,
103 AccountID const& src,
104 AccountID const& dst,
105 MPTID const& mpt)
106 : src_(src)
107 , dst_(dst)
108 , mptIssue_(mpt)
109 , prevStep_(ctx.prevStep)
110 , isLast_(ctx.isLast)
112 mptIssue_ == ctx.strandDeliver && ctx.strandSrc != mptIssue_.getIssuer() &&
113 ctx.strandDst != mptIssue_.getIssuer() &&
114 (ctx.isFirst || (ctx.prevStep != nullptr && !ctx.prevStep->bookStepBook())))
115 , j_(ctx.j)
116 {
117 XRPL_ASSERT(
118 src_ == mptIssue_.getIssuer() || dst_ == mptIssue_.getIssuer(),
119 "MPTEndpointStep::MPTEndpointStep src or dst must be an issuer");
120 }
121
122public:
123 [[nodiscard]] AccountID const&
124 src() const
125 {
126 return src_;
127 }
128 [[nodiscard]] AccountID const&
129 dst() const
130 {
131 return dst_;
132 }
133 [[nodiscard]] MPTID const&
134 mptID() const
135 {
136 return mptIssue_.getMptID();
137 }
138
139 [[nodiscard]] std::optional<EitherAmount>
140 cachedIn() const override
141 {
142 if (!cache_)
143 return std::nullopt;
144 return EitherAmount(cache_->in);
145 }
146
147 [[nodiscard]] std::optional<EitherAmount>
148 cachedOut() const override
149 {
150 if (!cache_)
151 return std::nullopt;
152 return EitherAmount(cache_->out);
153 }
154
155 [[nodiscard]] std::optional<AccountID>
156 directStepSrcAcct() const override
157 {
158 return src_;
159 }
160
162 directStepAccts() const override
163 {
164 return std::make_pair(src_, dst_);
165 }
166
167 [[nodiscard]] DebtDirection
168 debtDirection(ReadView const& sb, StrandDirection dir) const override;
169
170 [[nodiscard]] std::uint32_t
171 lineQualityIn(ReadView const& v) const override;
172
174 qualityUpperBound(ReadView const& v, DebtDirection dir) const override;
175
178 PaymentSandbox& sb,
179 ApplyView& afView,
180 boost::container::flat_set<UInt256>& ofrsToRm,
181 MPTAmount const& out);
182
185 PaymentSandbox& sb,
186 ApplyView& afView,
187 boost::container::flat_set<UInt256>& ofrsToRm,
188 MPTAmount const& in);
189
191 validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in) override;
192
193 // Check for error, existing liquidity, and violations of auth/frozen
194 // constraints.
195 [[nodiscard]] TER
196 check(StrandContext const& ctx) const;
197
198 void
200 MPTAmount const& fwdIn,
201 MPTAmount const& fwdSrcToDst,
202 MPTAmount const& fwdOut,
203 DebtDirection srcDebtDir);
204
205 friend bool
207 {
208 return lhs.src_ == rhs.src_ && lhs.dst_ == rhs.dst_ && lhs.mptIssue_ == rhs.mptIssue_;
209 }
210
211 friend bool
213 {
214 return !(lhs == rhs);
215 }
216
217protected:
219 logStringImpl(char const* name) const
220 {
222 ostr << name << ": "
223 << "\nSrc: " << src_ << "\nDst: " << dst_;
224 return ostr.str();
225 }
226
227private:
228 [[nodiscard]] bool
229 equal(Step const& rhs) const override
230 {
231 if (auto ds = dynamic_cast<MPTEndpointStep const*>(&rhs))
232 {
233 return *this == *ds;
234 }
235 return false;
236 }
237
238 friend TDerived;
239};
240
241//------------------------------------------------------------------------------
242
243// Flow is used in two different circumstances for transferring funds:
244// o Payments, and
245// o Offer crossing.
246// The rules for handling funds in these two cases are almost, but not
247// quite, the same.
248
249// Payment MPTEndpointStep class (not offer crossing).
250class MPTEndpointPaymentStep : public MPTEndpointStep<MPTEndpointPaymentStep>
251{
252public:
255
257 StrandContext const& ctx,
258 AccountID const& src,
259 AccountID const& dst,
260 MPTID const& mpt)
262 {
263 }
264
265 static bool
267 {
268 // A payment doesn't care regardless of prevStepRedeems.
269 return true;
270 }
271
272 // Verify the consistency of the step. These checks are specific to
273 // payments and assume that general checks were already performed.
274 [[nodiscard]] TER
275 check(StrandContext const& ctx, SLE::ConstRef sleSrc) const;
276
277 [[nodiscard]] std::string
278 logString() const override
279 {
280 return logStringImpl("MPTEndpointPaymentStep");
281 }
282
283 // Not applicable for payment
284 static TER
286 {
287 return tesSUCCESS;
288 }
289};
290
291// Offer crossing MPTEndpointStep class (not a payment).
292class MPTEndpointOfferCrossingStep : public MPTEndpointStep<MPTEndpointOfferCrossingStep>
293{
294public:
297
299 StrandContext const& ctx,
300 AccountID const& src,
301 AccountID const& dst,
302 MPTID const& mpt)
304 {
305 }
306
307 static bool
309 {
310 // During offer crossing we rely on the fact that prevStepRedeems
311 // will *always* issue. That's because:
312 // o If there's a prevStep_, it will always be a BookStep.
313 // o BookStep::debtDirection() always returns `issues` when offer
314 // crossing.
315 // An assert based on this return value will tell us if that
316 // behavior changes.
317 return issues(prevStepDir);
318 }
319
320 // Verify the consistency of the step. These checks are specific to
321 // offer crossing and assume that general checks were already performed.
322 static TER
323 check(StrandContext const& ctx, SLE::ConstRef sleSrc);
324
325 [[nodiscard]] std::string
326 logString() const override
327 {
328 return logStringImpl("MPTEndpointOfferCrossingStep");
329 }
330
331 // Can be created in rev or fwd (if limiting step) direction.
332 TER
334};
335
336//------------------------------------------------------------------------------
337
338TER
340{
341 // Since this is a payment, MPToken must be present. Perform all
342 // MPToken related checks.
343
344 // requireAuth checks if MPTIssuance exist. Note that issuer to issuer
345 // payment is invalid
346 auto const& issuer = mptIssue_.getIssuer();
347 if (src_ != issuer)
348 {
349 if (auto const ter = requireAuth(ctx.view, mptIssue_, src_); !isTesSuccess(ter))
350 return ter;
351 }
352
353 if (dst_ != issuer)
354 {
355 if (auto const ter = requireAuth(ctx.view, mptIssue_, dst_); !isTesSuccess(ter))
356 return ter;
357 }
358
359 // Direct MPT payment, no DEX
360 if (mptIssue_ == ctx.strandDeliver &&
361 (ctx.isFirst || (ctx.prevStep != nullptr && !ctx.prevStep->bookStepBook())))
362 {
363 // Between holders
365 {
366 auto const& holder = ctx.isFirst ? src_ : dst_;
367 // Payment between the holders
368 if (isFrozen(ctx.view, holder, mptIssue_))
369 return tecLOCKED;
370
371 if (auto const ter = canTransfer(ctx.view, mptIssue_, holder, ctx.strandDst);
372 !isTesSuccess(ter))
373 return ter;
374 }
375 // Don't need to check if a payment is between issuer and holder
376 // in either direction
377 }
378 // Cross-token MPT payment via DEX
379 else
380 {
381 if (auto const ter = canTrade(ctx.view, mptIssue_); !isTesSuccess(ter))
382 return ter;
383 }
384
385 // Can't check for creditBalance/Limit unless it's the first step.
386 // Otherwise, even if OutstandingAmount is equal to MaximumAmount
387 // a payment can still be successful. For instance, when a balance
388 // is shifted from one holder to another.
389
390 if (prevStep_ == nullptr)
391 {
392 auto const owed = accountFunds(
394 // Already at MaximumAmount
395 if (owed <= beast::kZero)
396 return tecPATH_DRY;
397 }
398
399 return tesSUCCESS;
400}
401
402TER
404{
405 // The standard checks are all we can do because any remaining checks
406 // require the existence of a MPToken. Offer crossing does not
407 // require a pre-existing MPToken.
408 return tesSUCCESS;
409}
410
411TER
413{
414 // TakerPays is the last step if offer crossing
415 if (isLast_)
416 {
417 // Create MPToken for the offer's owner. No need to check
418 // for the reserve since the offer doesn't go on the books
419 // if crossed. Insufficient reserve is allowed if the offer
420 // crossed. See CreateOffer::applyGuts() for reserve check.
421 if (auto const err = xrpl::checkCreateMPT(view, mptIssue_, dst_, j_); !isTesSuccess(err))
422 {
423 // Unreachable: offer-crossing checks reject an offer whose owner
424 // could fail to create the MPToken.
425 // LCOV_EXCL_START
426 UNREACHABLE(
427 "xrpl::MPTEndpointOfferCrossingStep::checkCreateMPT : create MPToken failed");
428 JLOG(j_.trace()) << "MPTEndpointStep::checkCreateMPT: failed create MPT";
429 return err;
430 // LCOV_EXCL_STOP
431 }
432 }
433 return tesSUCCESS;
434}
435
436//------------------------------------------------------------------------------
437
438template <class TDerived>
439TER
441 ApplyView& view,
442 AccountID const& src,
443 AccountID const& dst,
444 MPTAmount const& amount)
445{
446 // Only offer crossing can fail here (payment checkCreateMPT is a no-op),
447 // via the unreachable path excluded in checkCreateMPT() above.
448 if (auto const err = static_cast<TDerived*>(this)->checkCreateMPT(view); !isTesSuccess(err))
449 return err; // LCOV_EXCL_LINE
450
451 return directSendNoFee(
452 view,
453 src,
454 dst,
455 toSTAmount(amount, mptIssue_),
456 /*checkIssuer*/ false,
457 j_);
458}
459
460//------------------------------------------------------------------------------
461
462template <class TDerived>
465{
466 auto const maxFlow = accountFunds(
468
469 // From a holder to an issuer
470 if (src_ != mptIssue_.getIssuer())
472
473 // From an issuer to a holder
474 if (auto const sle = sb.read(keylet::mptokenIssuance(mptIssue_)))
475 {
476 // If issuer is the source account, and it is direct payment then
477 // MPTEndpointStep is the only step. Provide available maxFlow.
478 if (prevStep_ == nullptr)
480
481 // MPTEndpointStep is the last step. It's always issuing in
482 // this case. Can't infer at this point what the maxFlow is, because
483 // the previous step may issue or redeem. Allow OutstandingAmount
484 // to temporarily overflow. Let the previous step figure out how
485 // to limit the flow.
486 std::int64_t const maxAmount = maxMPTAmount(*sle);
487 return {MPTAmount{maxAmount}, DebtDirection::Issues};
488 }
489
490 return {MPTAmount{0}, DebtDirection::Issues};
491}
492
493template <class TDerived>
496{
497 if (dir == StrandDirection::Forward && cache_)
498 return cache_->srcDebtDir;
499
501}
502
503template <class TDerived>
506 PaymentSandbox& sb,
507 ApplyView& /*afView*/,
508 boost::container::flat_set<UInt256>& /*ofrsToRm*/,
509 MPTAmount const& out)
510{
511 cache_.reset();
512
513 auto const [maxSrcToDst, srcDebtDir] = static_cast<TDerived const*>(this)->maxPaymentFlow(sb);
514
515 auto const [srcQOut, dstQIn] = qualities(sb, srcDebtDir, StrandDirection::Reverse);
516 (void)dstQIn;
517
518 JLOG(j_.trace()) << "MPTEndpointStep::rev"
519 << " srcRedeems: " << redeems(srcDebtDir) << " outReq: " << to_string(out)
520 << " maxSrcToDst: " << to_string(maxSrcToDst) << " srcQOut: " << srcQOut
521 << " dstQIn: " << dstQIn;
522
523 if (maxSrcToDst.signum() <= 0)
524 {
525 JLOG(j_.trace()) << "MPTEndpointStep::rev: dry";
526 resetCache(srcDebtDir);
527 return {beast::kZero, beast::kZero};
528 }
529
530 // When a previous step feeds this issuing step, srcQOut is the issuer's
531 // transfer rate and maxPaymentFlow() returns the issuance maximum rather
532 // than a real limit, so srcToDst * srcQOut need not be representable. Cap
533 // srcToDst at the largest amount whose input is; the previous step then
534 // limits the flow to what the source actually holds.
535 MPTAmount const maxRepresentable =
536 mulRatio(MPTAmount(kMaxMpTokenAmount), QUALITY_ONE, srcQOut, /*roundUp*/ false);
537
538 // Don't have to factor in dstQIn since it is always QUALITY_ONE
539 MPTAmount const srcToDst = std::min({out, maxSrcToDst, maxRepresentable});
540
541 // Can't overflow: srcToDst <= kMaxMpTokenAmount * QUALITY_ONE / srcQOut,
542 // so the rounded up product is at most kMaxMpTokenAmount.
543 MPTAmount const in = mulRatio(srcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
544
545 cache_.emplace(in, srcToDst, srcToDst, srcDebtDir);
546
547 auto const ter = sendWithMPTCreate(sb, src_, dst_, srcToDst);
548 if (!isTesSuccess(ter))
549 {
550 // Unreachable: send fails only on funds/auth/overflow, precluded by
551 // maxPaymentFlow, check() requireAuth, and 2*kMaxMpTokenAmount < 2^64.
552 // LCOV_EXCL_START
553 UNREACHABLE("xrpl::MPTEndpointStep::revImp : send failed");
554 JLOG(j_.trace()) << "MPTEndpointStep::rev: error " << ter;
555 resetCache(srcDebtDir);
556 return {beast::kZero, beast::kZero};
557 // LCOV_EXCL_STOP
558 }
559
560 JLOG(j_.trace()) << "MPTEndpointStep::rev: " << (srcToDst < out ? "Limiting" : "Non-limiting")
561 << " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(in)
562 << " srcToDst: " << to_string(srcToDst) << " out: " << to_string(out);
563
564 return {in, srcToDst};
565}
566
567// The forward pass should never have more liquidity than the reverse
568// pass. But sometimes rounding differences cause the forward pass to
569// deliver more liquidity. Use the cached values from the reverse pass
570// to prevent this.
571template <class TDerived>
572void
574 MPTAmount const& fwdIn,
575 MPTAmount const& fwdSrcToDst,
576 MPTAmount const& fwdOut,
577 DebtDirection srcDebtDir)
578{
579 // NOLINTBEGIN(bugprone-unchecked-optional-access) cache_ always set before setCacheLimiting is
580 // called
581 if (cache_->in < fwdIn)
582 {
583 MPTAmount const smallDiff(1);
584 auto const diff = fwdIn - cache_->in;
585 if (diff > smallDiff)
586 {
587 if (!cache_->in.value() ||
588 (Number(fwdIn.value()) / Number(cache_->in.value())) > Number(101, -2))
589 {
590 // Detect large diffs on forward pass so they may be
591 // investigated
592 JLOG(j_.warn()) << "MPTEndpointStep::fwd: setCacheLimiting"
593 << " fwdIn: " << to_string(fwdIn)
594 << " cacheIn: " << to_string(cache_->in)
595 << " fwdSrcToDst: " << to_string(fwdSrcToDst)
596 << " cacheSrcToDst: " << to_string(cache_->srcToDst)
597 << " fwdOut: " << to_string(fwdOut)
598 << " cacheOut: " << to_string(cache_->out);
599 cache_.emplace(fwdIn, fwdSrcToDst, fwdOut, srcDebtDir);
600 return;
601 }
602 }
603 }
604 cache_->in = fwdIn;
605 if (fwdSrcToDst < cache_->srcToDst)
606 cache_->srcToDst = fwdSrcToDst;
607 if (fwdOut < cache_->out)
608 cache_->out = fwdOut;
609 cache_->srcDebtDir = srcDebtDir;
610 // NOLINTEND(bugprone-unchecked-optional-access)
611};
612
613template <class TDerived>
616 PaymentSandbox& sb,
617 ApplyView& /*afView*/,
618 boost::container::flat_set<UInt256>& /*ofrsToRm*/,
619 MPTAmount const& in)
620{
621 XRPL_ASSERT(cache_, "MPTEndpointStep<TDerived>::fwdImp : valid cache");
622 // NOLINTBEGIN(bugprone-unchecked-optional-access) assert above
623
624 auto const [maxSrcToDst, srcDebtDir] = static_cast<TDerived const*>(this)->maxPaymentFlow(sb);
625
626 auto const [srcQOut, dstQIn] = qualities(sb, srcDebtDir, StrandDirection::Forward);
627 (void)dstQIn;
628
629 JLOG(j_.trace()) << "MPTEndpointStep::fwd"
630 << " srcRedeems: " << redeems(srcDebtDir) << " inReq: " << to_string(in)
631 << " maxSrcToDst: " << to_string(maxSrcToDst) << " srcQOut: " << srcQOut
632 << " dstQIn: " << dstQIn;
633
634 if (maxSrcToDst.signum() <= 0)
635 {
636 // Unreachable: the reverse pass owns dry detection; every path that
637 // reaches fwdImp (see StrandFlow::flow) has a funded source.
638 // LCOV_EXCL_START
639 UNREACHABLE("xrpl::MPTEndpointStep::fwdImp : dry source");
640 JLOG(j_.trace()) << "MPTEndpointStep::fwd: dry";
641 resetCache(srcDebtDir);
642 return {beast::kZero, beast::kZero};
643 // LCOV_EXCL_STOP
644 }
645
646 auto const maybeSrcToDst = tryMulRatio(in, QUALITY_ONE, srcQOut, /*roundUp*/ false);
647 if (!maybeSrcToDst)
648 {
649 // Unreachable: divides by srcQOut >= QUALITY_ONE, so result <= in <=
650 // maxMPTAmount and can never overflow int64.
651 // LCOV_EXCL_START
652 UNREACHABLE("xrpl::MPTEndpointStep::fwdImp : source to destination overflow");
653 JLOG(j_.trace()) << "MPTEndpointStep::fwd: overflow";
654 resetCache(srcDebtDir);
655 return {beast::kZero, beast::kZero};
656 // LCOV_EXCL_STOP
657 }
658
659 MPTAmount const srcToDst = *maybeSrcToDst;
660
661 if (srcToDst <= maxSrcToDst)
662 {
663 // Don't have to factor in dstQIn since it's always QUALITY_ONE
664 MPTAmount const out = srcToDst;
665 setCacheLimiting(in, srcToDst, out, srcDebtDir);
666
667 JLOG(j_.trace()) << "MPTEndpointStep::fwd: Non-limiting"
668 << " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(in)
669 << " srcToDst: " << to_string(srcToDst) << " out: " << to_string(out);
670 }
671 else
672 {
673 // Unreachable: the reverse pass owns all limiting; the forward driver
674 // (StrandFlow::flow) never re-finds a limit, so srcToDst <= maxSrcToDst.
675 // LCOV_EXCL_START
676 UNREACHABLE("xrpl::MPTEndpointStep::fwdImp : forward pass limiting");
677 // limiting node
678 auto const maybeActualIn = tryMulRatio(maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
679 if (!maybeActualIn)
680 {
681 JLOG(j_.trace()) << "MPTEndpointStep::fwd: overflow";
682 resetCache(srcDebtDir);
683 return {beast::kZero, beast::kZero};
684 }
685
686 MPTAmount const actualIn = *maybeActualIn;
687
688 // Don't have to factor in dstQIn since it's always QUALITY_ONE
689 MPTAmount const out = maxSrcToDst;
690 setCacheLimiting(actualIn, maxSrcToDst, out, srcDebtDir);
691
692 JLOG(j_.trace()) << "MPTEndpointStep::fwd: Limiting"
693 << " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(actualIn)
694 << " srcToDst: " << to_string(srcToDst) << " out: " << to_string(out);
695 // LCOV_EXCL_STOP
696 }
697
698 auto const ter = sendWithMPTCreate(sb, src_, dst_, cache_->srcToDst);
699 if (!isTesSuccess(ter))
700 {
701 // Unreachable: send fails only on funds/auth/overflow, precluded by
702 // maxPaymentFlow, check() requireAuth, and 2*kMaxMpTokenAmount < 2^64.
703 // LCOV_EXCL_START
704 UNREACHABLE("xrpl::MPTEndpointStep::fwdImp : send failed");
705 JLOG(j_.trace()) << "MPTEndpointStep::fwd: error " << ter;
706 resetCache(srcDebtDir);
707 return {beast::kZero, beast::kZero};
708 // LCOV_EXCL_STOP
709 }
710
711 return {cache_->in, cache_->out};
712 // NOLINTEND(bugprone-unchecked-optional-access)
713}
714
715template <class TDerived>
718{
719 if (!cache_)
720 {
721 JLOG(j_.trace()) << "Expected valid cache in validFwd";
722 return {false, EitherAmount(MPTAmount(beast::kZero))};
723 }
724
725 auto const savCache = *cache_;
726
727 XRPL_ASSERT(in.holds<MPTAmount>(), "MPTEndpoint<TDerived>::validFwd : is MPT");
728
729 auto const [maxSrcToDst, srcDebtDir] = static_cast<TDerived const*>(this)->maxPaymentFlow(sb);
730 (void)srcDebtDir;
731
732 try
733 {
734 boost::container::flat_set<UInt256> dummy;
735 fwdImp(sb, afView, dummy, in.get<MPTAmount>()); // changes cache
736 }
737 catch (FlowException const&)
738 {
739 return {false, EitherAmount(MPTAmount(beast::kZero))};
740 }
741
742 // NOLINTBEGIN(bugprone-unchecked-optional-access) fwdImp sets cache_ on success
743 if (maxSrcToDst < cache_->srcToDst)
744 {
745 JLOG(j_.warn()) << "MPTEndpointStep: Strand re-execute check failed."
746 << " Exceeded max src->dst limit"
747 << " max src->dst: " << to_string(maxSrcToDst)
748 << " actual src->dst: " << to_string(cache_->srcToDst);
749 return {false, EitherAmount(cache_->out)};
750 }
751
752 if (!(checkNear(savCache.in, cache_->in) && checkNear(savCache.out, cache_->out)))
753 {
754 JLOG(j_.warn()) << "MPTEndpointStep: Strand re-execute check failed."
755 << " ExpectedIn: " << to_string(savCache.in)
756 << " CachedIn: " << to_string(cache_->in)
757 << " ExpectedOut: " << to_string(savCache.out)
758 << " CachedOut: " << to_string(cache_->out);
759 return {false, EitherAmount(cache_->out)};
760 }
761 return {true, EitherAmount(cache_->out)};
762 // NOLINTEND(bugprone-unchecked-optional-access)
763}
764
765// Returns srcQOut, dstQIn
766template <class TDerived>
769{
770 if (prevStep_ == nullptr)
771 return {QUALITY_ONE, QUALITY_ONE};
772
773 auto const prevStepQIn = prevStep_->lineQualityIn(sb);
774 // Unlike trustline MPT doesn't have line quality field
775 auto srcQOut = QUALITY_ONE;
776
777 srcQOut = std::max<std::uint32_t>(prevStepQIn, srcQOut);
778 return {srcQOut, QUALITY_ONE};
779}
780
781// Returns srcQOut, dstQIn
782template <class TDerived>
785 ReadView const& sb,
786 DebtDirection prevStepDebtDirection) const
787{
788 // Charge a transfer rate when issuing and previous step redeems
789
790 XRPL_ASSERT(
791 static_cast<TDerived const*>(this)->verifyPrevStepDebtDirection(prevStepDebtDirection),
792 "MPTEndpointStep<TDerived>::qualitiesSrcIssues : verify prev step debt "
793 "direction");
794
795 std::uint32_t const srcQOut =
796 redeems(prevStepDebtDirection) ? transferRate(sb, mptIssue_.getMptID()).value : QUALITY_ONE;
797
798 // Unlike trustline, MPT doesn't have line quality field
799 return {srcQOut, QUALITY_ONE};
800}
801
802// Returns srcQOut, dstQIn
803template <class TDerived>
806 ReadView const& sb,
807 DebtDirection srcDebtDir,
808 StrandDirection strandDir) const
809{
810 if (redeems(srcDebtDir))
811 {
812 return qualitiesSrcRedeems(sb);
813 }
814
815 auto const prevStepDebtDirection = [&] {
816 if (prevStep_ != nullptr)
817 return prevStep_->debtDirection(sb, strandDir);
819 }();
820 return qualitiesSrcIssues(sb, prevStepDebtDirection);
821}
822
823template <class TDerived>
826{
827 // dst quality in
828 return QUALITY_ONE;
829}
830
831template <class TDerived>
834{
835 auto const dir = this->debtDirection(v, StrandDirection::Forward);
836
837 auto const [srcQOut, dstQIn] =
838 redeems(dir) ? qualitiesSrcRedeems(v) : qualitiesSrcIssues(v, prevStepDir);
839 (void)dstQIn;
840
841 MPTIssue const iss{mptIssue_};
842 // Be careful not to switch the parameters to `getRate`. The
843 // `getRate(offerOut, offerIn)` function is usually used for offers. It
844 // returns offerIn/offerOut. For a direct step, the rate is srcQOut/dstQIn
845 // (Input*dstQIn/srcQOut = Output; So rate = srcQOut/dstQIn). Although the
846 // first parameter is called `offerOut`, it should take the `dstQIn`
847 // variable.
848 return {Quality(getRate(STAmount(iss, QUALITY_ONE), STAmount(iss, srcQOut))), dir};
849}
850
851template <class TDerived>
852TER
854{
855 // The following checks apply for both payments and offer crossing.
856 if (!src_ || !dst_)
857 {
858 JLOG(j_.debug()) << "MPTEndpointStep: specified bad account.";
859 return temBAD_PATH;
860 }
861
862 if (src_ == dst_)
863 {
864 JLOG(j_.debug()) << "MPTEndpointStep: same src and dst.";
865 return temBAD_PATH;
866 }
867
868 auto const sleSrc = ctx.view.read(keylet::account(src_));
869 if (!sleSrc)
870 {
871 JLOG(j_.warn()) << "MPTEndpointStep: can't receive MPT from non-existent issuer: " << src_;
872 return terNO_ACCOUNT;
873 }
874
875 // pure issue/redeem can't be frozen (issuer/holder)
876 // For the first step: check global freeze of the step's own asset.
877 // For the last step: check only the per-holder MPToken lock.
878 // Global freeze of the deliver asset is not checked here
879 // because MPT semantics allow issuer<->holder transfers even when globally
880 // locked — only holder-to-holder DEX paths are restricted.
881 if (!(ctx.isLast && ctx.isFirst))
882 {
883 auto const& account = ctx.isFirst ? src_ : dst_;
884 bool const frozen = (ctx.isFirst && isGlobalFrozen(ctx.view, mptIssue_)) ||
885 isIndividualFrozen(ctx.view, account, mptIssue_);
886 if (frozen)
887 return terLOCKED;
888 }
889
890 if (ctx.seenBookOuts.count(mptIssue_) > 0)
891 {
892 if (ctx.prevStep == nullptr)
893 {
894 UNREACHABLE(
895 "xrpl::MPTEndpointStep::check : prev seen book without a "
896 "prev step");
897 return temBAD_PATH_LOOP;
898 }
899
900 // This is OK if the previous step is a book step that outputs this
901 // issue
902 if (auto book = ctx.prevStep->bookStepBook())
903 {
904 if (book->out.get<MPTIssue>() != mptIssue_)
905 return temBAD_PATH_LOOP;
906 }
907 }
908
909 if ((ctx.isFirst && !ctx.seenDirectAssets[0].insert(mptIssue_).second) ||
910 (ctx.isLast && !ctx.seenDirectAssets[1].insert(mptIssue_).second))
911 {
912 JLOG(j_.debug()) << "MPTEndpointStep: loop detected: Index: " << ctx.strandSize << ' '
913 << *this;
914 return temBAD_PATH_LOOP;
915 }
916
917 // MPT can only be an endpoint
918 if (!ctx.isLast && !ctx.isFirst)
919 {
920 JLOG(j_.warn()) << "MPTEndpointStep: MPT can only be an endpoint";
921 return temBAD_PATH;
922 }
923
924 auto const& issuer = mptIssue_.getIssuer();
925 if ((src_ != issuer && dst_ != issuer) || (src_ == issuer && dst_ == issuer))
926 {
927 JLOG(j_.warn()) << "MPTEndpointStep: invalid src/dst";
928 return temBAD_PATH;
929 }
930
931 return static_cast<TDerived const*>(this)->check(ctx, sleSrc);
932}
933
934template <class TDerived>
935void
940
941//------------------------------------------------------------------------------
942
945 StrandContext const& ctx,
946 AccountID const& src,
947 AccountID const& dst,
948 MPTID const& mpt)
949{
950 TER ter = tefINTERNAL;
953 {
954 auto offerCrossingStep = std::make_unique<MPTEndpointOfferCrossingStep>(ctx, src, dst, mpt);
955 ter = offerCrossingStep->check(ctx);
956 r = std::move(offerCrossingStep);
957 }
958 else // payment
959 {
960 auto paymentStep = std::make_unique<MPTEndpointPaymentStep>(ctx, src, dst, mpt);
961 ter = paymentStep->check(ctx);
962 r = std::move(paymentStep);
963 }
964 if (!isTesSuccess(ter))
965 return {ter, nullptr};
966
967 return {tesSUCCESS, std::move(r)};
968}
969
970namespace test {
971// Needed for testing
972bool
974 Step const& step,
975 AccountID const& src,
976 AccountID const& dst,
977 MPTID const& mptid)
978{
979 if (auto ds = dynamic_cast<MPTEndpointStep<MPTEndpointPaymentStep> const*>(&step))
980 {
981 return ds->src() == src && ds->dst() == dst && ds->mptID() == mptid;
982 }
983 return false;
984}
985} // namespace test
986
987} // 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
constexpr value_type value() const
Returns the underlying value.
Definition MPTAmount.h:135
static bool verifyPrevStepDebtDirection(DebtDirection prevStepDir)
MPTEndpointOfferCrossingStep(StrandContext const &ctx, AccountID const &src, AccountID const &dst, MPTID const &mpt)
std::string logString() const override
static TER check(StrandContext const &ctx, SLE::ConstRef sleSrc)
MPTEndpointPaymentStep(StrandContext const &ctx, AccountID const &src, AccountID const &dst, MPTID const &mpt)
static TER checkCreateMPT(ApplyView &)
TER check(StrandContext const &ctx, SLE::ConstRef sleSrc) const
static bool verifyPrevStepDebtDirection(DebtDirection)
std::string logString() const override
beast::Journal const j_
std::pair< MPTAmount, MPTAmount > fwdImp(PaymentSandbox &sb, ApplyView &afView, boost::container::flat_set< UInt256 > &ofrsToRm, MPTAmount const &in)
std::optional< std::pair< AccountID, AccountID > > directStepAccts() const override
std::pair< std::uint32_t, std::uint32_t > qualitiesSrcRedeems(ReadView const &sb) const
bool equal(Step const &rhs) const override
TER sendWithMPTCreate(ApplyView &view, AccountID const &src, AccountID const &dst, MPTAmount const &amount)
std::pair< MPTAmount, MPTAmount > revImp(PaymentSandbox &sb, ApplyView &afView, boost::container::flat_set< UInt256 > &ofrsToRm, MPTAmount const &out)
std::string logStringImpl(char const *name) const
DebtDirection debtDirection(ReadView const &sb, StrandDirection dir) const override
MPTEndpointStep(StrandContext const &ctx, AccountID const &src, AccountID const &dst, MPTID const &mpt)
std::pair< std::uint32_t, std::uint32_t > qualities(ReadView const &sb, DebtDirection srcDebtDir, StrandDirection strandDir) const
void setCacheLimiting(MPTAmount const &fwdIn, MPTAmount const &fwdSrcToDst, MPTAmount const &fwdOut, DebtDirection srcDebtDir)
std::uint32_t lineQualityIn(ReadView const &v) const override
std::optional< Cache > cache_
std::optional< EitherAmount > cachedOut() const override
TER check(StrandContext const &ctx) const
std::optional< AccountID > directStepSrcAcct() const override
std::pair< MPTAmount, DebtDirection > maxPaymentFlow(ReadView const &sb) const
MPTID const & mptID() const
friend bool operator==(MPTEndpointStep const &lhs, MPTEndpointStep const &rhs)
void resetCache(DebtDirection dir)
std::pair< bool, EitherAmount > validFwd(PaymentSandbox &sb, ApplyView &afView, EitherAmount const &in) override
std::pair< std::uint32_t, std::uint32_t > qualitiesSrcIssues(ReadView const &sb, DebtDirection prevStepDebtDirection) const
std::optional< EitherAmount > cachedIn() const override
friend bool operator!=(MPTEndpointStep const &lhs, MPTEndpointStep const &rhs)
Step const *const prevStep_
std::pair< std::optional< Quality >, DebtDirection > qualityUpperBound(ReadView const &v, DebtDirection dir) const override
Number is a floating point type that can represent a wide range of values.
Definition Number.h:351
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< 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)
T max(T... args)
T min(T... args)
constexpr Zero kZero
Definition Zero.h:30
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:220
Keylet mptokenIssuance(MPTID const &issuanceID) noexcept
Definition Indexes.cpp:567
bool mptEndpointStepEqual(Step const &step, AccountID const &src, AccountID const &dst, MPTID const &mptid)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ terLOCKED
Definition TER.h:232
@ terNO_ACCOUNT
Definition TER.h:218
std::int64_t maxMPTAmount(SLE const &sleIssuance)
TER checkCreateMPT(xrpl::ApplyView &view, xrpl::MPTIssue const &mptIssue, xrpl::AccountID const &holder, SLE::Ref sponsorSle, std::uint32_t flags, beast::Journal j)
bool isIndividualFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue)
Returns true if account's MPToken for mptIssue carries the individual-lock flag (lsfMPTLocked).
MPTAmount toAmount< MPTAmount >(STAmount const &amt)
bool issues(DebtDirection dir)
Definition Steps.h:47
@ tefINTERNAL
Definition TER.h:168
std::pair< TER, std::unique_ptr< Step > > makeMptEndpointStep(StrandContext const &ctx, AccountID const &src, AccountID const &dst, MPTID const &mpt)
TER canTransfer(ReadView const &view, MPTIssue const &mptIssue, AccountID const &from, AccountID const &to, WaiveMPTCanTransfer waive=WaiveMPTCanTransfer::No, std::uint8_t depth=0)
Check whether to may receive the given MPT from from.
DebtDirection
Definition Steps.h:35
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
TER canTrade(ReadView const &view, Asset const &asset, std::uint8_t depth=0)
Check whether asset may be traded on the DEX.
StrandDirection
Definition Steps.h:37
std::optional< MPTAmount > tryMulRatio(MPTAmount const &amt, std::uint32_t num, std::uint32_t den, bool roundUp)
Definition MPTAmount.h:179
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
bool isGlobalFrozen(ReadView const &view, AccountID const &issuer)
Check if the issuer has the global freeze flag set.
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.
BaseUInt< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
Definition UintTypes.h:54
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
bool isFrozen(ReadView const &view, AccountID const &account, MPTIssue const &mptIssue, std::uint8_t depth=0)
Returns true if account cannot send or receive tokens of mptIssue because a freeze applies.
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
bool isTesSuccess(TER x) noexcept
Definition TER.h:683
TERSubset< CanCvtToTER > TER
Definition TER.h:654
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, std::uint8_t depth=0)
Check if the account lacks required authorization for MPT.
@ tecLOCKED
Definition TER.h:366
@ tecPATH_DRY
Definition TER.h:302
bool redeems(DebtDirection dir)
Definition Steps.h:41
constexpr std::uint64_t kMaxMpTokenAmount
The maximum amount of MPTokenIssuance.
Definition Protocol.h:297
@ tesSUCCESS
Definition TER.h:250
STAmount toSTAmount(IOUAmount const &iou, Asset const &asset)
T str(T... args)
bool holds() const
T const & get() const
Cache(MPTAmount const &in, MPTAmount const &srcToDst, MPTAmount const &out, DebtDirection srcDebtDir)
std::uint32_t value
Definition Rate.h:22
Context needed to build Strand Steps and for error checking.
Definition Steps.h:525
Asset const strandDeliver
Asset strand delivers.
Definition Steps.h:529
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
AccountID const strandDst
Strand destination account.
Definition Steps.h:528
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