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