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