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