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