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