rippled
Loading...
Searching...
No Matches
BookStep.cpp
1#include <xrpl/basics/Log.h>
2#include <xrpl/basics/contract.h>
3#include <xrpl/beast/utility/instrumentation.h>
4#include <xrpl/ledger/PaymentSandbox.h>
5#include <xrpl/ledger/helpers/AccountRootHelpers.h>
6#include <xrpl/protocol/Book.h>
7#include <xrpl/protocol/Feature.h>
8#include <xrpl/protocol/IOUAmount.h>
9#include <xrpl/protocol/Quality.h>
10#include <xrpl/protocol/XRPAmount.h>
11#include <xrpl/tx/paths/AMMLiquidity.h>
12#include <xrpl/tx/paths/AMMOffer.h>
13#include <xrpl/tx/paths/OfferStream.h>
14#include <xrpl/tx/paths/detail/FlatSets.h>
15#include <xrpl/tx/paths/detail/Steps.h>
16#include <xrpl/tx/transactors/dex/AMMUtils.h>
17
18#include <boost/container/flat_set.hpp>
19
20#include <numeric>
21#include <sstream>
22
23namespace xrpl {
24
25template <class TIn, class TOut, class TDerived>
26class BookStep : public StepImp<TIn, TOut, BookStep<TIn, TOut, TDerived>>
27{
28protected:
29 enum class OfferType { AMM, CLOB };
30
31 static constexpr uint32_t MaxOffersToConsume{1000};
35 // Charge transfer fees when the prev step redeems
36 Step const* const prevStep_ = nullptr;
38 // Mark as inactive (dry) if too many offers are consumed
39 bool inactive_ = false;
48 // If set, AMM liquidity might be available
49 // if AMM offer quality is better than CLOB offer
50 // quality or there is no CLOB offer.
53
54 struct Cache
55 {
56 TIn in;
57 TOut out;
58
59 Cache(TIn const& in_, TOut const& out_) : in(in_), out(out_)
60 {
61 }
62 };
63
65
66private:
67 BookStep(StrandContext const& ctx, Issue const& in, Issue const& out)
68 : book_(in, out, ctx.domainID)
69 , strandSrc_(ctx.strandSrc)
70 , strandDst_(ctx.strandDst)
71 , prevStep_(ctx.prevStep)
72 , ownerPaysTransferFee_(ctx.ownerPaysTransferFee)
73 , j_(ctx.j)
74 {
75 if (auto const ammSle = ctx.view.read(keylet::amm(in, out));
76 ammSle && ammSle->getFieldAmount(sfLPTokenBalance) != beast::zero)
77 {
78 ammLiquidity_.emplace(
79 ctx.view,
80 (*ammSle)[sfAccount],
81 getTradingFee(ctx.view, *ammSle, ctx.ammContext.account()),
82 in,
83 out,
84 ctx.ammContext,
85 ctx.j);
86 }
87 }
88
89public:
90 Book const&
91 book() const
92 {
93 return book_;
94 }
95
97 cachedIn() const override
98 {
99 if (!cache_)
100 return std::nullopt;
101 return EitherAmount(cache_->in);
102 }
103
105 cachedOut() const override
106 {
107 if (!cache_)
108 return std::nullopt;
109 return EitherAmount(cache_->out);
110 }
111
117
119 bookStepBook() const override
120 {
121 return book_;
122 }
123
125 qualityUpperBound(ReadView const& v, DebtDirection prevStepDir) const override;
126
128 getQualityFunc(ReadView const& v, DebtDirection prevStepDir) const override;
129
131 offersUsed() const override;
132
135 PaymentSandbox& sb,
136 ApplyView& afView,
137 boost::container::flat_set<uint256>& ofrsToRm,
138 TOut const& out);
139
142 PaymentSandbox& sb,
143 ApplyView& afView,
144 boost::container::flat_set<uint256>& ofrsToRm,
145 TIn const& in);
146
148 validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in) override;
149
150 // Check for errors frozen constraints.
151 TER
152 check(StrandContext const& ctx) const;
153
154 bool
155 inactive() const override
156 {
157 return inactive_;
158 }
159
160protected:
162 logStringImpl(char const* name) const
163 {
165 ostr << name << ": "
166 << "\ninIss: " << book_.in.account << "\noutIss: " << book_.out.account
167 << "\ninCur: " << book_.in.currency << "\noutCur: " << book_.out.currency;
168 return ostr.str();
169 }
170
171private:
172 friend bool
173 operator==(BookStep const& lhs, BookStep const& rhs)
174 {
175 return lhs.book_ == rhs.book_;
176 }
177
178 friend bool
179 operator!=(BookStep const& lhs, BookStep const& rhs)
180 {
181 return !(lhs == rhs);
182 }
183
184 bool
185 equal(Step const& rhs) const override;
186
187 // Iterate through the offers at the best quality in a book.
188 // Unfunded offers and bad offers are skipped (and returned).
189 // callback is called with the offer SLE, taker pays, taker gets.
190 // If callback returns false, don't process any more offers.
191 // Return the unfunded and bad offers and the number of offers consumed.
192 template <class Callback>
195 PaymentSandbox& sb,
196 ApplyView& afView,
197 DebtDirection prevStepDebtDir,
198 Callback& callback) const;
199
200 // Offer is either TOffer or AMMOffer
201 template <template <typename, typename> typename Offer>
202 void
204 PaymentSandbox& sb,
205 Offer<TIn, TOut>& offer,
206 TAmounts<TIn, TOut> const& ofrAmt,
207 TAmounts<TIn, TOut> const& stepAmt,
208 TOut const& ownerGives) const;
209
210 // If clobQuality is available and has a better quality then return nullopt,
211 // otherwise if amm liquidity is available return AMM offer adjusted based
212 // on clobQuality.
214 getAMMOffer(ReadView const& view, std::optional<Quality> const& clobQuality) const;
215
216 // If seated then it is either order book tip quality or AMMOffer,
217 // whichever is a better quality.
219 tip(ReadView const& view) const;
220 // If seated then it is either AMM or CLOB quality,
221 // whichever is a better quality. OfferType is AMM
222 // if AMM quality is better.
224 tipOfferQuality(ReadView const& view) const;
225 // If seated then it is either AMM or CLOB quality function,
226 // whichever is a better quality.
228 tipOfferQualityF(ReadView const& view) const;
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 BookStep template class (not offer crossing).
242template <class TIn, class TOut>
243class BookPaymentStep : public BookStep<TIn, TOut, BookPaymentStep<TIn, TOut>>
244{
245public:
246 explicit BookPaymentStep() = default;
247
248 BookPaymentStep(StrandContext const& ctx, Issue const& in, Issue const& out)
249 : BookStep<TIn, TOut, BookPaymentStep<TIn, TOut>>(ctx, in, out)
250 {
251 }
252
255
256 // Never limit self cross quality on a payment.
257 template <template <typename, typename> typename Offer>
258 bool
260 AccountID const&,
261 AccountID const&,
262 Offer<TIn, TOut> const& offer,
265 bool) const
266 {
267 return false;
268 }
269
270 // A payment can look at offers of any quality
271 bool
272 checkQualityThreshold(Quality const& quality) const
273 {
274 return true;
275 }
276
277 // A payment doesn't use quality threshold (limitQuality)
278 // since the strand's quality doesn't directly relate to the step's quality.
280 qualityThreshold(Quality const& lobQuality) const
281 {
282 return lobQuality;
283 }
284
285 // For a payment ofrInRate is always the same as trIn.
287 getOfrInRate(Step const*, AccountID const&, std::uint32_t trIn) const
288 {
289 return trIn;
290 }
291
292 // For a payment ofrOutRate is always the same as trOut.
294 getOfrOutRate(Step const*, AccountID const&, AccountID const&, std::uint32_t trOut) const
295 {
296 return trOut;
297 }
298
299 Quality
301 ReadView const& v,
302 Quality const& ofrQ,
303 DebtDirection prevStepDir,
304 WaiveTransferFee waiveFee,
305 OfferType,
306 Rules const&) const
307 {
308 // Charge the offer owner, not the sender
309 // Charge a fee even if the owner is the same as the issuer
310 // (the old code does not charge a fee)
311 // Calculate amount that goes to the taker and the amount charged the
312 // offer owner
313 auto rate = [&](AccountID const& id) {
314 if (isXRP(id) || id == this->strandDst_)
315 return parityRate;
316 return transferRate(v, id);
317 };
318
319 auto const trIn = redeems(prevStepDir) ? rate(this->book_.in.account) : parityRate;
320 // Always charge the transfer fee, even if the owner is the issuer,
321 // unless the fee is waived
322 auto const trOut = (this->ownerPaysTransferFee_ && waiveFee == WaiveTransferFee::No)
323 ? rate(this->book_.out.account)
324 : parityRate;
325
326 Quality const q1{getRate(STAmount(trOut.value), STAmount(trIn.value))};
327 return composed_quality(q1, ofrQ);
328 }
329
331 logString() const override
332 {
333 return this->logStringImpl("BookPaymentStep");
334 }
335};
336
337// Offer crossing BookStep template class (not a payment).
338template <class TIn, class TOut>
339class BookOfferCrossingStep : public BookStep<TIn, TOut, BookOfferCrossingStep<TIn, TOut>>
340{
343
344private:
345 // Helper function that throws if the optional passed to the constructor
346 // is none.
347 static Quality
349 {
350 // It's really a programming error if the quality is missing.
351 XRPL_ASSERT(limitQuality, "xrpl::BookOfferCrossingStep::getQuality : nonzero quality");
352 if (!limitQuality)
353 Throw<FlowException>(tefINTERNAL, "Offer requires quality.");
354 return *limitQuality;
355 }
356
357public:
359 : BookStep<TIn, TOut, BookOfferCrossingStep<TIn, TOut>>(ctx, in, out)
361 , qualityThreshold_(getQuality(ctx.limitQuality))
362 {
363 }
364
365 template <template <typename, typename> typename Offer>
366 bool
368 AccountID const& strandSrc,
369 AccountID const& strandDst,
370 Offer<TIn, TOut> const& offer,
373 bool const offerAttempted) const
374 {
375 // This method supports some correct but slightly surprising
376 // behavior in offer crossing. The scenario:
377 //
378 // o alice has already created one or more offers.
379 // o alice creates another offer that can be directly crossed (not
380 // autobridged) by one or more of her previously created offer(s).
381 //
382 // What does the offer crossing do?
383 //
384 // o The offer crossing could go ahead and cross the offers leaving
385 // either one reduced offer (partial crossing) or zero offers
386 // (exact crossing) in the ledger. We don't do this. And, really,
387 // the offer creator probably didn't want us to.
388 //
389 // o We could skip over the self offer in the book and only cross
390 // offers that are not our own. This would make a lot of sense,
391 // but we don't do it. Part of the rationale is that we can only
392 // operate on the tip of the order book. We can't leave an offer
393 // behind -- it would sit on the tip and block access to other
394 // offers.
395 //
396 // o We could delete the self-crossable offer(s) off the tip of the
397 // book and continue with offer crossing. That's what we do.
398 //
399 // To support this scenario offer crossing has a special rule. If:
400 // a. We're offer crossing using default path (no autobridging), and
401 // b. The offer's quality is at least as good as our quality, and
402 // c. We're about to cross one of our own offers, then
403 // d. Delete the old offer from the ledger.
404 if (defaultPath_ && offer.quality() >= qualityThreshold_ && strandSrc == offer.owner() &&
405 strandDst == offer.owner())
406 {
407 // Remove this offer even if no crossing occurs.
408 if (auto const key = offer.key())
409 offers.permRmOffer(*key);
410
411 // If no offers have been attempted yet then it's okay to move to
412 // a different quality.
413 if (!offerAttempted)
414 ofrQ = std::nullopt;
415
416 // Return true so the current offer will be deleted.
417 return true;
418 }
419 return false;
420 }
421
422 // Offer crossing can prune the offers it needs to look at with a
423 // quality threshold.
424 bool
425 checkQualityThreshold(Quality const& quality) const
426 {
427 return !defaultPath_ || quality >= qualityThreshold_;
428 }
429
430 // Return quality threshold or nullopt to use when generating AMM offer.
431 // AMM synthetic offer is generated to match LOB offer quality.
432 // If LOB tip offer quality is less than qualityThreshold
433 // then generated AMM offer quality is also less than qualityThreshold and
434 // the offer is not crossed even though AMM might generate a better quality
435 // offer. To address this, if qualityThreshold is greater than lobQuality
436 // then don't use quality to generate the AMM offer. The limit out value
437 // generates the maximum AMM offer in this case, which matches
438 // the quality threshold. This only applies to single path scenario.
439 // Multi-path AMM offers work the same as LOB offers.
441 qualityThreshold(Quality const& lobQuality) const
442 {
443 if (this->ammLiquidity_ && !this->ammLiquidity_->multiPath() &&
444 qualityThreshold_ > lobQuality)
445 return std::nullopt;
446 return lobQuality;
447 }
448
449 // For offer crossing don't pay the transfer fee if alice is paying alice.
450 // A regular (non-offer-crossing) payment does not apply this rule.
452 getOfrInRate(Step const* prevStep, AccountID const& owner, std::uint32_t trIn) const
453 {
454 auto const srcAcct = (prevStep != nullptr) ? prevStep->directStepSrcAcct() : std::nullopt;
455
456 return owner == srcAcct // If offer crossing && prevStep is DirectI
457 ? QUALITY_ONE // && src is offer owner
458 : trIn; // then rate = QUALITY_ONE
459 }
460
461 // See comment on getOfrInRate().
464 Step const* prevStep,
465 AccountID const& owner,
466 AccountID const& strandDst,
467 std::uint32_t trOut) const
468 {
469 return // If offer crossing
470 (prevStep != nullptr) && prevStep->bookStepBook() && // && prevStep is BookStep
471 owner == strandDst // && dest is offer owner
472 ? QUALITY_ONE
473 : trOut; // then rate = QUALITY_ONE
474 }
475
476 Quality
478 ReadView const& v,
479 Quality const& ofrQ,
480 DebtDirection prevStepDir,
481 WaiveTransferFee waiveFee,
482 OfferType offerType,
483 Rules const& rules) const
484 {
485 // Offer x-ing does not charge a transfer fee when the offer's owner
486 // is the same as the strand dst. It is important that
487 // `qualityUpperBound` is an upper bound on the quality (it is used to
488 // ignore strands whose quality cannot meet a minimum threshold). When
489 // calculating quality assume no fee is charged, or the estimate will no
490 // longer be an upper bound.
491
492 // Single path AMM offer has to factor in the transfer in rate
493 // when calculating the upper bound quality and the quality function
494 // because single path AMM's offer quality is not constant.
495 if (!rules.enabled(fixAMMv1_1))
496 {
497 return ofrQ;
498 }
499 if (offerType == OfferType::CLOB ||
500 (this->ammLiquidity_ && this->ammLiquidity_->multiPath()))
501 {
502 return ofrQ;
503 }
504
505 auto rate = [&](AccountID const& id) {
506 if (isXRP(id) || id == this->strandDst_)
507 return parityRate;
508 return transferRate(v, id);
509 };
510
511 auto const trIn = redeems(prevStepDir) ? rate(this->book_.in.account) : parityRate;
512 // AMM doesn't pay the transfer fee on the out amount
513 auto const trOut = parityRate;
514
515 Quality const q1{getRate(STAmount(trOut.value), STAmount(trIn.value))};
516 return composed_quality(q1, ofrQ);
517 }
518
520 logString() const override
521 {
522 return this->logStringImpl("BookOfferCrossingStep");
523 }
524
525private:
526 bool const defaultPath_;
527 Quality const qualityThreshold_;
528};
529
530//------------------------------------------------------------------------------
531
532template <class TIn, class TOut, class TDerived>
533bool
535{
536 if (auto bs = dynamic_cast<BookStep<TIn, TOut, TDerived> const*>(&rhs))
537 return book_ == bs->book_;
538 return false;
539}
540
541template <class TIn, class TOut, class TDerived>
544{
545 auto const dir = this->debtDirection(v, StrandDirection::forward);
546
547 std::optional<std::pair<Quality, OfferType>> const res = tipOfferQuality(v);
548 if (!res)
549 return {std::nullopt, dir};
550
551 auto const waiveFee = (std::get<OfferType>(*res) == OfferType::AMM) ? WaiveTransferFee::Yes
553
554 Quality const q = static_cast<TDerived const*>(this)->adjustQualityWithFees(
555 v, std::get<Quality>(*res), prevStepDir, waiveFee, std::get<OfferType>(*res), v.rules());
556 return {q, dir};
557}
558
559template <class TIn, class TOut, class TDerived>
562{
563 auto const dir = this->debtDirection(v, StrandDirection::forward);
564
565 std::optional<QualityFunction> const res = tipOfferQualityF(v);
566 if (!res)
567 return {std::nullopt, dir};
568
569 // AMM
570 if (!res->isConst())
571 {
572 auto static const qOne = Quality{STAmount::uRateOne};
573 auto const q = static_cast<TDerived const*>(this)->adjustQualityWithFees(
574 v, qOne, prevStepDir, WaiveTransferFee::Yes, OfferType::AMM, v.rules());
575 if (q == qOne)
576 return {res, dir};
578 qf.combine(*res);
579 return {qf, dir};
580 }
581
582 // CLOB
583 Quality const q = static_cast<TDerived const*>(this)->adjustQualityWithFees(
584 v, *(res->quality()), prevStepDir, WaiveTransferFee::No, OfferType::CLOB, v.rules());
586}
587
588template <class TIn, class TOut, class TDerived>
591{
592 return offersUsed_;
593}
594
595// Adjust the offer amount and step amount subject to the given input limit
596template <class TIn, class TOut, class Offer>
597static void
599 Offer const& offer,
600 TAmounts<TIn, TOut>& ofrAmt,
601 TAmounts<TIn, TOut>& stpAmt,
602 TOut& ownerGives,
603 std::uint32_t transferRateIn,
604 std::uint32_t transferRateOut,
605 TIn const& limit)
606{
607 if (limit < stpAmt.in)
608 {
609 stpAmt.in = limit;
610 auto const inLmt = mulRatio(stpAmt.in, QUALITY_ONE, transferRateIn, /*roundUp*/ false);
611 // It turns out we can prevent order book blocking by (strictly)
612 // rounding down the ceil_in() result. By rounding down we guarantee
613 // that the quality of an offer left in the ledger is as good or
614 // better than the quality of the containing order book page.
615 //
616 // This adjustment changes transaction outcomes, so it must be made
617 // under an amendment.
618 ofrAmt = offer.limitIn(ofrAmt, inLmt, /* roundUp */ false);
619 stpAmt.out = ofrAmt.out;
620 ownerGives = mulRatio(ofrAmt.out, transferRateOut, QUALITY_ONE, /*roundUp*/ false);
621 }
622}
623
624// Adjust the offer amount and step amount subject to the given output limit
625template <class TIn, class TOut, class Offer>
626static void
628 Offer const& offer,
629 TAmounts<TIn, TOut>& ofrAmt,
630 TAmounts<TIn, TOut>& stpAmt,
631 TOut& ownerGives,
632 std::uint32_t transferRateIn,
633 std::uint32_t transferRateOut,
634 TOut const& limit)
635{
636 if (limit < stpAmt.out)
637 {
638 stpAmt.out = limit;
639 ownerGives = mulRatio(stpAmt.out, transferRateOut, QUALITY_ONE, /*roundUp*/ false);
640 ofrAmt = offer.limitOut(
641 ofrAmt,
642 stpAmt.out,
643 /*roundUp*/ true);
644 stpAmt.in = mulRatio(ofrAmt.in, transferRateIn, QUALITY_ONE, /*roundUp*/ true);
645 }
646}
647
648template <class TIn, class TOut, class TDerived>
649template <class Callback>
652 PaymentSandbox& sb,
653 ApplyView& afView,
654 DebtDirection prevStepDir,
655 Callback& callback) const
656{
657 // Charge the offer owner, not the sender
658 // Charge a fee even if the owner is the same as the issuer
659 // (the old code does not charge a fee)
660 // Calculate amount that goes to the taker and the amount charged the offer
661 // owner
662 auto rate = [this, &sb](AccountID const& id) -> std::uint32_t {
663 if (isXRP(id) || id == this->strandDst_)
664 return QUALITY_ONE;
665 return transferRate(sb, id).value;
666 };
667
668 std::uint32_t const trIn = redeems(prevStepDir) ? rate(book_.in.account) : QUALITY_ONE;
669 // Always charge the transfer fee, even if the owner is the issuer
670 std::uint32_t const trOut = ownerPaysTransferFee_ ? rate(book_.out.account) : QUALITY_ONE;
671
672 typename FlowOfferStream<TIn, TOut>::StepCounter counter(MaxOffersToConsume, j_);
673
674 FlowOfferStream<TIn, TOut> offers(sb, afView, book_, sb.parentCloseTime(), counter, j_);
675
676 bool offerAttempted = false;
678 auto execOffer = [&](auto& offer) {
679 // Note that offer.quality() returns a (non-optional) Quality. So
680 // ofrQ is always safe to use below this point in the lambda.
681 if (!ofrQ)
682 {
683 ofrQ = offer.quality();
684 }
685 else if (*ofrQ != offer.quality())
686 {
687 return false;
688 }
689
690 if (static_cast<TDerived const*>(this)->limitSelfCrossQuality(
691 strandSrc_, strandDst_, offer, ofrQ, offers, offerAttempted))
692 return true;
693
694 // Make sure offer owner has authorization to own IOUs from issuer.
695 // An account can always own XRP or their own IOUs.
696 if (!isXRP(offer.issueIn().currency) && offer.owner() != offer.issueIn().account)
697 {
698 auto const& issuerID = offer.issueIn().account;
699 auto const issuer = afView.read(keylet::account(issuerID));
700 if (issuer && ((*issuer)[sfFlags] & lsfRequireAuth))
701 {
702 // Issuer requires authorization. See if offer owner has that.
703 auto const& ownerID = offer.owner();
704 auto const authFlag = issuerID > ownerID ? lsfHighAuth : lsfLowAuth;
705
706 auto const line =
707 afView.read(keylet::line(ownerID, issuerID, offer.issueIn().currency));
708
709 if (!line || (((*line)[sfFlags] & authFlag) == 0))
710 {
711 // Offer owner not authorized to hold IOU from issuer.
712 // Remove this offer even if no crossing occurs.
713 if (auto const key = offer.key())
714 offers.permRmOffer(*key);
715 if (!offerAttempted)
716 {
717 // Change quality only if no previous offers were tried.
718 ofrQ = std::nullopt;
719 }
720 // Returning true causes offers.step() to delete the offer.
721 return true;
722 }
723 }
724 }
725
726 if (!static_cast<TDerived const*>(this)->checkQualityThreshold(offer.quality()))
727 return false;
728
729 auto const [ofrInRate, ofrOutRate] = offer.adjustRates(
730 static_cast<TDerived const*>(this)->getOfrInRate(prevStep_, offer.owner(), trIn),
731 static_cast<TDerived const*>(this)->getOfrOutRate(
732 prevStep_, offer.owner(), strandDst_, trOut));
733
734 auto ofrAmt = offer.amount();
735 TAmounts stpAmt{mulRatio(ofrAmt.in, ofrInRate, QUALITY_ONE, /*roundUp*/ true), ofrAmt.out};
736
737 // owner pays the transfer fee.
738 auto ownerGives = mulRatio(ofrAmt.out, ofrOutRate, QUALITY_ONE, /*roundUp*/ false);
739
740 auto const funds = offer.isFunded()
741 ? ownerGives // Offer owner is issuer; they have unlimited funds
742 : offers.ownerFunds();
743
744 // Only if CLOB offer
745 if (funds < ownerGives)
746 {
747 // We already know offer.owner()!=offer.issueOut().account
748 ownerGives = funds;
749 stpAmt.out = mulRatio(ownerGives, QUALITY_ONE, ofrOutRate, /*roundUp*/ false);
750
751 // It turns out we can prevent order book blocking by (strictly)
752 // rounding down the ceil_out() result. This adjustment changes
753 // transaction outcomes, so it must be made under an amendment.
754 ofrAmt = offer.limitOut(ofrAmt, stpAmt.out, /*roundUp*/ false);
755
756 stpAmt.in = mulRatio(ofrAmt.in, ofrInRate, QUALITY_ONE, /*roundUp*/ true);
757 }
758
759 offerAttempted = true;
760 return callback(offer, ofrAmt, stpAmt, ownerGives, ofrInRate, ofrOutRate);
761 };
762
763 // At any payment engine iteration, AMM offer can only be consumed once.
764 auto tryAMM = [&](std::optional<Quality> const& lobQuality) -> bool {
765 // amm doesn't support domain yet
766 if (book_.domain)
767 return true;
768
769 // If offer crossing then use either LOB quality or nullopt
770 // to prevent AMM being blocked by a lower quality LOB.
771 auto const qualityThreshold = [&]() -> std::optional<Quality> {
772 if (sb.rules().enabled(fixAMMv1_1) && lobQuality)
773 return static_cast<TDerived const*>(this)->qualityThreshold(*lobQuality);
774 return lobQuality;
775 }();
776 auto ammOffer = getAMMOffer(sb, qualityThreshold);
777 return !ammOffer || execOffer(*ammOffer);
778 };
779
780 if (offers.step())
781 {
782 if (tryAMM(offers.tip().quality()))
783 {
784 do
785 {
786 if (!execOffer(offers.tip()))
787 break;
788 } while (offers.step());
789 }
790 }
791 else
792 {
793 // Might have AMM offer if there are no LOB offers.
794 tryAMM(std::nullopt);
795 }
796
797 return {offers.permToRemove(), counter.count()};
798}
799
800template <class TIn, class TOut, class TDerived>
801template <template <typename, typename> typename Offer>
802void
804 PaymentSandbox& sb,
805 Offer<TIn, TOut>& offer,
806 TAmounts<TIn, TOut> const& ofrAmt,
807 TAmounts<TIn, TOut> const& stepAmt,
808 TOut const& ownerGives) const
809{
810 if (!offer.checkInvariant(ofrAmt, j_))
811 {
812 // purposely written as separate if statements so we get logging even
813 // when the amendment isn't active.
814 if (sb.rules().enabled(fixAMMOverflowOffer))
815 {
816 Throw<FlowException>(tecINVARIANT_FAILED, "AMM pool product invariant failed.");
817 }
818 }
819
820 // The offer owner gets the ofrAmt. The difference between ofrAmt and
821 // stepAmt is a transfer fee that goes to book_.in.account
822 {
823 auto const dr =
824 offer.send(sb, book_.in.account, offer.owner(), toSTAmount(ofrAmt.in, book_.in), j_);
825 if (!isTesSuccess(dr))
826 Throw<FlowException>(dr);
827 }
828
829 // The offer owner pays `ownerGives`. The difference between ownerGives and
830 // stepAmt is a transfer fee that goes to book_.out.account
831 {
832 auto const cr =
833 offer.send(sb, offer.owner(), book_.out.account, toSTAmount(ownerGives, book_.out), j_);
834 if (!isTesSuccess(cr))
835 Throw<FlowException>(cr);
836 }
837
838 offer.consume(sb, ofrAmt);
839}
840
841template <class TIn, class TOut, class TDerived>
844 ReadView const& view,
845 std::optional<Quality> const& clobQuality) const
846{
847 if (ammLiquidity_)
848 return ammLiquidity_->getOffer(view, clobQuality);
849 return std::nullopt;
850}
851
852template <class TIn, class TOut, class TDerived>
855{
856 // This can be simplified (and sped up) if directories are never empty.
857 Sandbox sb(&view, tapNONE);
858 BookTip bt(sb, book_);
859 auto const lobQuality = bt.step(j_) ? std::optional<Quality>(bt.quality()) : std::nullopt;
860 // Multi-path offer generates an offer with the quality
861 // calculated from the offer size and the quality is constant in this case.
862 // Single path offer quality changes with the offer size. Spot price quality
863 // (SPQ) can't be used in this case as the upper bound quality because
864 // even if SPQ quality is better than LOB quality, it might not be possible
865 // to generate AMM offer at or better quality than LOB quality. Another
866 // factor to consider is limit quality on offer crossing. If LOB quality
867 // is greater than limit quality then use LOB quality when generating AMM
868 // offer, otherwise don't use quality threshold when generating AMM offer.
869 // AMM or LOB offer, whether multi-path or single path then can be selected
870 // based on the best offer quality. Using the quality to generate AMM offer
871 // in this case also prevents the payment engine from going into multiple
872 // iterations to cross a LOB offer. This happens when AMM changes
873 // the out amount at the start of iteration to match the limitQuality
874 // on offer crossing but AMM can't generate the offer at this quality,
875 // as the result a LOB offer is partially crossed, and it might take a few
876 // iterations to fully cross the offer.
877 auto const qualityThreshold = [&]() -> std::optional<Quality> {
878 if (view.rules().enabled(fixAMMv1_1) && lobQuality)
879 return static_cast<TDerived const*>(this)->qualityThreshold(*lobQuality);
880 return std::nullopt;
881 }();
882 // AMM quality is better or no LOB offer
883 if (auto const ammOffer = getAMMOffer(view, qualityThreshold);
884 ammOffer && ((lobQuality && ammOffer->quality() > lobQuality) || !lobQuality))
885 return ammOffer;
886 // LOB quality is better or nullopt
887 return lobQuality;
888}
889
890template <class TIn, class TOut, class TDerived>
891auto
894{
895 auto const res = tip(view);
896 if (!res)
897 {
898 return std::nullopt;
899 }
900 if (auto const q = std::get_if<Quality>(&(*res)))
901 {
902 return std::make_pair(*q, OfferType::CLOB);
903 }
904
905 return std::make_pair(std::get<AMMOffer<TIn, TOut>>(*res).quality(), OfferType::AMM);
906}
907
908template <class TIn, class TOut, class TDerived>
911{
912 auto const res = tip(view);
913 if (!res)
914 {
915 return std::nullopt;
916 }
917 if (auto const q = std::get_if<Quality>(&(*res)))
918 {
920 }
921
922 return std::get<AMMOffer<TIn, TOut>>(*res).getQualityFunc();
923}
924
925template <class TCollection>
926static auto
927sum(TCollection const& col)
928{
929 using TResult = std::decay_t<decltype(*col.begin())>;
930 if (col.empty())
931 return TResult{beast::zero};
932 return std::accumulate(col.begin() + 1, col.end(), *col.begin());
933};
934
935template <class TIn, class TOut, class TDerived>
938 PaymentSandbox& sb,
939 ApplyView& afView,
940 boost::container::flat_set<uint256>& ofrsToRm,
941 TOut const& out)
942{
943 cache_.reset();
944
945 TAmounts<TIn, TOut> result(beast::zero, beast::zero);
946
947 auto remainingOut = out;
948
949 boost::container::flat_multiset<TIn> savedIns;
950 savedIns.reserve(64);
951 boost::container::flat_multiset<TOut> savedOuts;
952 savedOuts.reserve(64);
953
954 /* amt fed will be adjusted by owner funds (and may differ from the offer's
955 amounts - tho always <=)
956 Return true to continue to receive offers, false to stop receiving offers.
957 */
958 auto eachOffer = [&](auto& offer,
959 TAmounts<TIn, TOut> const& ofrAmt,
960 TAmounts<TIn, TOut> const& stpAmt,
961 TOut const& ownerGives,
962 std::uint32_t transferRateIn,
963 std::uint32_t transferRateOut) mutable -> bool {
964 if (remainingOut <= beast::zero)
965 return false;
966
967 if (stpAmt.out <= remainingOut)
968 {
969 savedIns.insert(stpAmt.in);
970 savedOuts.insert(stpAmt.out);
971 result = TAmounts<TIn, TOut>(sum(savedIns), sum(savedOuts));
972 remainingOut = out - result.out;
973 this->consumeOffer(sb, offer, ofrAmt, stpAmt, ownerGives);
974 // return true b/c even if the payment is satisfied,
975 // we need to consume the offer
976 return true;
977 }
978
979 auto ofrAdjAmt = ofrAmt;
980 auto stpAdjAmt = stpAmt;
981 auto ownerGivesAdj = ownerGives;
983 offer,
984 ofrAdjAmt,
985 stpAdjAmt,
986 ownerGivesAdj,
987 transferRateIn,
988 transferRateOut,
989 remainingOut);
990 remainingOut = beast::zero;
991 savedIns.insert(stpAdjAmt.in);
992 savedOuts.insert(remainingOut);
993 result.in = sum(savedIns);
994 result.out = out;
995 this->consumeOffer(sb, offer, ofrAdjAmt, stpAdjAmt, ownerGivesAdj);
996
997 // Explicitly check whether the offer is funded. Given that we have
998 // (stpAmt.out > remainingOut), it's natural to assume the offer
999 // will still be funded after consuming remainingOut but that is
1000 // not always the case. If the mantissas of two IOU amounts differ
1001 // by less than ten, then subtracting them leaves a zero.
1002 return offer.fully_consumed();
1003 };
1004
1005 {
1006 auto const prevStepDebtDir = [&] {
1007 if (prevStep_)
1008 return prevStep_->debtDirection(sb, StrandDirection::reverse);
1009 return DebtDirection::issues;
1010 }();
1011 auto const r = forEachOffer(sb, afView, prevStepDebtDir, eachOffer);
1012 boost::container::flat_set<uint256> const toRm = std::move(std::get<0>(r));
1013 std::uint32_t const offersConsumed = std::get<1>(r);
1014 offersUsed_ = offersConsumed;
1015 SetUnion(ofrsToRm, toRm);
1016
1017 // Too many iterations, mark this strand as inactive
1018 if (offersConsumed >= MaxOffersToConsume)
1019 {
1020 inactive_ = true;
1021 }
1022 }
1023
1024 switch (remainingOut.signum())
1025 {
1026 case -1: {
1027 // something went very wrong
1028 // LCOV_EXCL_START
1029 JLOG(j_.error()) << "BookStep remainingOut < 0 " << to_string(remainingOut);
1030 UNREACHABLE("xrpl::BookStep::revImp : remaining less than zero");
1031 cache_.emplace(beast::zero, beast::zero);
1032 return {beast::zero, beast::zero};
1033 // LCOV_EXCL_STOP
1034 }
1035 case 0: {
1036 // due to normalization, remainingOut can be zero without
1037 // result.out == out. Force result.out == out for this case
1038 result.out = out;
1039 }
1040 }
1041
1042 cache_.emplace(result.in, result.out);
1043 return {result.in, result.out};
1044}
1045
1046template <class TIn, class TOut, class TDerived>
1049 PaymentSandbox& sb,
1050 ApplyView& afView,
1051 boost::container::flat_set<uint256>& ofrsToRm,
1052 TIn const& in)
1053{
1054 XRPL_ASSERT(cache_, "xrpl::BookStep::fwdImp : cache is set");
1055
1056 TAmounts<TIn, TOut> result(beast::zero, beast::zero);
1057
1058 auto remainingIn = in;
1059
1060 boost::container::flat_multiset<TIn> savedIns;
1061 savedIns.reserve(64);
1062 boost::container::flat_multiset<TOut> savedOuts;
1063 savedOuts.reserve(64);
1064
1065 // amt fed will be adjusted by owner funds (and may differ from the offer's
1066 // amounts - tho always <=)
1067 auto eachOffer = [&](auto& offer,
1068 TAmounts<TIn, TOut> const& ofrAmt,
1069 TAmounts<TIn, TOut> const& stpAmt,
1070 TOut const& ownerGives,
1071 std::uint32_t transferRateIn,
1072 std::uint32_t transferRateOut) mutable -> bool {
1073 XRPL_ASSERT(cache_, "xrpl::BookStep::fwdImp::eachOffer : cache is set");
1074
1075 if (remainingIn <= beast::zero)
1076 return false;
1077
1078 bool processMore = true;
1079 auto ofrAdjAmt = ofrAmt;
1080 auto stpAdjAmt = stpAmt;
1081 auto ownerGivesAdj = ownerGives;
1082
1083 typename boost::container::flat_multiset<TOut>::const_iterator lastOut;
1084 if (stpAmt.in <= remainingIn)
1085 {
1086 savedIns.insert(stpAmt.in);
1087 lastOut = savedOuts.insert(stpAmt.out);
1088 result = TAmounts<TIn, TOut>(sum(savedIns), sum(savedOuts));
1089 // consume the offer even if stepAmt.in == remainingIn
1090 processMore = true;
1091 }
1092 else
1093 {
1095 offer,
1096 ofrAdjAmt,
1097 stpAdjAmt,
1098 ownerGivesAdj,
1099 transferRateIn,
1100 transferRateOut,
1101 remainingIn);
1102 savedIns.insert(remainingIn);
1103 lastOut = savedOuts.insert(stpAdjAmt.out);
1104 result.out = sum(savedOuts);
1105 result.in = in;
1106
1107 processMore = false;
1108 }
1109
1110 if (result.out > cache_->out && result.in <= cache_->in)
1111 {
1112 // The step produced more output in the forward pass than the
1113 // reverse pass while consuming the same input (or less). If we
1114 // compute the input required to produce the cached output
1115 // (produced in the reverse step) and the input is equal to
1116 // the input consumed in the forward step, then consume the
1117 // input provided in the forward step and produce the output
1118 // requested from the reverse step.
1119 auto const lastOutAmt = *lastOut;
1120 savedOuts.erase(lastOut);
1121 auto const remainingOut = cache_->out - sum(savedOuts);
1122 auto ofrAdjAmtRev = ofrAmt;
1123 auto stpAdjAmtRev = stpAmt;
1124 auto ownerGivesAdjRev = ownerGives;
1126 offer,
1127 ofrAdjAmtRev,
1128 stpAdjAmtRev,
1129 ownerGivesAdjRev,
1130 transferRateIn,
1131 transferRateOut,
1132 remainingOut);
1133
1134 if (stpAdjAmtRev.in == remainingIn)
1135 {
1136 result.in = in;
1137 result.out = cache_->out;
1138
1139 savedIns.clear();
1140 savedIns.insert(result.in);
1141 savedOuts.clear();
1142 savedOuts.insert(result.out);
1143
1144 ofrAdjAmt = ofrAdjAmtRev;
1145 stpAdjAmt.in = remainingIn;
1146 stpAdjAmt.out = remainingOut;
1147 ownerGivesAdj = ownerGivesAdjRev;
1148 }
1149 else
1150 {
1151 // This is (likely) a problem case, and will be caught
1152 // with later checks
1153 savedOuts.insert(lastOutAmt);
1154 }
1155 }
1156
1157 remainingIn = in - result.in;
1158 this->consumeOffer(sb, offer, ofrAdjAmt, stpAdjAmt, ownerGivesAdj);
1159
1160 // When the mantissas of two iou amounts differ by less than ten, then
1161 // subtracting them leaves a result of zero. This can cause the check
1162 // for (stpAmt.in > remainingIn) to incorrectly think an offer will be
1163 // funded after subtracting remainingIn.
1164 return processMore || offer.fully_consumed();
1165 };
1166
1167 {
1168 auto const prevStepDebtDir = [&] {
1169 if (prevStep_)
1170 return prevStep_->debtDirection(sb, StrandDirection::forward);
1171 return DebtDirection::issues;
1172 }();
1173 auto const r = forEachOffer(sb, afView, prevStepDebtDir, eachOffer);
1174 boost::container::flat_set<uint256> const toRm = std::move(std::get<0>(r));
1175 std::uint32_t const offersConsumed = std::get<1>(r);
1176 offersUsed_ = offersConsumed;
1177 SetUnion(ofrsToRm, toRm);
1178
1179 // Too many iterations, mark this strand as inactive (dry)
1180 if (offersConsumed >= MaxOffersToConsume)
1181 {
1182 inactive_ = true;
1183 }
1184 }
1185
1186 switch (remainingIn.signum())
1187 {
1188 case -1: {
1189 // LCOV_EXCL_START
1190 // something went very wrong
1191 JLOG(j_.error()) << "BookStep remainingIn < 0 " << to_string(remainingIn);
1192 UNREACHABLE("xrpl::BookStep::fwdImp : remaining less than zero");
1193 cache_.emplace(beast::zero, beast::zero);
1194 return {beast::zero, beast::zero};
1195 // LCOV_EXCL_STOP
1196 }
1197 case 0: {
1198 // due to normalization, remainingIn can be zero without
1199 // result.in == in. Force result.in == in for this case
1200 result.in = in;
1201 }
1202 }
1203
1204 cache_.emplace(result.in, result.out);
1205 return {result.in, result.out};
1206}
1207
1208template <class TIn, class TOut, class TDerived>
1211 PaymentSandbox& sb,
1212 ApplyView& afView,
1213 EitherAmount const& in)
1214{
1215 if (!cache_)
1216 {
1217 JLOG(j_.trace()) << "Expected valid cache in validFwd";
1218 return {false, EitherAmount(TOut(beast::zero))};
1219 }
1220
1221 auto const savCache = *cache_;
1222
1223 try
1224 {
1225 boost::container::flat_set<uint256> dummy;
1226 fwdImp(sb, afView, dummy, get<TIn>(in)); // changes cache
1227 }
1228 catch (FlowException const&)
1229 {
1230 return {false, EitherAmount(TOut(beast::zero))};
1231 }
1232
1233 if (!(checkNear(savCache.in, cache_->in) && checkNear(savCache.out, cache_->out)))
1234 {
1235 JLOG(j_.warn()) << "Strand re-execute check failed."
1236 << " ExpectedIn: " << to_string(savCache.in)
1237 << " CachedIn: " << to_string(cache_->in)
1238 << " ExpectedOut: " << to_string(savCache.out)
1239 << " CachedOut: " << to_string(cache_->out);
1240 return {false, EitherAmount(cache_->out)};
1241 }
1242 return {true, EitherAmount(cache_->out)};
1243}
1244
1245template <class TIn, class TOut, class TDerived>
1246TER
1248{
1249 if (book_.in == book_.out)
1250 {
1251 JLOG(j_.debug()) << "BookStep: Book with same in and out issuer " << *this;
1252 return temBAD_PATH;
1253 }
1254 if (!isConsistent(book_.in) || !isConsistent(book_.out))
1255 {
1256 JLOG(j_.debug()) << "Book: currency is inconsistent with issuer." << *this;
1257 return temBAD_PATH;
1258 }
1259
1260 // Do not allow two books to output the same issue. This may cause offers on
1261 // one step to unfund offers in another step.
1262 if (!ctx.seenBookOuts.insert(book_.out).second ||
1263 (ctx.seenDirectIssues[0].count(book_.out) != 0u))
1264 {
1265 JLOG(j_.debug()) << "BookStep: loop detected: " << *this;
1266 return temBAD_PATH_LOOP;
1267 }
1268
1269 if (ctx.seenDirectIssues[1].count(book_.out) != 0u)
1270 {
1271 JLOG(j_.debug()) << "BookStep: loop detected: " << *this;
1272 return temBAD_PATH_LOOP;
1273 }
1274
1275 auto issuerExists = [](ReadView const& view, Issue const& iss) -> bool {
1276 return isXRP(iss.account) || view.read(keylet::account(iss.account));
1277 };
1278
1279 if (!issuerExists(ctx.view, book_.in) || !issuerExists(ctx.view, book_.out))
1280 {
1281 JLOG(j_.debug()) << "BookStep: deleted issuer detected: " << *this;
1282 return tecNO_ISSUER;
1283 }
1284
1285 if (ctx.prevStep != nullptr)
1286 {
1287 if (auto const prev = ctx.prevStep->directStepSrcAcct())
1288 {
1289 auto const& view = ctx.view;
1290 auto const& cur = book_.in.account;
1291
1292 auto sle = view.read(keylet::line(*prev, cur, book_.in.currency));
1293 if (!sle)
1294 return terNO_LINE;
1295 if (((*sle)[sfFlags] & ((cur > *prev) ? lsfHighNoRipple : lsfLowNoRipple)) != 0u)
1296 return terNO_RIPPLE;
1297 }
1298 }
1299
1300 return tesSUCCESS;
1301}
1302
1303//------------------------------------------------------------------------------
1304
1305namespace test {
1306// Needed for testing
1307
1308template <class TIn, class TOut, class TDerived>
1309static bool
1310equalHelper(Step const& step, xrpl::Book const& book)
1311{
1312 if (auto bs = dynamic_cast<BookStep<TIn, TOut, TDerived> const*>(&step))
1313 return book == bs->book();
1314 return false;
1315}
1316
1317bool
1318bookStepEqual(Step const& step, xrpl::Book const& book)
1319{
1320 bool const inXRP = isXRP(book.in.currency);
1321 bool const outXRP = isXRP(book.out.currency);
1322 if (inXRP && outXRP)
1323 {
1324 // LCOV_EXCL_START
1325 UNREACHABLE("xrpl::test::bookStepEqual : no XRP to XRP book step");
1326 return false; // no such thing as xrp/xrp book step
1327 // LCOV_EXCL_STOP
1328 }
1329 if (inXRP && !outXRP)
1330 return equalHelper<XRPAmount, IOUAmount, BookPaymentStep<XRPAmount, IOUAmount>>(step, book);
1331 if (!inXRP && outXRP)
1332 return equalHelper<IOUAmount, XRPAmount, BookPaymentStep<IOUAmount, XRPAmount>>(step, book);
1333 if (!inXRP && !outXRP)
1334 return equalHelper<IOUAmount, IOUAmount, BookPaymentStep<IOUAmount, IOUAmount>>(step, book);
1335 return false;
1336}
1337} // namespace test
1338
1339//------------------------------------------------------------------------------
1340
1341template <class TIn, class TOut>
1344{
1345 TER ter = tefINTERNAL;
1347 if (ctx.offerCrossing)
1348 {
1349 auto offerCrossingStep = std::make_unique<BookOfferCrossingStep<TIn, TOut>>(ctx, in, out);
1350 ter = offerCrossingStep->check(ctx);
1351 r = std::move(offerCrossingStep);
1352 }
1353 else // payment
1354 {
1355 auto paymentStep = std::make_unique<BookPaymentStep<TIn, TOut>>(ctx, in, out);
1356 ter = paymentStep->check(ctx);
1357 r = std::move(paymentStep);
1358 }
1359 if (!isTesSuccess(ter))
1360 return {ter, nullptr};
1361
1362 return {tesSUCCESS, std::move(r)};
1363}
1364
1366make_BookStepII(StrandContext const& ctx, Issue const& in, Issue const& out)
1367{
1368 return make_BookStepHelper<IOUAmount, IOUAmount>(ctx, in, out);
1369}
1370
1373{
1374 return make_BookStepHelper<IOUAmount, XRPAmount>(ctx, in, xrpIssue());
1375}
1376
1379{
1380 return make_BookStepHelper<XRPAmount, IOUAmount>(ctx, xrpIssue(), out);
1381}
1382
1383} // namespace xrpl
T accumulate(T... args)
A generic endpoint for log messages.
Definition Journal.h:40
Stream error() const
Definition Journal.h:319
Stream debug() const
Definition Journal.h:301
Stream trace() const
Severity stream access functions.
Definition Journal.h:295
Stream warn() const
Definition Journal.h:313
AccountID account() const
Definition AMMContext.h:81
Represents synthetic AMM offer in BookStep.
Definition AMMOffer.h:21
Quality quality() const noexcept
Definition AMMOffer.h:50
Writeable view to a ledger, for applying a transaction.
Definition ApplyView.h:116
std::uint32_t getOfrInRate(Step const *prevStep, AccountID const &owner, std::uint32_t trIn) const
Definition BookStep.cpp:452
std::string logString() const override
Definition BookStep.cpp:520
bool checkQualityThreshold(Quality const &quality) const
Definition BookStep.cpp:425
Quality adjustQualityWithFees(ReadView const &v, Quality const &ofrQ, DebtDirection prevStepDir, WaiveTransferFee waiveFee, OfferType offerType, Rules const &rules) const
Definition BookStep.cpp:477
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:367
BookOfferCrossingStep(StrandContext const &ctx, Issue const &in, Issue const &out)
Definition BookStep.cpp:358
static Quality getQuality(std::optional< Quality > const &limitQuality)
Definition BookStep.cpp:348
std::optional< Quality > qualityThreshold(Quality const &lobQuality) const
Definition BookStep.cpp:441
Quality const qualityThreshold_
Definition BookStep.cpp:527
std::uint32_t getOfrOutRate(Step const *prevStep, AccountID const &owner, AccountID const &strandDst, std::uint32_t trOut) const
Definition BookStep.cpp:463
std::optional< Quality > qualityThreshold(Quality const &lobQuality) const
Definition BookStep.cpp:280
bool limitSelfCrossQuality(AccountID const &, AccountID const &, Offer< TIn, TOut > const &offer, std::optional< Quality > &, FlowOfferStream< TIn, TOut > &, bool) const
Definition BookStep.cpp:259
Quality adjustQualityWithFees(ReadView const &v, Quality const &ofrQ, DebtDirection prevStepDir, WaiveTransferFee waiveFee, OfferType, Rules const &) const
Definition BookStep.cpp:300
BookPaymentStep(StrandContext const &ctx, Issue const &in, Issue const &out)
Definition BookStep.cpp:248
std::uint32_t getOfrInRate(Step const *, AccountID const &, std::uint32_t trIn) const
Definition BookStep.cpp:287
std::uint32_t getOfrOutRate(Step const *, AccountID const &, AccountID const &, std::uint32_t trOut) const
Definition BookStep.cpp:294
std::string logString() const override
Definition BookStep.cpp:331
bool checkQualityThreshold(Quality const &quality) const
Definition BookStep.cpp:272
TER check(StrandContext const &ctx) const
AccountID strandSrc_
Definition BookStep.cpp:33
Step const *const prevStep_
Definition BookStep.cpp:36
friend bool operator==(BookStep const &lhs, BookStep const &rhs)
Definition BookStep.cpp:173
std::pair< boost::container::flat_set< uint256 >, std::uint32_t > forEachOffer(PaymentSandbox &sb, ApplyView &afView, DebtDirection prevStepDebtDir, Callback &callback) const
Definition BookStep.cpp:651
std::optional< Book > bookStepBook() const override
Definition BookStep.cpp:119
beast::Journal const j_
Definition BookStep.cpp:52
std::optional< std::variant< Quality, AMMOffer< TIn, TOut > > > tip(ReadView const &view) const
Definition BookStep.cpp:854
std::optional< QualityFunction > tipOfferQualityF(ReadView const &view) const
Definition BookStep.cpp:910
std::optional< EitherAmount > cachedIn() const override
Definition BookStep.cpp:97
std::optional< AMMOffer< TIn, TOut > > getAMMOffer(ReadView const &view, std::optional< Quality > const &clobQuality) const
Definition BookStep.cpp:843
bool const ownerPaysTransferFee_
Definition BookStep.cpp:37
DebtDirection debtDirection(ReadView const &sb, StrandDirection dir) const override
Definition BookStep.cpp:113
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:803
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:534
std::optional< Cache > cache_
Definition BookStep.cpp:64
std::uint32_t offersUsed_
Number of offers consumed or partially consumed the last time the step ran, including expired and unf...
Definition BookStep.cpp:47
std::optional< EitherAmount > cachedOut() const override
Definition BookStep.cpp:105
std::pair< TIn, TOut > revImp(PaymentSandbox &sb, ApplyView &afView, boost::container::flat_set< uint256 > &ofrsToRm, TOut const &out)
Definition BookStep.cpp:937
std::pair< std::optional< Quality >, DebtDirection > qualityUpperBound(ReadView const &v, DebtDirection prevStepDir) const override
Definition BookStep.cpp:543
bool inactive() const override
Definition BookStep.cpp:155
friend bool operator!=(BookStep const &lhs, BookStep const &rhs)
Definition BookStep.cpp:179
std::uint32_t offersUsed() const override
Definition BookStep.cpp:590
std::pair< std::optional< QualityFunction >, DebtDirection > getQualityFunc(ReadView const &v, DebtDirection prevStepDir) const override
Definition BookStep.cpp:561
BookStep(StrandContext const &ctx, Issue const &in, Issue const &out)
Definition BookStep.cpp:67
std::optional< AMMLiquidity< TIn, TOut > > ammLiquidity_
Definition BookStep.cpp:51
Book const & book() const
Definition BookStep.cpp:91
std::optional< std::pair< Quality, OfferType > > tipOfferQuality(ReadView const &view) const
Definition BookStep.cpp:892
AccountID strandDst_
Definition BookStep.cpp:34
static constexpr uint32_t MaxOffersToConsume
Definition BookStep.cpp:31
std::string logStringImpl(char const *name) const
Definition BookStep.cpp:162
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:13
Quality const & quality() const noexcept
Definition BookTip.h:44
Specifies an order book.
Definition Book.h:16
Issue in
Definition Book.h:18
Issue out
Definition Book.h:19
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,...
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 std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
Rules controlling protocol behavior.
Definition Rules.h:18
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:120
static std::uint64_t const uRateOne
Definition STAmount.h:62
Discardable, editable view to a ledger.
Definition Sandbox.h:15
A step in a payment path.
Definition Steps.h:65
virtual std::optional< AccountID > directStepSrcAcct() const
If this step is DirectStepI (IOU->IOU direct step), return the src account.
Definition Steps.h:124
virtual std::optional< Book > bookStepBook() const
If this step is a BookStep, return the book.
Definition Steps.h:200
Rules const & rules() const override
Returns the tx processing rules.
T is_same_v
T make_pair(T... args)
Keylet amm(Asset const &issue1, Asset const &issue2) noexcept
AMM entry.
Definition Indexes.cpp:404
Keylet line(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:220
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:165
bool 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
bool isConsistent(Book const &book)
Definition Book.cpp:10
@ terNO_LINE
Definition TER.h:199
@ terNO_RIPPLE
Definition TER.h:204
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
Definition Issue.h:97
static auto sum(TCollection const &col)
Definition BookStep.cpp:927
bool isXRP(AccountID const &c)
Definition AccountID.h:70
Quality composed_quality(Quality const &lhs, Quality const &rhs)
Definition Quality.cpp:113
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:627
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:602
STAmount toSTAmount(IOUAmount const &iou, Issue const &iss)
std::pair< TER, std::unique_ptr< Step > > make_BookStepXI(StrandContext const &ctx, Issue const &out)
@ tefINTERNAL
Definition TER.h:153
WaiveTransferFee
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:598
DebtDirection
Definition Steps.h:21
StrandDirection
Definition Steps.h:23
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
static bool isDefaultPath(STPath const &path)
IOUAmount mulRatio(IOUAmount const &amt, std::uint32_t num, std::uint32_t den, bool roundUp)
Rate transferRate(ReadView const &view, AccountID const &issuer)
Returns IOU issuer transfer fee as Rate.
std::uint64_t getRate(STAmount const &offerOut, STAmount const &offerIn)
Definition STAmount.cpp:450
std::pair< TER, std::unique_ptr< Step > > make_BookStepIX(StrandContext const &ctx, Issue const &in)
bool checkNear(IOUAmount const &expected, IOUAmount const &actual)
Definition PaySteps.cpp:14
@ tapNONE
Definition ApplyView.h:11
@ temBAD_PATH
Definition TER.h:76
@ temBAD_PATH_LOOP
Definition TER.h:77
bool isTesSuccess(TER x) noexcept
Definition TER.h:651
std::pair< TER, std::unique_ptr< Step > > make_BookStepII(StrandContext const &ctx, Issue const &in, Issue const &out)
@ tecINVARIANT_FAILED
Definition TER.h:294
@ tecNO_ISSUER
Definition TER.h:280
std::uint16_t getTradingFee(ReadView const &view, SLE const &ammSle, AccountID const &account)
Get AMM trading fee for the given account.
Definition AMMUtils.cpp:153
@ tesSUCCESS
Definition TER.h:225
Rate const parityRate
A transfer rate signifying a 1:1 exchange.
static std::pair< TER, std::unique_ptr< Step > > make_BookStepHelper(StrandContext const &ctx, Issue const &in, Issue const &out)
T str(T... args)
Cache(TIn const &in_, TOut const &out_)
Definition BookStep.cpp:59
uint256 key
Definition Keylet.h:20
std::uint32_t value
Definition Rate.h:21
Context needed to build Strand Steps and for error checking.
Definition Steps.h:504
beast::Journal const j
Definition Steps.h:532
boost::container::flat_set< Issue > & seenBookOuts
A strand may not include an offer that output the same issue more than once.
Definition Steps.h:529
AMMContext & ammContext
Definition Steps.h:530
ReadView const & view
Current ReadView.
Definition Steps.h:505
std::array< boost::container::flat_set< Issue >, 2 > & seenDirectIssues
A strand may not include the same account node more than once in the same currency.
Definition Steps.h:525
Step const *const prevStep
The previous step in the strand.
Definition Steps.h:519
OfferCrossing const offerCrossing
Yes/Sell if offer crossing, not payment.
Definition Steps.h:513