xrpld
Loading...
Searching...
No Matches
BookStep.cpp
1#include <xrpl/basics/Log.h>
2#include <xrpl/basics/base_uint.h>
3#include <xrpl/basics/contract.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/ledger/ApplyView.h>
8#include <xrpl/ledger/PaymentSandbox.h>
9#include <xrpl/ledger/Sandbox.h>
10#include <xrpl/ledger/helpers/AMMHelpers.h>
11#include <xrpl/ledger/helpers/AccountRootHelpers.h>
12#include <xrpl/ledger/helpers/MPTokenHelpers.h>
13#include <xrpl/ledger/helpers/TokenHelpers.h>
14#include <xrpl/protocol/AccountID.h>
15#include <xrpl/protocol/Asset.h>
16#include <xrpl/protocol/Book.h>
17#include <xrpl/protocol/Concepts.h>
18#include <xrpl/protocol/Feature.h>
19#include <xrpl/protocol/IOUAmount.h>
20#include <xrpl/protocol/Indexes.h>
21#include <xrpl/protocol/Issue.h>
22#include <xrpl/protocol/LedgerFormats.h>
23#include <xrpl/protocol/MPTAmount.h>
24#include <xrpl/protocol/MPTIssue.h>
25#include <xrpl/protocol/Quality.h>
26#include <xrpl/protocol/Rate.h>
27#include <xrpl/protocol/Rules.h>
28#include <xrpl/protocol/SField.h>
29#include <xrpl/protocol/STAmount.h>
30#include <xrpl/protocol/TER.h>
31#include <xrpl/protocol/XRPAmount.h>
32#include <xrpl/tx/paths/AMMLiquidity.h>
33#include <xrpl/tx/paths/AMMOffer.h>
34#include <xrpl/tx/paths/BookTip.h>
35#include <xrpl/tx/paths/OfferStream.h>
36#include <xrpl/tx/paths/detail/EitherAmount.h>
37#include <xrpl/tx/paths/detail/FlatSets.h>
38#include <xrpl/tx/paths/detail/Steps.h>
39
40#include <boost/container/flat_set.hpp>
41
42#include <cstdint>
43#include <memory>
44#include <numeric>
45#include <optional>
46#include <sstream>
47#include <string>
48#include <type_traits>
49#include <utility>
50#include <variant>
51
52namespace xrpl {
53
54template <class TIn, class TOut, class TDerived>
55class BookStep : public StepImp<TIn, TOut, BookStep<TIn, TOut, TDerived>>
56{
57protected:
58 enum class OfferType { Amm, Clob };
59
60 static constexpr uint32_t kMaxOffersToConsume{1000};
64 // Charge transfer fees when the prev step redeems
65 Step const* const prevStep_ = nullptr;
67 // Mark as inactive (dry) if too many offers are consumed
68 bool inactive_ = false;
77 // If set, AMM liquidity might be available
78 // if AMM offer quality is better than CLOB offer
79 // quality or there is no CLOB offer.
83
84 struct Cache
85 {
86 TIn in;
87 TOut out;
88
89 Cache(TIn const& in, TOut const& out) : in(in), out(out)
90 {
91 }
92 };
93
95
96private:
97 BookStep(StrandContext const& ctx, Asset const& in, Asset const& out)
98 : book_(in, out, ctx.domainID)
99 , strandSrc_(ctx.strandSrc)
100 , strandDst_(ctx.strandDst)
101 , prevStep_(ctx.prevStep)
102 , ownerPaysTransferFee_(ctx.ownerPaysTransferFee)
103 , j_(ctx.j)
104 , strandDeliver_(ctx.strandDeliver)
105 {
106 if (auto const ammSle = ctx.view.read(keylet::amm(in, out));
107 ammSle && ammSle->getFieldAmount(sfLPTokenBalance) != beast::kZero)
108 {
109 ammLiquidity_.emplace(
110 ctx.view,
111 (*ammSle)[sfAccount],
112 getTradingFee(ctx.view, *ammSle, ctx.ammContext.account()),
113 in,
114 out,
115 ctx.ammContext,
116 ctx.j);
117 }
118 }
119
120public:
121 [[nodiscard]] Book const&
122 book() const
123 {
124 return book_;
125 }
126
127 [[nodiscard]] std::optional<EitherAmount>
128 cachedIn() const override
129 {
130 if (!cache_)
131 return std::nullopt;
132 return EitherAmount(cache_->in);
133 }
134
135 [[nodiscard]] std::optional<EitherAmount>
136 cachedOut() const override
137 {
138 if (!cache_)
139 return std::nullopt;
140 return EitherAmount(cache_->out);
141 }
142
143 [[nodiscard]] DebtDirection
148
149 [[nodiscard]] std::optional<Book>
150 bookStepBook() const override
151 {
152 return book_;
153 }
154
156 qualityUpperBound(ReadView const& v, DebtDirection prevStepDir) const override;
157
159 getQualityFunc(ReadView const& v, DebtDirection prevStepDir) const override;
160
161 [[nodiscard]] std::uint32_t
162 offersUsed() const override;
163
166 PaymentSandbox& sb,
167 ApplyView& afView,
168 boost::container::flat_set<uint256>& ofrsToRm,
169 TOut const& out);
170
173 PaymentSandbox& sb,
174 ApplyView& afView,
175 boost::container::flat_set<uint256>& ofrsToRm,
176 TIn const& in);
177
179 validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in) override;
180
181 // Check for errors frozen constraints.
182 [[nodiscard]] TER
183 check(StrandContext const& ctx) const;
184
185 [[nodiscard]] bool
186 inactive() const override
187 {
188 return inactive_;
189 }
190
191protected:
193 logStringImpl(char const* name) const
194 {
196 ostr << name << ": "
197 << "\ninIss: " << book_.in.getIssuer() << "\noutIss: " << book_.out.getIssuer()
198 << "\ninCur: " << to_string(book_.in) << "\noutCur: " << to_string(book_.out);
199 return ostr.str();
200 }
201
202 [[nodiscard]] Rate
203 rate(ReadView const& view, Asset const& asset, AccountID const& dstAccount) const;
204
205private:
206 friend bool
207 operator==(BookStep const& lhs, BookStep const& rhs)
208 {
209 return lhs.book_ == rhs.book_;
210 }
211
212 friend bool
213 operator!=(BookStep const& lhs, BookStep const& rhs)
214 {
215 return !(lhs == rhs);
216 }
217
218 [[nodiscard]] bool
219 equal(Step const& rhs) const override;
220
221 // Iterate through the offers at the best quality in a book.
222 // Unfunded offers and bad offers are skipped (and returned).
223 // callback is called with the offer SLE, taker pays, taker gets.
224 // If callback returns false, don't process any more offers.
225 // Return the unfunded, bad offers and the number of offers consumed.
226 template <class Callback>
229 PaymentSandbox& sb,
230 ApplyView& afView,
231 DebtDirection prevStepDebtDir,
232 Callback& callback) const;
233
234 // Offer is either TOffer or AMMOffer
235 template <template <typename, typename> typename Offer>
236 void
238 PaymentSandbox& sb,
239 Offer<TIn, TOut>& offer,
240 TAmounts<TIn, TOut> const& ofrAmt,
241 TAmounts<TIn, TOut> const& stepAmt,
242 TOut const& ownerGives) const;
243
244 // If clobQuality is available and has a better quality then return nullopt,
245 // otherwise if amm liquidity is available return AMM offer adjusted based
246 // on clobQuality.
248 getAMMOffer(ReadView const& view, std::optional<Quality> const& clobQuality) const;
249
250 // If seated then it is either order book tip quality or AMMOffer,
251 // whichever is a better quality.
253 tip(ReadView const& view) const;
254 // If seated then it is either AMM or CLOB quality,
255 // whichever is a better quality. OfferType is AMM
256 // if AMM quality is better.
258 tipOfferQuality(ReadView const& view) const;
259 // If seated then it is either AMM or CLOB quality function,
260 // whichever is a better quality.
262 tipOfferQualityF(ReadView const& view) const;
263
264 // Check that takerPays/takerGets can be transferred/traded.
265 // Applies to MPT assets.
266 [[nodiscard]] bool
267 checkMPTDEX(ReadView const& view, AccountID const& owner) const;
268
269 friend TDerived;
270};
271
272//------------------------------------------------------------------------------
273
274// Flow is used in two different circumstances for transferring funds:
275// o Payments, and
276// o Offer crossing.
277// The rules for handling funds in these two cases are almost, but not
278// quite, the same.
279
280// Payment BookStep template class (not offer crossing).
281template <class TIn, class TOut>
282class BookPaymentStep : public BookStep<TIn, TOut, BookPaymentStep<TIn, TOut>>
283{
284public:
285 explicit BookPaymentStep() = default;
286
287 BookPaymentStep(StrandContext const& ctx, Asset const& in, Asset const& out)
288 : BookStep<TIn, TOut, BookPaymentStep<TIn, TOut>>(ctx, in, out)
289 {
290 }
291
294
295 // Never limit self cross quality on a payment.
296 template <template <typename, typename> typename Offer>
297 bool
299 AccountID const&,
300 AccountID const&,
301 Offer<TIn, TOut> const& offer,
304 bool) const
305 {
306 return false;
307 }
308
309 // A payment can look at offers of any quality
310 [[nodiscard]] bool
311 checkQualityThreshold(Quality const& quality) const
312 {
313 return true;
314 }
315
316 // A payment doesn't use quality threshold (limitQuality)
317 // since the strand's quality doesn't directly relate to the step's quality.
318 [[nodiscard]] std::optional<Quality>
319 qualityThreshold(Quality const& lobQuality) const
320 {
321 return lobQuality;
322 }
323
324 // For a payment ofrInRate is always the same as trIn.
326 getOfrInRate(Step const*, AccountID const&, std::uint32_t trIn) const
327 {
328 return trIn;
329 }
330
331 // For a payment ofrOutRate is always the same as trOut.
333 getOfrOutRate(Step const*, AccountID const&, AccountID const&, std::uint32_t trOut) const
334 {
335 return trOut;
336 }
337
338 [[nodiscard]] Quality
340 ReadView const& v,
341 Quality const& ofrQ,
342 DebtDirection prevStepDir,
343 WaiveTransferFee waiveFee,
344 OfferType,
345 Rules const&) const
346 {
347 // Charge the offer owner, not the sender
348 // Charge a fee even if the owner is the same as the issuer
349 // (the old code does not charge a fee)
350 // Calculate amount that goes to the taker and the amount charged the
351 // offer owner
352 auto const trIn =
353 redeems(prevStepDir) ? this->rate(v, this->book_.in, this->strandDst_) : kParityRate;
354 // Always charge the transfer fee, even if the owner is the issuer,
355 // unless the fee is waived
356 auto const trOut = (this->ownerPaysTransferFee_ && waiveFee == WaiveTransferFee::No)
357 ? this->rate(v, this->book_.out, this->strandDst_)
358 : kParityRate;
359
360 Quality const q1{getRate(STAmount(trOut.value), STAmount(trIn.value))};
361 return composedQuality(q1, ofrQ);
362 }
363
364 [[nodiscard]] std::string
365 logString() const override
366 {
367 return this->logStringImpl("BookPaymentStep");
368 }
369};
370
371// Offer crossing BookStep template class (not a payment).
372template <class TIn, class TOut>
373class BookOfferCrossingStep : public BookStep<TIn, TOut, BookOfferCrossingStep<TIn, TOut>>
374{
377
378private:
379 // Helper function that throws if the optional passed to the constructor
380 // is none.
381 static Quality
383 {
384 // It's really a programming error if the quality is missing.
385 XRPL_ASSERT(limitQuality, "xrpl::BookOfferCrossingStep::getQuality : nonzero quality");
386 if (!limitQuality)
387 Throw<FlowException>(tefINTERNAL, "Offer requires quality.");
388 return *limitQuality;
389 }
390
391public:
392 BookOfferCrossingStep(StrandContext const& ctx, Asset const& in, Asset const& out)
393 : BookStep<TIn, TOut, BookOfferCrossingStep<TIn, TOut>>(ctx, in, out)
395 , qualityThreshold_(getQuality(ctx.limitQuality))
396 {
397 }
398
399 template <template <typename, typename> typename Offer>
400 bool
402 AccountID const& strandSrc,
403 AccountID const& strandDst,
404 Offer<TIn, TOut> const& offer,
407 bool const offerAttempted) const
408 {
409 // This method supports some correct but slightly surprising
410 // behavior in offer crossing. The scenario:
411 //
412 // o alice has already created one or more offers.
413 // o alice creates another offer that can be directly crossed (not
414 // autobridged) by one or more of her previously created offer(s).
415 //
416 // What does the offer crossing do?
417 //
418 // o The offer crossing could go ahead and cross the offers leaving
419 // either one reduced offer (partial crossing) or zero offers
420 // (exact crossing) in the ledger. We don't do this. And, really,
421 // the offer creator probably didn't want us to.
422 //
423 // o We could skip over the self offer in the book and only cross
424 // offers that are not our own. This would make a lot of sense,
425 // but we don't do it. Part of the rationale is that we can only
426 // operate on the tip of the order book. We can't leave an offer
427 // behind -- it would sit on the tip and block access to other
428 // offers.
429 //
430 // o We could delete the self-crossable offer(s) off the tip of the
431 // book and continue with offer crossing. That's what we do.
432 //
433 // To support this scenario offer crossing has a special rule. If:
434 // a. We're offer crossing using default path (no autobridging), and
435 // b. The offer's quality is at least as good as our quality, and
436 // c. We're about to cross one of our own offers, then
437 // d. Delete the old offer from the ledger.
438 if (defaultPath_ && offer.quality() >= qualityThreshold_ && strandSrc == offer.owner() &&
439 strandDst == offer.owner())
440 {
441 // Remove this offer even if no crossing occurs.
442 if (auto const key = offer.key())
443 offers.permRmOffer(*key);
444
445 // If no offers have been attempted yet then it's okay to move to
446 // a different quality.
447 if (!offerAttempted)
448 ofrQ = std::nullopt;
449
450 // Return true so the current offer will be deleted.
451 return true;
452 }
453 return false;
454 }
455
456 // Offer crossing can prune the offers it needs to look at with a
457 // quality threshold.
458 [[nodiscard]] bool
459 checkQualityThreshold(Quality const& quality) const
460 {
461 return !defaultPath_ || quality >= qualityThreshold_;
462 }
463
464 // Return quality threshold or nullopt to use when generating AMM offer.
465 // AMM synthetic offer is generated to match LOB offer quality.
466 // If LOB tip offer quality is less than qualityThreshold
467 // then generated AMM offer quality is also less than qualityThreshold and
468 // the offer is not crossed even though AMM might generate a better quality
469 // offer. To address this, if qualityThreshold is greater than lobQuality
470 // then don't use quality to generate the AMM offer. The limit out value
471 // generates the maximum AMM offer in this case, which matches
472 // the quality threshold. This only applies to single path scenario.
473 // Multi-path AMM offers work the same as LOB offers.
474 [[nodiscard]] std::optional<Quality>
475 qualityThreshold(Quality const& lobQuality) const
476 {
477 if (this->ammLiquidity_ && !this->ammLiquidity_->multiPath() &&
478 qualityThreshold_ > lobQuality)
479 return std::nullopt;
480 return lobQuality;
481 }
482
483 // For offer crossing don't pay the transfer fee if alice is paying alice.
484 // A regular (non-offer-crossing) payment does not apply this rule.
486 getOfrInRate(Step const* prevStep, AccountID const& owner, std::uint32_t trIn) const
487 {
488 auto const srcAcct = (prevStep != nullptr) ? prevStep->directStepSrcAcct() : std::nullopt;
489
490 return owner == srcAcct // If offer crossing && prevStep is DirectI
491 ? QUALITY_ONE // or MPTEndpoint && src is offer owner
492 : trIn; // then rate = QUALITY_ONE
493 }
494
495 // See comment on getOfrInRate().
498 Step const* prevStep,
499 AccountID const& owner,
500 AccountID const& strandDst,
501 std::uint32_t trOut) const
502 {
503 return // If offer crossing
504 (prevStep != nullptr) && prevStep->bookStepBook() && // && prevStep is BookStep
505 owner == strandDst // && dest is offer owner
506 ? QUALITY_ONE
507 : trOut; // then rate = QUALITY_ONE
508 }
509
510 [[nodiscard]] Quality
512 ReadView const& v,
513 Quality const& ofrQ,
514 DebtDirection prevStepDir,
515 WaiveTransferFee waiveFee,
516 OfferType offerType,
517 Rules const& rules) const
518 {
519 // Offer x-ing does not charge a transfer fee when the offer's owner
520 // is the same as the strand dst. It is important that
521 // `qualityUpperBound` is an upper bound on the quality (it is used to
522 // ignore strands whose quality cannot meet a minimum threshold). When
523 // calculating quality assume no fee is charged, or the estimate will no
524 // longer be an upper bound.
525
526 // Single path AMM offer has to factor in the transfer in rate
527 // when calculating the upper bound quality and the quality function
528 // because single path AMM's offer quality is not constant.
529 if (!rules.enabled(fixAMMv1_1))
530 {
531 return ofrQ;
532 }
533 if (offerType == OfferType::Clob ||
534 (this->ammLiquidity_ && this->ammLiquidity_->multiPath()))
535 {
536 return ofrQ;
537 }
538
539 auto const trIn =
540 redeems(prevStepDir) ? this->rate(v, this->book_.in, this->strandDst_) : kParityRate;
541 // AMM doesn't pay the transfer fee on the out amount
542 auto const trOut = kParityRate;
543
544 Quality const q1{getRate(STAmount(trOut.value), STAmount(trIn.value))};
545 return composedQuality(q1, ofrQ);
546 }
547
548 [[nodiscard]] std::string
549 logString() const override
550 {
551 return this->logStringImpl("BookOfferCrossingStep");
552 }
553
554private:
555 bool const defaultPath_;
557};
558
559//------------------------------------------------------------------------------
560
561template <class TIn, class TOut, class TDerived>
562bool
564{
565 if (auto bs = dynamic_cast<BookStep<TIn, TOut, TDerived> const*>(&rhs))
566 return book_ == bs->book_;
567 return false;
568}
569
570template <class TIn, class TOut, class TDerived>
573{
574 auto const dir = this->debtDirection(v, StrandDirection::Forward);
575
577 if (!res)
578 return {std::nullopt, dir};
579
580 auto const waiveFee = (std::get<OfferType>(*res) == OfferType::Amm) ? WaiveTransferFee::Yes
582
583 Quality const q = static_cast<TDerived const*>(this)->adjustQualityWithFees(
584 v, std::get<Quality>(*res), prevStepDir, waiveFee, std::get<OfferType>(*res), v.rules());
585 return {q, dir};
586}
587
588template <class TIn, class TOut, class TDerived>
591{
592 auto const dir = this->debtDirection(v, StrandDirection::Forward);
593
595 if (!res)
596 return {std::nullopt, dir};
597
598 // AMM
599 if (!res->isConst())
600 {
601 auto static const kQOne = Quality{STAmount::kURateOne};
602 auto const q = static_cast<TDerived const*>(this)->adjustQualityWithFees(
603 v, kQOne, prevStepDir, WaiveTransferFee::Yes, OfferType::Amm, v.rules());
604 if (q == kQOne)
605 return {res, dir};
607 qf.combine(*res);
608 return {qf, dir};
609 }
610
611 // CLOB
612 Quality const q = static_cast<TDerived const*>(this)->adjustQualityWithFees(
613 v,
614 *(res->quality()), // NOLINT(bugprone-unchecked-optional-access) CLOB QualityFunction
615 // always has quality set
616 prevStepDir,
619 v.rules());
621}
622
623template <class TIn, class TOut, class TDerived>
629
630// Adjust the offer amount and step amount subject to the given input limit
631template <class TIn, class TOut, class Offer>
632static void
634 Offer const& offer,
635 TAmounts<TIn, TOut>& ofrAmt,
636 TAmounts<TIn, TOut>& stpAmt,
637 TOut& ownerGives,
638 std::uint32_t transferRateIn,
639 std::uint32_t transferRateOut,
640 TIn const& limit)
641{
642 if (limit < stpAmt.in)
643 {
644 stpAmt.in = limit;
645 auto const inLmt = mulRatio(stpAmt.in, QUALITY_ONE, transferRateIn, /*roundUp*/ false);
646 // It turns out we can prevent order book blocking by (strictly)
647 // rounding down the ceil_in() result. By rounding down we guarantee
648 // that the quality of an offer left in the ledger is as good or
649 // better than the quality of the containing order book page.
650 //
651 // This adjustment changes transaction outcomes, so it must be made
652 // under an amendment.
653 ofrAmt = offer.limitIn(ofrAmt, inLmt, /* roundUp */ false);
654 stpAmt.out = ofrAmt.out;
655 ownerGives = mulRatio(ofrAmt.out, transferRateOut, QUALITY_ONE, /*roundUp*/ false);
656 }
657}
658
659// Adjust the offer amount and step amount subject to the given output limit
660template <class TIn, class TOut, class Offer>
661static void
663 Offer const& offer,
664 TAmounts<TIn, TOut>& ofrAmt,
665 TAmounts<TIn, TOut>& stpAmt,
666 TOut& ownerGives,
667 std::uint32_t transferRateIn,
668 std::uint32_t transferRateOut,
669 TOut const& limit)
670{
671 if (limit < stpAmt.out)
672 {
673 stpAmt.out = limit;
674 ownerGives = mulRatio(stpAmt.out, transferRateOut, QUALITY_ONE, /*roundUp*/ false);
675 ofrAmt = offer.limitOut(
676 ofrAmt,
677 stpAmt.out,
678 /*roundUp*/ true);
679 stpAmt.in = mulRatio(ofrAmt.in, transferRateIn, QUALITY_ONE, /*roundUp*/ true);
680 }
681}
682
683template <class TIn, class TOut, class TDerived>
684template <class Callback>
687 PaymentSandbox& sb,
688 ApplyView& afView,
689 DebtDirection prevStepDir,
690 Callback& callback) const
691{
692 // Charge the offer owner, not the sender
693 // Charge a fee even if the owner is the same as the issuer
694 // (the old code does not charge a fee)
695 // Calculate amount that goes to the taker and the amount charged the offer
696 // owner
697 std::uint32_t const trIn =
698 redeems(prevStepDir) ? rate(sb, book_.in, this->strandDst_).value : QUALITY_ONE;
699 // Always charge the transfer fee, even if the owner is the issuer
700 std::uint32_t const trOut =
701 ownerPaysTransferFee_ ? rate(sb, book_.out, this->strandDst_).value : QUALITY_ONE;
702
704
705 FlowOfferStream<TIn, TOut> offers(sb, afView, book_, sb.parentCloseTime(), counter, j_);
706
707 bool offerAttempted = false;
709 auto execOffer = [&](auto& offer) {
710 // Note that offer.quality() returns a (non-optional) Quality. So
711 // ofrQ is always safe to use below this point in the lambda.
712 if (!ofrQ)
713 {
714 ofrQ = offer.quality();
715 }
716 else if (*ofrQ != offer.quality())
717 {
718 return false;
719 }
720
721 if (static_cast<TDerived const*>(this)->limitSelfCrossQuality(
722 strandSrc_, strandDst_, offer, ofrQ, offers, offerAttempted))
723 return true;
724
725 Asset const& assetIn = offer.assetIn();
726 bool const isAssetInMPT = assetIn.holds<MPTIssue>();
727 auto const& owner = offer.owner();
728
729 if (isAssetInMPT)
730 {
731 // Create MPToken for the offer's owner. No need to check
732 // for the reserve since the offer is removed if it is consumed.
733 // Therefore, the owner count remains the same.
734 if (auto const err = checkCreateMPT(sb, assetIn.get<MPTIssue>(), owner, j_);
735 !isTesSuccess(err))
736 {
737 return true;
738 }
739 }
740
741 // It shouldn't matter from auth point of view whether it's sb
742 // or afView. Amendment guard this change just in case.
743 auto& applyView = sb.rules().enabled(featureMPTokensV2) ? sb : afView;
744 // Make sure offer owner has authorization to own Assets from issuer
745 // and MPT assets can be traded/transferred.
746 // An account can always own XRP or their own Assets.
747 if (!isTesSuccess(requireAuth(applyView, assetIn, owner)) || !checkMPTDEX(sb, owner))
748 {
749 // Offer owner not authorized to hold IOU/MPT from issuer.
750 // Remove this offer even if no crossing occurs.
751 if (auto const key = offer.key())
752 offers.permRmOffer(*key);
753 if (!offerAttempted)
754 {
755 // Change quality only if no previous offers were tried.
756 ofrQ = std::nullopt;
757 }
758 // Returning true causes offers.step() to delete the offer.
759 return true;
760 }
761
762 if (!static_cast<TDerived const*>(this)->checkQualityThreshold(offer.quality()))
763 return false;
764
765 auto const [ofrInRate, ofrOutRate] = offer.adjustRates(
766 static_cast<TDerived const*>(this)->getOfrInRate(prevStep_, owner, trIn),
767 static_cast<TDerived const*>(this)->getOfrOutRate(prevStep_, owner, strandDst_, trOut));
768
769 auto ofrAmt = offer.amount();
770 TAmounts stpAmt{mulRatio(ofrAmt.in, ofrInRate, QUALITY_ONE, /*roundUp*/ true), ofrAmt.out};
771
772 // owner pays the transfer fee.
773 auto ownerGives = mulRatio(ofrAmt.out, ofrOutRate, QUALITY_ONE, /*roundUp*/ false);
774
775 auto const funds = offer.isFunded()
776 ? ownerGives // Offer owner is issuer; they have unlimited funds
777 : offers.ownerFunds();
778
779 // Only if CLOB offer
780 if (funds < ownerGives)
781 {
782 // We already know offer.owner()!=offer.issueOut().account
783 ownerGives = funds;
784 stpAmt.out = mulRatio(ownerGives, QUALITY_ONE, ofrOutRate, /*roundUp*/ false);
785
786 // It turns out we can prevent order book blocking by (strictly)
787 // rounding down the ceil_out() result. This adjustment changes
788 // transaction outcomes, so it must be made under an amendment.
789 ofrAmt = offer.limitOut(ofrAmt, stpAmt.out, /*roundUp*/ false);
790
791 stpAmt.in = mulRatio(ofrAmt.in, ofrInRate, QUALITY_ONE, /*roundUp*/ true);
792 }
793
794 // Limit offer's input if MPT, BookStep is the first step (an issuer
795 // is making a cross-currency payment), and this offer is not owned
796 // by the issuer. Otherwise, OutstandingAmount may overflow.
797 auto const& issuer = assetIn.getIssuer();
798 if (isAssetInMPT && !prevStep_ && offer.owner() != issuer)
799 {
800 // Funds available to issue
801 auto const available = toAmount<TIn>(accountFunds(
802 sb,
803 issuer,
804 assetIn, // STAmount{0}, but the default is not used
807 j_));
808 if (stpAmt.in > available)
809 {
810 limitStepIn(offer, ofrAmt, stpAmt, ownerGives, ofrInRate, ofrOutRate, available);
811 }
812 }
813
814 offerAttempted = true;
815 return callback(offer, ofrAmt, stpAmt, ownerGives, ofrInRate, ofrOutRate);
816 };
817
818 // At any payment engine iteration, AMM offer can only be consumed once.
819 auto tryAMM = [&](std::optional<Quality> const& lobQuality) -> bool {
820 // amm doesn't support domain yet
821 if (book_.domain)
822 return true;
823
824 // If offer crossing then use either LOB quality or nullopt
825 // to prevent AMM being blocked by a lower quality LOB.
826 auto const qualityThreshold = [&]() -> std::optional<Quality> {
827 if (sb.rules().enabled(fixAMMv1_1) && lobQuality)
828 return static_cast<TDerived const*>(this)->qualityThreshold(*lobQuality);
829 return lobQuality;
830 }();
831 auto ammOffer = getAMMOffer(sb, qualityThreshold);
832 return !ammOffer || execOffer(*ammOffer);
833 };
834
835 if (offers.step())
836 {
837 if (tryAMM(offers.tip().quality()))
838 {
839 do
840 {
841 if (!execOffer(offers.tip()))
842 break;
843 } while (offers.step());
844 }
845 }
846 else
847 {
848 // Might have AMM offer if there are no LOB offers.
849 tryAMM(std::nullopt);
850 }
851
852 return {offers.permToRemove(), counter.count()};
853}
854
855template <class TIn, class TOut, class TDerived>
856template <template <typename, typename> typename Offer>
857void
859 PaymentSandbox& sb,
860 Offer<TIn, TOut>& offer,
861 TAmounts<TIn, TOut> const& ofrAmt,
862 TAmounts<TIn, TOut> const& stepAmt,
863 TOut const& ownerGives) const
864{
865 if (!offer.checkInvariant(ofrAmt, j_))
866 {
867 // purposely written as separate if statements so we get logging even
868 // when the amendment isn't active.
869 if (sb.rules().enabled(fixAMMOverflowOffer))
870 {
871 Throw<FlowException>(tecINVARIANT_FAILED, "AMM pool product invariant failed.");
872 }
873 }
874
875 // The offer owner gets the ofrAmt. The difference between ofrAmt and
876 // stepAmt is a transfer fee that goes to book_.in.account
877 {
878 auto const dr = offer.send(
879 sb, book_.in.getIssuer(), offer.owner(), toSTAmount(ofrAmt.in, book_.in), j_);
880 if (!isTesSuccess(dr))
882 }
883
884 // The offer owner pays `ownerGives`. The difference between ownerGives and
885 // stepAmt is a transfer fee that goes to book_.out.account
886 {
887 auto const& issuer = book_.out.getIssuer();
888 auto const cr =
889 offer.send(sb, offer.owner(), issuer, toSTAmount(ownerGives, book_.out), j_);
890 if (!isTesSuccess(cr))
893 {
894 if (offer.owner() == issuer)
895 issuerSelfDebitHookMPT(sb, book_.out.get<MPTIssue>(), ofrAmt.out.value());
896 }
897 }
898
899 offer.consume(sb, ofrAmt);
900}
901
902template <class TIn, class TOut, class TDerived>
905 ReadView const& view,
906 std::optional<Quality> const& clobQuality) const
907{
908 // AMM doesn't support domain books. When fixCleanup3_3_0 is enabled, exclude
909 // AMM liquidity so quality estimation matches actual crossing (tryAMM skips
910 // AMM for domain books).
911 if (book_.domain && view.rules().enabled(fixCleanup3_3_0))
912 return std::nullopt;
913 if (ammLiquidity_)
914 return ammLiquidity_->getOffer(view, clobQuality);
915 return std::nullopt;
916}
917
918template <class TIn, class TOut, class TDerived>
921{
922 // This can be simplified (and sped up) if directories are never empty.
923 Sandbox sb(&view, TapNone);
924 BookTip bt(sb, book_);
925 auto const lobQuality = bt.step(j_) ? std::optional<Quality>(bt.quality()) : std::nullopt;
926 // Multi-path offer generates an offer with the quality
927 // calculated from the offer size and the quality is constant in this case.
928 // Single path offer quality changes with the offer size. Spot price quality
929 // (SPQ) can't be used in this case as the upper bound quality because
930 // even if SPQ quality is better than LOB quality, it might not be possible
931 // to generate AMM offer at or better quality than LOB quality. Another
932 // factor to consider is limit quality on offer crossing. If LOB quality
933 // is greater than limit quality then use LOB quality when generating AMM
934 // offer, otherwise don't use quality threshold when generating AMM offer.
935 // AMM or LOB offer, whether multi-path or single path then can be selected
936 // based on the best offer quality. Using the quality to generate AMM offer
937 // in this case also prevents the payment engine from going into multiple
938 // iterations to cross a LOB offer. This happens when AMM changes
939 // the out amount at the start of iteration to match the limitQuality
940 // on offer crossing but AMM can't generate the offer at this quality,
941 // as the result a LOB offer is partially crossed, and it might take a few
942 // iterations to fully cross the offer.
943 auto const qualityThreshold = [&]() -> std::optional<Quality> {
944 if (view.rules().enabled(fixAMMv1_1) && lobQuality)
945 return static_cast<TDerived const*>(this)->qualityThreshold(*lobQuality);
946 return std::nullopt;
947 }();
948 // AMM quality is better or no LOB offer
949 if (auto const ammOffer = getAMMOffer(view, qualityThreshold);
950 ammOffer && ((lobQuality && ammOffer->quality() > lobQuality) || !lobQuality))
951 return ammOffer;
952 // LOB quality is better or nullopt
953 return lobQuality;
954}
955
956template <class TIn, class TOut, class TDerived>
957auto
960{
961 auto const res = tip(view);
962 if (!res)
963 {
964 return std::nullopt;
965 }
966 if (auto const q = std::get_if<Quality>(&(*res)))
967 {
969 }
970
971 return std::make_pair(std::get<AMMOffer<TIn, TOut>>(*res).quality(), OfferType::Amm);
972}
973
974template <class TIn, class TOut, class TDerived>
977{
978 auto const res = tip(view);
979 if (!res)
980 {
981 return std::nullopt;
982 }
983 if (auto const q = std::get_if<Quality>(&(*res)))
984 {
986 }
987
988 return std::get<AMMOffer<TIn, TOut>>(*res).getQualityFunc();
989}
990
991template <class TCollection>
992static auto
993sum(TCollection const& col)
994{
995 using TResult = std::decay_t<decltype(*col.begin())>;
996 if (col.empty())
997 return TResult{beast::kZero};
998 return std::accumulate(col.begin() + 1, col.end(), *col.begin());
999};
1000
1001template <class TIn, class TOut, class TDerived>
1004 PaymentSandbox& sb,
1005 ApplyView& afView,
1006 boost::container::flat_set<uint256>& ofrsToRm,
1007 TOut const& out)
1008{
1009 cache_.reset();
1010
1011 TAmounts<TIn, TOut> result(beast::kZero, beast::kZero);
1012
1013 auto remainingOut = out;
1014
1015 boost::container::flat_multiset<TIn> savedIns;
1016 savedIns.reserve(64);
1017 boost::container::flat_multiset<TOut> savedOuts;
1018 savedOuts.reserve(64);
1019
1020 /* amt fed will be adjusted by owner funds (and may differ from the offer's
1021 amounts - tho always <=)
1022 Return true to continue to receive offers, false to stop receiving offers.
1023 */
1024 auto eachOffer = [&](auto& offer,
1025 TAmounts<TIn, TOut> const& ofrAmt,
1026 TAmounts<TIn, TOut> const& stpAmt,
1027 TOut const& ownerGives,
1028 std::uint32_t transferRateIn,
1029 std::uint32_t transferRateOut) mutable -> bool {
1030 if (remainingOut <= beast::kZero)
1031 return false;
1032
1033 if (stpAmt.out <= remainingOut)
1034 {
1035 savedIns.insert(stpAmt.in);
1036 savedOuts.insert(stpAmt.out);
1037 result = TAmounts<TIn, TOut>(sum(savedIns), sum(savedOuts));
1038 remainingOut = out - result.out;
1039 this->consumeOffer(sb, offer, ofrAmt, stpAmt, ownerGives);
1040 // return true b/c even if the payment is satisfied,
1041 // we need to consume the offer
1042 return true;
1043 }
1044
1045 auto ofrAdjAmt = ofrAmt;
1046 auto stpAdjAmt = stpAmt;
1047 auto ownerGivesAdj = ownerGives;
1049 offer,
1050 ofrAdjAmt,
1051 stpAdjAmt,
1052 ownerGivesAdj,
1053 transferRateIn,
1054 transferRateOut,
1055 remainingOut);
1056 remainingOut = beast::kZero;
1057 savedIns.insert(stpAdjAmt.in);
1058 savedOuts.insert(remainingOut);
1059 result.in = sum(savedIns);
1060 result.out = out;
1061 this->consumeOffer(sb, offer, ofrAdjAmt, stpAdjAmt, ownerGivesAdj);
1062
1063 // Explicitly check whether the offer is funded. Given that we have
1064 // (stpAmt.out > remainingOut), it's natural to assume the offer
1065 // will still be funded after consuming remainingOut but that is
1066 // not always the case. If the mantissas of two IOU amounts differ
1067 // by less than ten, then subtracting them leaves a zero.
1068 return offer.fullyConsumed();
1069 };
1070
1071 {
1072 auto const prevStepDebtDir = [&] {
1073 if (prevStep_)
1074 return prevStep_->debtDirection(sb, StrandDirection::Reverse);
1075 return DebtDirection::Issues;
1076 }();
1077 auto const r = forEachOffer(sb, afView, prevStepDebtDir, eachOffer);
1078 boost::container::flat_set<uint256> const toRm = std::move(std::get<0>(r));
1079 std::uint32_t const offersConsumed = std::get<1>(r);
1080 offersUsed_ = offersConsumed;
1081 setUnion(ofrsToRm, toRm);
1082
1083 // Too many iterations, mark this strand as inactive
1084 if (offersConsumed >= kMaxOffersToConsume)
1085 {
1086 inactive_ = true;
1087 }
1088 }
1089
1090 switch (remainingOut.signum())
1091 {
1092 case -1: {
1093 // something went very wrong
1094 // LCOV_EXCL_START
1095 JLOG(j_.error()) << "BookStep remainingOut < 0 " << to_string(remainingOut);
1096 UNREACHABLE("xrpl::BookStep::revImp : remaining less than zero");
1097 cache_.emplace(beast::kZero, beast::kZero);
1098 return {beast::kZero, beast::kZero};
1099 // LCOV_EXCL_STOP
1100 }
1101 case 0: {
1102 // due to normalization, remainingOut can be zero without
1103 // result.out == out. Force result.out == out for this case
1104 result.out = out;
1105 }
1106 }
1107
1108 cache_.emplace(result.in, result.out);
1109 return {result.in, result.out};
1110}
1111
1112template <class TIn, class TOut, class TDerived>
1115 PaymentSandbox& sb,
1116 ApplyView& afView,
1117 boost::container::flat_set<uint256>& ofrsToRm,
1118 TIn const& in)
1119{
1120 XRPL_ASSERT(cache_, "xrpl::BookStep::fwdImp : cache is set");
1121
1122 TAmounts<TIn, TOut> result(beast::kZero, beast::kZero);
1123
1124 auto remainingIn = in;
1125
1126 boost::container::flat_multiset<TIn> savedIns;
1127 savedIns.reserve(64);
1128 boost::container::flat_multiset<TOut> savedOuts;
1129 savedOuts.reserve(64);
1130
1131 // amt fed will be adjusted by owner funds (and may differ from the offer's
1132 // amounts - tho always <=)
1133 auto eachOffer = [&](auto& offer,
1134 TAmounts<TIn, TOut> const& ofrAmt,
1135 TAmounts<TIn, TOut> const& stpAmt,
1136 TOut const& ownerGives,
1137 std::uint32_t transferRateIn,
1138 std::uint32_t transferRateOut) mutable -> bool {
1139 XRPL_ASSERT(cache_, "xrpl::BookStep::fwdImp::eachOffer : cache is set");
1140
1141 if (remainingIn <= beast::kZero)
1142 return false;
1143
1144 bool processMore = true;
1145 auto ofrAdjAmt = ofrAmt;
1146 auto stpAdjAmt = stpAmt;
1147 auto ownerGivesAdj = ownerGives;
1148
1149 typename boost::container::flat_multiset<TOut>::const_iterator lastOut;
1150 if (stpAmt.in <= remainingIn)
1151 {
1152 savedIns.insert(stpAmt.in);
1153 lastOut = savedOuts.insert(stpAmt.out);
1154 result = TAmounts<TIn, TOut>(sum(savedIns), sum(savedOuts));
1155 // consume the offer even if stepAmt.in == remainingIn
1156 processMore = true;
1157 }
1158 else
1159 {
1161 offer,
1162 ofrAdjAmt,
1163 stpAdjAmt,
1164 ownerGivesAdj,
1165 transferRateIn,
1166 transferRateOut,
1167 remainingIn);
1168 savedIns.insert(remainingIn);
1169 lastOut = savedOuts.insert(stpAdjAmt.out);
1170 result.out = sum(savedOuts);
1171 result.in = in;
1172
1173 processMore = false;
1174 }
1175
1176 if (result.out > cache_->out && result.in <= cache_->in)
1177 {
1178 // The step produced more output in the forward pass than the
1179 // reverse pass while consuming the same input (or less). If we
1180 // compute the input required to produce the cached output
1181 // (produced in the reverse step) and the input is equal to
1182 // the input consumed in the forward step, then consume the
1183 // input provided in the forward step and produce the output
1184 // requested from the reverse step.
1185 auto const lastOutAmt = *lastOut;
1186 savedOuts.erase(lastOut);
1187 auto const remainingOut = cache_->out - sum(savedOuts);
1188 auto ofrAdjAmtRev = ofrAmt;
1189 auto stpAdjAmtRev = stpAmt;
1190 auto ownerGivesAdjRev = ownerGives;
1192 offer,
1193 ofrAdjAmtRev,
1194 stpAdjAmtRev,
1195 ownerGivesAdjRev,
1196 transferRateIn,
1197 transferRateOut,
1198 remainingOut);
1199
1200 if (stpAdjAmtRev.in == remainingIn)
1201 {
1202 result.in = in;
1203 result.out = cache_->out;
1204
1205 savedIns.clear();
1206 savedIns.insert(result.in);
1207 savedOuts.clear();
1208 savedOuts.insert(result.out);
1209
1210 ofrAdjAmt = ofrAdjAmtRev;
1211 stpAdjAmt.in = remainingIn;
1212 stpAdjAmt.out = remainingOut;
1213 ownerGivesAdj = ownerGivesAdjRev;
1214 }
1215 else
1216 {
1217 // This is (likely) a problem case, and will be caught
1218 // with later checks
1219 savedOuts.insert(lastOutAmt);
1220 }
1221 }
1222
1223 remainingIn = in - result.in;
1224 this->consumeOffer(sb, offer, ofrAdjAmt, stpAdjAmt, ownerGivesAdj);
1225
1226 // When the mantissas of two iou amounts differ by less than ten, then
1227 // subtracting them leaves a result of zero. This can cause the check
1228 // for (stpAmt.in > remainingIn) to incorrectly think an offer will be
1229 // funded after subtracting remainingIn.
1230 return processMore || offer.fullyConsumed();
1231 };
1232
1233 {
1234 auto const prevStepDebtDir = [&] {
1235 if (prevStep_)
1236 return prevStep_->debtDirection(sb, StrandDirection::Forward);
1237 return DebtDirection::Issues;
1238 }();
1239 auto const r = forEachOffer(sb, afView, prevStepDebtDir, eachOffer);
1240 boost::container::flat_set<uint256> const toRm = std::move(std::get<0>(r));
1241 std::uint32_t const offersConsumed = std::get<1>(r);
1242 offersUsed_ = offersConsumed;
1243 setUnion(ofrsToRm, toRm);
1244
1245 // Too many iterations, mark this strand as inactive (dry)
1246 if (offersConsumed >= kMaxOffersToConsume)
1247 {
1248 inactive_ = true;
1249 }
1250 }
1251
1252 switch (remainingIn.signum())
1253 {
1254 case -1: {
1255 // LCOV_EXCL_START
1256 // something went very wrong
1257 JLOG(j_.error()) << "BookStep remainingIn < 0 " << to_string(remainingIn);
1258 UNREACHABLE("xrpl::BookStep::fwdImp : remaining less than zero");
1259 cache_.emplace(beast::kZero, beast::kZero);
1260 return {beast::kZero, beast::kZero};
1261 // LCOV_EXCL_STOP
1262 }
1263 case 0: {
1264 // due to normalization, remainingIn can be zero without
1265 // result.in == in. Force result.in == in for this case
1266 result.in = in;
1267 }
1268 }
1269
1270 cache_.emplace(result.in, result.out);
1271 return {result.in, result.out};
1272}
1273
1274template <class TIn, class TOut, class TDerived>
1277 PaymentSandbox& sb,
1278 ApplyView& afView,
1279 EitherAmount const& in)
1280{
1281 if (!cache_)
1282 {
1283 JLOG(j_.trace()) << "Expected valid cache in validFwd";
1284 return {false, EitherAmount(TOut(beast::kZero))};
1285 }
1286
1287 auto const savCache = *cache_;
1288
1289 try
1290 {
1291 boost::container::flat_set<uint256> dummy;
1292 fwdImp(sb, afView, dummy, get<TIn>(in)); // changes cache
1293 }
1294 catch (FlowException const&)
1295 {
1296 return {false, EitherAmount(TOut(beast::kZero))};
1297 }
1298
1299 // NOLINTBEGIN(bugprone-unchecked-optional-access) fwdImp sets cache_ on success
1300 if (!(checkNear(savCache.in, cache_->in) && checkNear(savCache.out, cache_->out)))
1301 {
1302 JLOG(j_.warn()) << "Strand re-execute check failed."
1303 << " ExpectedIn: " << to_string(savCache.in)
1304 << " CachedIn: " << to_string(cache_->in)
1305 << " ExpectedOut: " << to_string(savCache.out)
1306 << " CachedOut: " << to_string(cache_->out);
1307 return {false, EitherAmount(cache_->out)};
1308 }
1309 return {true, EitherAmount(cache_->out)};
1310 // NOLINTEND(bugprone-unchecked-optional-access)
1311}
1312
1313template <class TIn, class TOut, class TDerived>
1314TER
1316{
1317 if (book_.in == book_.out)
1318 {
1319 JLOG(j_.debug()) << "BookStep: Book with same in and out issuer " << *this;
1320 return temBAD_PATH;
1321 }
1322 if (!isConsistent(book_.in) || !isConsistent(book_.out))
1323 {
1324 JLOG(j_.debug()) << "Book: currency is inconsistent with issuer." << *this;
1325 return temBAD_PATH;
1326 }
1327
1328 // Do not allow two books to output the same issue. This may cause offers on
1329 // one step to unfund offers in another step.
1330 if (!ctx.seenBookOuts.insert(book_.out).second ||
1331 (ctx.seenDirectAssets[0].count(book_.out) != 0u))
1332 {
1333 JLOG(j_.debug()) << "BookStep: loop detected: " << *this;
1334 return temBAD_PATH_LOOP;
1335 }
1336
1337 if (ctx.seenDirectAssets[1].count(book_.out) != 0u)
1338 {
1339 JLOG(j_.debug()) << "BookStep: loop detected: " << *this;
1340 return temBAD_PATH_LOOP;
1341 }
1342
1343 auto issuerExists = [](ReadView const& view, Asset const& iss) -> bool {
1344 return isXRP(iss.getIssuer()) || view.exists(keylet::account(iss.getIssuer()));
1345 };
1346
1347 if (!issuerExists(ctx.view, book_.in) || !issuerExists(ctx.view, book_.out))
1348 {
1349 JLOG(j_.debug()) << "BookStep: deleted issuer detected: " << *this;
1350 return tecNO_ISSUER;
1351 }
1352
1353 if (ctx.prevStep != nullptr)
1354 {
1355 if (auto const prev = ctx.prevStep->directStepSrcAcct())
1356 {
1357 auto const& view = ctx.view;
1358 auto const& cur = book_.in.getIssuer();
1359
1360 auto const err = book_.in.visit(
1361 [&](Issue const& issue) -> std::optional<TER> {
1362 auto sle = view.read(keylet::trustLine(*prev, cur, issue.currency));
1363 if (!sle)
1364 return terNO_LINE;
1365 if (sle->isFlag((cur > *prev) ? lsfHighNoRipple : lsfLowNoRipple))
1366 return terNO_RIPPLE;
1367 return std::nullopt;
1368 },
1369 [&](MPTIssue const& issue) -> std::optional<TER> { return std::nullopt; });
1370 if (err)
1371 return *err;
1372 }
1373 }
1374
1375 // Check if the offer can be traded on DEX.
1376 if (auto const ter = canTrade(ctx.view, book_.in); !isTesSuccess(ter))
1377 return ter;
1378 if (auto const ter = canTrade(ctx.view, book_.out); !isTesSuccess(ter))
1379 return ter;
1380
1381 return tesSUCCESS;
1382}
1383
1384template <class TIn, class TOut, class TDerived>
1385Rate
1387 ReadView const& view,
1388 Asset const& asset,
1389 AccountID const& dstAccount) const
1390{
1391 return asset.visit(
1392 [&](Issue const& issue) -> Rate {
1393 if (isXRP(issue.account) || issue.account == dstAccount)
1394 return kParityRate;
1395 return transferRate(view, issue.account);
1396 },
1397 [&](MPTIssue const& mptIssue) -> Rate {
1398 // For MPT, parity applies only when this asset is the final strand
1399 // delivery AND the destination is the MPT issuer (holder → issuer,
1400 // which is fee-free). Using strandDst_ alone is wrong because it
1401 // incorrectly suppresses the fee when MPT is an intermediate or
1402 // the in-side of a book that precedes the issuer's XRP receipt.
1403 if (asset == strandDeliver_ && mptIssue.getIssuer() == dstAccount)
1404 return kParityRate;
1405 return transferRate(view, mptIssue.getMptID());
1406 });
1407};
1408
1409template <class TIn, class TOut, class TDerived>
1410bool
1412{
1413 if (!isTesSuccess(canTrade(view, book_.in)) || !isTesSuccess(canTrade(view, book_.out)))
1414 return false;
1415
1416 if (book_.in.holds<MPTIssue>())
1417 {
1418 auto ret = [&]() {
1419 auto const& asset = book_.in;
1420 // Strand's source is an issuer
1421 if (!prevStep_)
1422 return true;
1423 // Offer's owner is an issuer
1424 if (asset.getIssuer() == owner)
1425 return true;
1426 // The previous step could be MPTEndpointStep with non issuer account or
1427 // BookStep. Fail both if in asset is locked. In the former case it is holder
1428 // to locked holder transfer. In the latter case it is not possible to tell if
1429 // it is issuer to holder or holder to holder transfer.
1430 if (isFrozen(view, owner, book_.in.get<MPTIssue>()))
1431 return false;
1432 // Previous step is BookStep. BookStep only sends if CanTransfer is
1433 // set and not locked or the offer is owned by an issuer
1434 if (prevStep_->bookStepBook())
1435 return true;
1436 // Previous step is MPTEndpointStep and offer's owner is not an
1437 // issuer
1438 return isTesSuccess(canTransfer(view, asset, owner, owner));
1439 }();
1440 if (!ret)
1441 return false;
1442 }
1443
1444 if (book_.out.holds<MPTIssue>())
1445 {
1446 auto const& asset = book_.out;
1447 // Last step if the strand's destination is an issuer
1448 if (strandDeliver_ == asset && strandDst_ == asset.getIssuer())
1449 return true;
1450 // Offer's owner is an issuer
1451 if (asset.getIssuer() == owner)
1452 return true;
1453
1454 // Next step is BookStep and offer's owner is not an issuer.
1455 return isTesSuccess(canTransfer(view, asset, owner, owner));
1456 }
1457
1458 return true;
1459}
1460
1461//------------------------------------------------------------------------------
1462
1463namespace test {
1464// Needed for testing
1465
1466template <class TIn, class TOut, class TDerived>
1467static bool
1468equalHelper(Step const& step, xrpl::Book const& book)
1469{
1470 if (auto bs = dynamic_cast<BookStep<TIn, TOut, TDerived> const*>(&step))
1471 return book == bs->book();
1472 return false;
1473}
1474
1475bool
1476bookStepEqual(Step const& step, xrpl::Book const& book)
1477{
1478 return std::visit(
1479 [&]<typename TIn, typename TOut>(TIn const&, TOut const&) {
1480 using TIn_ = TIn::amount_type;
1481 using TOut_ = TOut::amount_type;
1482
1483 if constexpr (ValidTaker<TIn_, TOut_>)
1484 {
1486 }
1487 else
1488 {
1489 // LCOV_EXCL_START
1490 UNREACHABLE("xrpl::bookStepEqual : invalid book step");
1491 return false;
1492 // LCOV_EXCL_STOP
1493 }
1494 },
1495 book.in.getAmountType(),
1496 book.out.getAmountType());
1497}
1498} // namespace test
1499
1500//------------------------------------------------------------------------------
1501
1502template <class TIn, class TOut>
1504makeBookStepHelper(StrandContext const& ctx, Asset const& in, Asset const& out)
1505{
1506 TER ter = tefINTERNAL;
1509 {
1510 auto offerCrossingStep = std::make_unique<BookOfferCrossingStep<TIn, TOut>>(ctx, in, out);
1511 ter = offerCrossingStep->check(ctx);
1512 r = std::move(offerCrossingStep);
1513 }
1514 else // payment
1515 {
1516 auto paymentStep = std::make_unique<BookPaymentStep<TIn, TOut>>(ctx, in, out);
1517 ter = paymentStep->check(ctx);
1518 r = std::move(paymentStep);
1519 }
1520 if (!isTesSuccess(ter))
1521 return {ter, nullptr};
1522
1523 return {tesSUCCESS, std::move(r)};
1524}
1525
1527makeBookStepIi(StrandContext const& ctx, Issue const& in, Issue const& out)
1528{
1529 return makeBookStepHelper<IOUAmount, IOUAmount>(ctx, in, out);
1530}
1531
1533makeBookStepIx(StrandContext const& ctx, Issue const& in)
1534{
1536}
1537
1539makeBookStepXi(StrandContext const& ctx, Issue const& out)
1540{
1542}
1543
1544// MPT's
1546makeBookStepMm(StrandContext const& ctx, MPTIssue const& in, MPTIssue const& out)
1547{
1548 return makeBookStepHelper<MPTAmount, MPTAmount>(ctx, in, out);
1549}
1550
1552makeBookStepMi(StrandContext const& ctx, MPTIssue const& in, Issue const& out)
1553{
1554 return makeBookStepHelper<MPTAmount, IOUAmount>(ctx, in, out);
1555}
1556
1558makeBookStepIm(StrandContext const& ctx, Issue const& in, MPTIssue const& out)
1559{
1560 return makeBookStepHelper<IOUAmount, MPTAmount>(ctx, in, out);
1561}
1562
1565{
1567}
1568
1571{
1573}
1574
1575} // namespace xrpl
T accumulate(T... args)
A generic endpoint for log messages.
Definition Journal.h:38
Represents synthetic AMM offer in BookStep.
Definition AMMOffer.h:22
Quality quality() const noexcept
Definition AMMOffer.h:51
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:118
constexpr auto visit(Visitors &&... visitors) const -> decltype(auto)
Definition Asset.h:107
constexpr TIss const & get() const
AccountID const & getIssuer() const
Definition Asset.cpp:21
constexpr bool holds() const
Definition Asset.h:166
std::uint32_t getOfrInRate(Step const *prevStep, AccountID const &owner, std::uint32_t trIn) const
Definition BookStep.cpp:486
std::string logString() const override
Definition BookStep.cpp:549
bool checkQualityThreshold(Quality const &quality) const
Definition BookStep.cpp:459
BookOfferCrossingStep(StrandContext const &ctx, Asset const &in, Asset const &out)
Definition BookStep.cpp:392
Quality adjustQualityWithFees(ReadView const &v, Quality const &ofrQ, DebtDirection prevStepDir, WaiveTransferFee waiveFee, OfferType offerType, Rules const &rules) const
Definition BookStep.cpp:511
bool limitSelfCrossQuality(AccountID const &strandSrc, AccountID const &strandDst, Offer< TIn, TOut > const &offer, std::optional< Quality > &ofrQ, FlowOfferStream< TIn, TOut > &offers, bool const offerAttempted) const
Definition BookStep.cpp:401
static Quality getQuality(std::optional< Quality > const &limitQuality)
Definition BookStep.cpp:382
std::optional< Quality > qualityThreshold(Quality const &lobQuality) const
Definition BookStep.cpp:475
Quality const qualityThreshold_
Definition BookStep.cpp:556
std::uint32_t getOfrOutRate(Step const *prevStep, AccountID const &owner, AccountID const &strandDst, std::uint32_t trOut) const
Definition BookStep.cpp:497
BookPaymentStep(StrandContext const &ctx, Asset const &in, Asset const &out)
Definition BookStep.cpp:287
std::optional< Quality > qualityThreshold(Quality const &lobQuality) const
Definition BookStep.cpp:319
bool limitSelfCrossQuality(AccountID const &, AccountID const &, Offer< TIn, TOut > const &offer, std::optional< Quality > &, FlowOfferStream< TIn, TOut > &, bool) const
Definition BookStep.cpp:298
Quality adjustQualityWithFees(ReadView const &v, Quality const &ofrQ, DebtDirection prevStepDir, WaiveTransferFee waiveFee, OfferType, Rules const &) const
Definition BookStep.cpp:339
std::uint32_t getOfrInRate(Step const *, AccountID const &, std::uint32_t trIn) const
Definition BookStep.cpp:326
std::uint32_t getOfrOutRate(Step const *, AccountID const &, AccountID const &, std::uint32_t trOut) const
Definition BookStep.cpp:333
std::string logString() const override
Definition BookStep.cpp:365
bool checkQualityThreshold(Quality const &quality) const
Definition BookStep.cpp:311
TER check(StrandContext const &ctx) const
AccountID strandSrc_
Definition BookStep.cpp:62
Step const *const prevStep_
Definition BookStep.cpp:65
friend bool operator==(BookStep const &lhs, BookStep const &rhs)
Definition BookStep.cpp:207
std::pair< boost::container::flat_set< uint256 >, std::uint32_t > forEachOffer(PaymentSandbox &sb, ApplyView &afView, DebtDirection prevStepDebtDir, Callback &callback) const
Definition BookStep.cpp:686
std::optional< Book > bookStepBook() const override
Definition BookStep.cpp:150
beast::Journal const j_
Definition BookStep.cpp:81
std::optional< std::variant< Quality, AMMOffer< TIn, TOut > > > tip(ReadView const &view) const
Definition BookStep.cpp:920
std::optional< QualityFunction > tipOfferQualityF(ReadView const &view) const
Definition BookStep.cpp:976
Asset const strandDeliver_
Definition BookStep.cpp:82
std::optional< EitherAmount > cachedIn() const override
Definition BookStep.cpp:128
std::optional< AMMOffer< TIn, TOut > > getAMMOffer(ReadView const &view, std::optional< Quality > const &clobQuality) const
Definition BookStep.cpp:904
bool const ownerPaysTransferFee_
Definition BookStep.cpp:66
DebtDirection debtDirection(ReadView const &sb, StrandDirection dir) const override
Definition BookStep.cpp:144
BookStep(StrandContext const &ctx, Asset const &in, Asset const &out)
Definition BookStep.cpp:97
void consumeOffer(PaymentSandbox &sb, Offer< TIn, TOut > &offer, TAmounts< TIn, TOut > const &ofrAmt, TAmounts< TIn, TOut > const &stepAmt, TOut const &ownerGives) const
Definition BookStep.cpp:858
std::pair< TIn, TOut > fwdImp(PaymentSandbox &sb, ApplyView &afView, boost::container::flat_set< uint256 > &ofrsToRm, TIn const &in)
bool equal(Step const &rhs) const override
Definition BookStep.cpp:563
std::optional< Cache > cache_
Definition BookStep.cpp:94
std::uint32_t offersUsed_
Number of offers consumed or partially consumed the last time the step ran, including expired and unf...
Definition BookStep.cpp:76
std::optional< EitherAmount > cachedOut() const override
Definition BookStep.cpp:136
std::pair< TIn, TOut > revImp(PaymentSandbox &sb, ApplyView &afView, boost::container::flat_set< uint256 > &ofrsToRm, TOut const &out)
std::pair< std::optional< Quality >, DebtDirection > qualityUpperBound(ReadView const &v, DebtDirection prevStepDir) const override
Definition BookStep.cpp:572
bool inactive() const override
Definition BookStep.cpp:186
friend bool operator!=(BookStep const &lhs, BookStep const &rhs)
Definition BookStep.cpp:213
std::uint32_t offersUsed() const override
Definition BookStep.cpp:625
std::pair< std::optional< QualityFunction >, DebtDirection > getQualityFunc(ReadView const &v, DebtDirection prevStepDir) const override
Definition BookStep.cpp:590
std::optional< AMMLiquidity< TIn, TOut > > ammLiquidity_
Definition BookStep.cpp:80
Rate rate(ReadView const &view, Asset const &asset, AccountID const &dstAccount) const
Book const & book() const
Definition BookStep.cpp:122
static constexpr uint32_t kMaxOffersToConsume
Definition BookStep.cpp:60
std::optional< std::pair< Quality, OfferType > > tipOfferQuality(ReadView const &view) const
Definition BookStep.cpp:958
AccountID strandDst_
Definition BookStep.cpp:63
std::string logStringImpl(char const *name) const
Definition BookStep.cpp:193
bool checkMPTDEX(ReadView const &view, AccountID const &owner) const
std::pair< bool, EitherAmount > validFwd(PaymentSandbox &sb, ApplyView &afView, EitherAmount const &in) override
Iterates and consumes raw offers in an order book.
Definition BookTip.h:16
bool step(beast::Journal j)
Erases the current offer and advance to the next offer.
Definition BookTip.cpp:19
Quality const & quality() const noexcept
Definition BookTip.h:44
Specifies an order book.
Definition Book.h:16
Presents and consumes the offers in an order book.
A currency issued by an account.
Definition Issue.h:13
Currency currency
Definition Issue.h:15
AccountID account
Definition Issue.h:16
A wrapper which makes credits unavailable to balances.
Average quality of a path as a function of out: q(out) = m * out + b, where m = -1 / poolGets,...
void combine(QualityFunction const &qf)
Combines QF with the next step QF.
Represents the logical ratio of output currency to input currency.
Definition Quality.h:91
A view into a ledger.
Definition ReadView.h:31
virtual Rules const & rules() const =0
Returns the tx processing rules.
NetClock::time_point parentCloseTime() const
Returns the close time of the previous ledger.
Definition ReadView.h:90
virtual bool exists(Keylet const &k) const =0
Determine if a state item exists.
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
Rules controlling protocol behavior.
Definition Rules.h:33
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:171
static std::uint64_t const kURateOne
Definition STAmount.h:64
Discardable, editable view to a ledger.
Definition Sandbox.h:15
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
Rules const & rules() const override
Returns the tx processing rules.
T get_if(T... args)
T is_same_v
T make_pair(T... args)
T make_unique(T... args)
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:425
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 bookStepEqual(Step const &step, xrpl::Book const &book)
static bool equalHelper(Step const &step, xrpl::Book const &book)
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_RIPPLE
Definition TER.h:216
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:97
static auto sum(TCollection const &col)
Definition BookStep.cpp:993
std::pair< TER, std::unique_ptr< Step > > makeBookStepIi(StrandContext const &ctx, Issue const &in, Issue const &out)
bool isXRP(AccountID const &c)
Definition AccountID.h:70
TER checkCreateMPT(xrpl::ApplyView &view, xrpl::MPTIssue const &mptIssue, xrpl::AccountID const &holder, beast::Journal j)
static void limitStepOut(Offer const &offer, TAmounts< TIn, TOut > &ofrAmt, TAmounts< TIn, TOut > &stpAmt, TOut &ownerGives, std::uint32_t transferRateIn, std::uint32_t transferRateOut, TOut const &limit)
Definition BookStep.cpp:662
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
std::pair< TER, std::unique_ptr< Step > > makeBookStepMx(StrandContext const &ctx, MPTIssue const &in)
void setUnion(boost::container::flat_set< T > &dst, boost::container::flat_set< T > const &src)
Given two flat sets dst and src, compute dst = dst union src.
Definition FlatSets.h:15
@ tefINTERNAL
Definition TER.h:163
WaiveTransferFee
std::pair< TER, std::unique_ptr< Step > > makeBookStepMi(StrandContext const &ctx, MPTIssue const &in, Issue const &out)
static void limitStepIn(Offer const &offer, TAmounts< TIn, TOut > &ofrAmt, TAmounts< TIn, TOut > &stpAmt, TOut &ownerGives, std::uint32_t transferRateIn, std::uint32_t transferRateOut, TIn const &limit)
Definition BookStep.cpp:633
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::pair< TER, std::unique_ptr< Step > > makeBookStepIx(StrandContext const &ctx, Issue const &in)
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.
T toAmount(STAmount const &amt)=delete
StrandDirection
Definition Steps.h:23
STAmount accountFunds(ReadView const &view, AccountID const &id, STAmount const &saDefault, FreezeHandling freezeHandling, beast::Journal j)
static bool isDefaultPath(STPath const &path)
std::pair< TER, std::unique_ptr< Step > > makeBookStepXm(StrandContext const &ctx, MPTIssue const &out)
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.
Rate const kParityRate
A transfer rate signifying a 1:1 exchange.
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition STAmount.cpp:422
std::pair< TER, std::unique_ptr< Step > > makeBookStepXi(StrandContext const &ctx, Issue const &out)
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)
@ TapNone
Definition ApplyView.h:13
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:28
std::pair< TER, std::unique_ptr< Step > > makeBookStepIm(StrandContext const &ctx, Issue const &in, MPTIssue const &out)
@ 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
bool isConsistent(Asset const &asset)
Definition Asset.h:312
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.
@ tecINVARIANT_FAILED
Definition TER.h:311
@ tecNO_ISSUER
Definition TER.h:297
void issuerSelfDebitHookMPT(ApplyView &view, MPTIssue const &issue, std::uint64_t amount)
Facilitate tracking of MPT sold by an issuer owning MPT sell offer.
bool redeems(DebtDirection dir)
Definition Steps.h:27
static std::pair< TER, std::unique_ptr< Step > > makeBookStepHelper(StrandContext const &ctx, Asset const &in, Asset const &out)
std::pair< TER, std::unique_ptr< Step > > makeBookStepMm(StrandContext const &ctx, MPTIssue const &in, MPTIssue const &out)
Quality composedQuality(Quality const &lhs, Quality const &rhs)
Calculate the quality of a two-hop path given the two hops.
Definition Quality.cpp:114
@ tesSUCCESS
Definition TER.h:240
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Definition contract.h:49
STAmount toSTAmount(IOUAmount const &iou, Asset const &asset)
T str(T... args)
Cache(TIn const &in, TOut const &out)
Definition BookStep.cpp:89
uint256 key
Definition Keylet.h:20
Represents a transfer rate.
Definition Rate.h:20
Context needed to build Strand Steps and for error checking.
Definition Steps.h:518
ReadView const & view
Current ReadView.
Definition Steps.h:519
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
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
Represents a pair of input and output currencies.
Definition Quality.h:26
T visit(T... args)