xrpld
Loading...
Searching...
No Matches
StrandFlow.h
1#pragma once
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/ledger/View.h>
5#include <xrpl/ledger/helpers/AMMHelpers.h>
6#include <xrpl/ledger/helpers/OfferHelpers.h>
7#include <xrpl/ledger/helpers/RippleStateHelpers.h>
8#include <xrpl/protocol/Feature.h>
9#include <xrpl/protocol/IOUAmount.h>
10#include <xrpl/protocol/XRPAmount.h>
11#include <xrpl/tx/paths/Flow.h>
12#include <xrpl/tx/paths/detail/FlatSets.h>
13#include <xrpl/tx/paths/detail/FlowDebugInfo.h>
14#include <xrpl/tx/paths/detail/Steps.h>
15#include <xrpl/tx/transactors/dex/AMMContext.h>
16
17#include <boost/container/flat_set.hpp>
18
19#include <algorithm>
20#include <iterator>
21#include <numeric>
22
23namespace xrpl {
24
26template <class TInAmt, class TOutAmt>
28{
29 bool success = false;
30 TInAmt in = beast::kZero;
31 TOutAmt out = beast::kZero;
33 boost::container::flat_set<uint256> ofrsToRm;
34 // Num offers consumed or partially consumed (includes expired and unfunded
35 // offers)
37 // strand can be inactive if there is no more liquidity or too many offers
38 // have been consumed
39 bool inactive = false;
41
43 StrandResult() = default;
44
46 Strand const& strand,
47 TInAmt const& in,
48 TOutAmt const& out,
50 boost::container::flat_set<uint256> ofrsToRemoveMember,
51 bool inactive)
52 : success(true)
53 , in(in)
54 , out(out)
55 , sandbox(std::move(sandbox))
56 , ofrsToRm(std::move(ofrsToRemoveMember))
57 , ofrsUsed(offersUsed(strand))
59 {
60 }
61
62 StrandResult(Strand const& strand, boost::container::flat_set<uint256> ofrsToRemoveMember)
63 : ofrsToRm(std::move(ofrsToRemoveMember)), ofrsUsed(offersUsed(strand))
64 {
65 }
66};
67
79template <class TInAmt, class TOutAmt>
80StrandResult<TInAmt, TOutAmt>
82 PaymentSandbox const& baseView,
83 Strand const& strand,
84 std::optional<TInAmt> const& maxIn,
85 TOutAmt const& out,
87{
89 if (strand.empty())
90 {
91 JLOG(j.warn()) << "Empty strand passed to Liquidity";
92 return {};
93 }
94
95 boost::container::flat_set<uint256> ofrsToRm;
96
97 if (isDirectXrpToXrp<TInAmt, TOutAmt>(strand))
98 {
99 return Result{strand, std::move(ofrsToRm)};
100 }
101
102 try
103 {
104 std::size_t const s = strand.size();
105
106 std::size_t limitingStep = strand.size();
107 std::optional<PaymentSandbox> sb(&baseView);
108 // The "all funds" view determines if an offer becomes unfunded or is
109 // found unfunded
110 // These are the account balances before the strand executes
111 std::optional<PaymentSandbox> afView(&baseView);
113 {
114 EitherAmount stepOut(out);
115 for (auto i = s; i--;)
116 {
117 auto r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
118 if (strand[i]->isZero(r.second))
119 {
120 JLOG(j.trace()) << "Strand found dry in rev";
121 return Result{strand, std::move(ofrsToRm)};
122 }
123
124 if (i == 0 && maxIn && *maxIn < get<TInAmt>(r.first))
125 {
126 // limiting - exceeded maxIn
127 // Throw out previous results
128 sb.emplace(&baseView);
129 limitingStep = i;
130
131 // re-execute the limiting step
132 r = strand[i]->fwd(*sb, *afView, ofrsToRm, EitherAmount(*maxIn));
133 limitStepOut = r.second;
134
135 if (strand[i]->isZero(r.second))
136 {
137 JLOG(j.trace()) << "First step found dry";
138 return Result{strand, std::move(ofrsToRm)};
139 }
140 if (get<TInAmt>(r.first) != *maxIn)
141 {
142 // Something is very wrong
143 // throwing out the sandbox can only increase liquidity
144 // yet the limiting is still limiting
145 // LCOV_EXCL_START
146 JLOG(j.fatal())
147 << "Re-executed limiting step failed. r.first: "
148 << to_string(get<TInAmt>(r.first)) << " maxIn: " << to_string(*maxIn);
149 UNREACHABLE(
150 "xrpl::flow : first step re-executing the "
151 "limiting step failed");
152 return Result{strand, std::move(ofrsToRm)};
153 // LCOV_EXCL_STOP
154 }
155 }
156 else if (!strand[i]->equalOut(r.second, stepOut))
157 {
158 // limiting
159 // Throw out previous results
160 sb.emplace(&baseView);
161 afView.emplace(&baseView);
162 limitingStep = i;
163
164 // re-execute the limiting step
165 stepOut = r.second;
166 r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
167 limitStepOut = r.second;
168
169 if (strand[i]->isZero(r.second))
170 {
171 // A tiny input amount can cause this step to output
172 // zero. I.e. 10^-80 IOU into an IOU -> XRP offer.
173 JLOG(j.trace()) << "Limiting step found dry";
174 return Result{strand, std::move(ofrsToRm)};
175 }
176 if (!strand[i]->equalOut(r.second, stepOut))
177 {
178 // Something is very wrong
179 // throwing out the sandbox can only increase liquidity
180 // yet the limiting is still limiting
181 // LCOV_EXCL_START
182#ifndef NDEBUG
183 JLOG(j.fatal())
184 << "Re-executed limiting step failed. r.second: " << r.second
185 << " stepOut: " << stepOut;
186#else
187 JLOG(j.fatal()) << "Re-executed limiting step failed";
188#endif
189 UNREACHABLE(
190 "xrpl::flow : limiting step re-executing the "
191 "limiting step failed");
192 return Result{strand, std::move(ofrsToRm)};
193 // LCOV_EXCL_STOP
194 }
195 }
196
197 // prev node needs to produce what this node wants to consume
198 stepOut = r.first;
199 }
200 }
201
202 {
204 for (auto i = limitingStep + 1; i < s; ++i)
205 {
206 auto const r = strand[i]->fwd(*sb, *afView, ofrsToRm, stepIn);
207 if (strand[i]->isZero(r.second))
208 {
209 // A tiny input amount can cause this step to output zero.
210 // I.e. 10^-80 IOU into an IOU -> XRP offer.
211 JLOG(j.trace()) << "Non-limiting step found dry";
212 return Result{strand, std::move(ofrsToRm)};
213 }
214 if (!strand[i]->equalIn(r.first, stepIn))
215 {
216 // The limits should already have been found, so executing a
217 // strand forward from the limiting step should not find a
218 // new limit
219 // LCOV_EXCL_START
220#ifndef NDEBUG
221 JLOG(j.fatal()) << "Re-executed forward pass failed. r.first: " << r.first
222 << " stepIn: " << stepIn;
223#else
224 JLOG(j.fatal()) << "Re-executed forward pass failed";
225#endif
226 UNREACHABLE(
227 "xrpl::flow : non-limiting step re-executing the "
228 "forward pass failed");
229 return Result{strand, std::move(ofrsToRm)};
230 // LCOV_EXCL_STOP
231 }
232 stepIn = r.second;
233 }
234 }
235
236 // NOLINTBEGIN(bugprone-unchecked-optional-access) cachedIn/Out set after strand is stepped
237 // above
238 auto const strandIn = *strand.front()->cachedIn();
239 auto const strandOut = *strand.back()->cachedOut();
240 // NOLINTEND(bugprone-unchecked-optional-access)
241
242#ifndef NDEBUG
243 {
244 // Check that the strand will execute as intended
245 // Re-executing the strand will change the cached values
246 PaymentSandbox checkSB(&baseView);
247 PaymentSandbox checkAfView(&baseView);
248 EitherAmount stepIn(
249 *strand[0]->cachedIn()); // NOLINT(bugprone-unchecked-optional-access)
250 for (auto i = 0; i < s; ++i)
251 {
252 bool valid = false;
253 std::tie(valid, stepIn) = strand[i]->validFwd(checkSB, checkAfView, stepIn);
254 if (!valid)
255 {
256 JLOG(j.warn()) << "Strand re-execute check failed. Step: " << i;
257 break;
258 }
259 }
260 }
261#endif
262
263 bool const inactive =
264 std::any_of(strand.begin(), strand.end(), [](std::unique_ptr<Step> const& step) {
265 return step->inactive();
266 });
267
268 return Result(
269 strand,
270 get<TInAmt>(strandIn),
271 get<TOutAmt>(strandOut),
272 std::move(*sb),
273 std::move(ofrsToRm),
274 inactive);
275 }
276 catch (FlowException const&)
277 {
278 return Result{strand, std::move(ofrsToRm)};
279 }
280}
281
283template <class TInAmt, class TOutAmt>
284struct FlowResult
285{
286 TInAmt in = beast::kZero;
287 TOutAmt out = beast::kZero;
289 boost::container::flat_set<uint256> removableOffers;
290 TER ter = temUNKNOWN;
291
292 FlowResult() = default;
293
294 FlowResult(
295 TInAmt const& in,
296 TOutAmt const& out,
297 PaymentSandbox&& sandbox,
298 boost::container::flat_set<uint256> ofrsToRm)
299 : in(in)
300 , out(out)
301 , sandbox(std::move(sandbox))
302 , removableOffers(std::move(ofrsToRm))
303 , ter(tesSUCCESS)
304 {
305 }
306
307 FlowResult(TER ter, boost::container::flat_set<uint256> ofrsToRm)
308 : removableOffers(std::move(ofrsToRm)), ter(ter)
309 {
310 }
311
312 FlowResult(
313 TER ter,
314 TInAmt const& in,
315 TOutAmt const& out,
316 boost::container::flat_set<uint256> ofrsToRm)
317 : in(in), out(out), removableOffers(std::move(ofrsToRm)), ter(ter)
318 {
319 }
320};
322
324inline std::optional<Quality>
325qualityUpperBound(ReadView const& v, Strand const& strand)
326{
328 std::optional<Quality> stepQ;
330 for (auto const& step : strand)
331 {
332 if (std::tie(stepQ, dir) = step->qualityUpperBound(v, dir); stepQ)
333 {
334 q = composedQuality(q, *stepQ);
335 }
336 else
337 {
338 return std::nullopt;
339 }
340 }
341 return q;
342};
344
346
354template <typename TOutAmt>
355inline TOutAmt
356limitOut(
357 ReadView const& v,
358 Strand const& strand,
359 TOutAmt const& remainingOut,
360 Quality const& limitQuality)
361{
362 std::optional<QualityFunction> stepQualityFunc;
363 std::optional<QualityFunction> qf;
365 for (auto const& step : strand)
366 {
367 if (std::tie(stepQualityFunc, dir) = step->getQualityFunc(v, dir); stepQualityFunc)
368 {
369 if (!qf)
370 {
371 qf = stepQualityFunc;
372 }
373 else
374 {
375 qf->combine(*stepQualityFunc);
376 }
377 }
378 else
379 {
380 return remainingOut;
381 }
382 }
383
384 // QualityFunction is constant
385 if (!qf || qf->isConst())
386 return remainingOut;
387
388 auto const out = [&]() {
389 auto const out = qf->outFromAvgQ(limitQuality);
390 if (!out)
391 return remainingOut;
393 {
394 return XRPAmount{*out};
395 }
396 else if constexpr (std::is_same_v<TOutAmt, IOUAmount>)
397 {
398 return IOUAmount{*out};
399 }
400 else if constexpr (std::is_same_v<TOutAmt, MPTAmount>)
401 {
402 return MPTAmount{*out};
403 }
404 else
405 {
406 return STAmount{remainingOut.asset(), out->mantissa(), out->exponent()};
407 }
408 }();
409 // A tiny difference could be due to the round off
410 if (withinRelativeDistance(out, remainingOut, Number(1, -9)))
411 return remainingOut;
412 return std::min(out, remainingOut);
413};
415
417/* Track the non-dry strands
418
419 flow will search the non-dry strands (stored in `cur_`) for the best
420 available liquidity If flow doesn't use all the liquidity of a strand, that
421 strand is added to `next_`. The strands in `next_` are searched after the
422 current best liquidity is used.
423 */
424class ActiveStrands
425{
426private:
427 // Strands to be explored for liquidity
428 std::vector<Strand const*> cur_;
429 // Strands that may be explored for liquidity on the next iteration
430 std::vector<Strand const*> next_;
431
432public:
433 ActiveStrands(std::vector<Strand> const& strands)
434 {
435 cur_.reserve(strands.size());
436 next_.reserve(strands.size());
437 for (auto& strand : strands)
438 next_.push_back(&strand);
439 }
440
441 // Start a new iteration in the search for liquidity
442 // Set the current strands to the strands in `next_`
443 void
444 activateNext(ReadView const& v, std::optional<Quality> const& limitQuality)
445 {
446 // add the strands in `next_` to `cur_`, sorted by theoretical quality.
447 // Best quality first.
448 cur_.clear();
449 if (!next_.empty())
450 {
451 std::vector<std::pair<Quality, Strand const*>> strandQualities;
452 strandQualities.reserve(next_.size());
453 if (next_.size() > 1) // no need to sort one strand
454 {
455 for (Strand const* strand : next_)
456 {
457 if (strand == nullptr)
458 {
459 // should not happen
460 continue;
461 }
462 if (auto const qual = qualityUpperBound(v, *strand))
463 {
464 if (limitQuality && *qual < *limitQuality)
465 {
466 // If a strand's quality is ever over limitQuality
467 // it is no longer part of the candidate set. Note
468 // that when transfer fees are charged, and an
469 // account goes from redeeming to issuing then
470 // strand quality _can_ increase; However, this is
471 // an unusual corner case.
472 continue;
473 }
474 strandQualities.emplace_back(*qual, strand);
475 }
476 }
477 // must stable sort for deterministic order across different c++
478 // standard library implementations
480 strandQualities,
481
482 [](auto const& lhs, auto const& rhs) {
483 // higher qualities first
484 return std::get<Quality>(lhs) > std::get<Quality>(rhs);
485 });
486 next_.clear();
487 next_.reserve(strandQualities.size());
488 for (auto const& sq : strandQualities)
489 {
490 next_.push_back(std::get<Strand const*>(sq));
491 }
492 }
493 }
494 std::swap(cur_, next_);
495 }
496
497 [[nodiscard]] Strand const*
498 get(size_t i) const
499 {
500 if (i >= cur_.size())
501 {
502 // LCOV_EXCL_START
503 UNREACHABLE("xrpl::ActiveStrands::get : input out of range");
504 return nullptr;
505 // LCOV_EXCL_STOP
506 }
507 return cur_[i];
508 }
509
510 void
511 push(Strand const* s)
512 {
513 next_.push_back(s);
514 }
515
516 // Push the strands from index i to the end of cur_ to next_
517 void
518 pushRemainingCurToNext(size_t i)
519 {
520 if (i >= cur_.size())
521 return;
522 next_.insert(next_.end(), std::next(cur_.begin(), i), cur_.end());
523 }
524
525 [[nodiscard]] auto
526 size() const
527 {
528 return cur_.size();
529 }
530};
532
553template <StepAmount TInAmt, StepAmount TOutAmt>
554FlowResult<TInAmt, TOutAmt>
556 PaymentSandbox const& baseView,
557 std::vector<Strand> const& strands,
558 TOutAmt const& outReq,
559 bool partialPayment,
560 OfferCrossing offerCrossing,
561 std::optional<Quality> const& limitQuality,
562 std::optional<STAmount> const& sendMaxST,
564 AMMContext& ammContext,
565 path::detail::FlowDebugInfo* flowDebugInfo = nullptr)
566{
567 // Used to track the strand that offers the best quality (output/input
568 // ratio)
569 struct BestStrand
570 {
571 TInAmt in;
572 TOutAmt out;
574 Strand const& strand;
575 Quality quality;
576
577 BestStrand(
578 TInAmt const& in,
579 TOutAmt const& out,
580 PaymentSandbox&& sb,
581 Strand const& strand,
582 Quality const& quality)
583 : in(in), out(out), sb(std::move(sb)), strand(strand), quality(quality)
584 {
585 }
586 };
587
588 std::size_t const maxTries = 1000;
589 std::size_t curTry = 0;
590 std::uint32_t const maxOffersToConsider = 1500;
591 std::uint32_t offersConsidered = 0;
592
593 // There is a bug in gcc that incorrectly warns about using uninitialized
594 // values if `remainingIn` is initialized through a copy constructor. We can
595 // get similar warnings for `sendMax` if it is initialized in the most
596 // natural way. Using `make_optional`, allows us to work around this bug.
597 TInAmt const sendMaxInit = sendMaxST ? toAmount<TInAmt>(*sendMaxST) : TInAmt{beast::kZero};
598 std::optional<TInAmt> const sendMax =
599 (sendMaxST && sendMaxInit >= beast::kZero) ? std::make_optional(sendMaxInit) : std::nullopt;
600 std::optional<TInAmt> remainingIn = !!sendMax ? std::make_optional(sendMaxInit) : std::nullopt;
601 // std::optional<TInAmt> remainingIn{sendMax};
602
603 TOutAmt remainingOut(outReq);
604
605 PaymentSandbox sb(&baseView);
606
607 // non-dry strands
608 ActiveStrands activeStrands(strands);
609
610 // Keeping a running sum of the amount in the order they are processed
611 // will not give the best precision. Keep a collection so they may be summed
612 // from smallest to largest
613 boost::container::flat_multiset<TInAmt> savedIns;
614 savedIns.reserve(maxTries);
615 boost::container::flat_multiset<TOutAmt> savedOuts;
616 savedOuts.reserve(maxTries);
617
618 auto sum = [](auto const& col) {
619 using TResult = std::decay_t<decltype(*col.begin())>;
620 if (col.empty())
621 return TResult{beast::kZero};
622 return std::accumulate(col.begin() + 1, col.end(), *col.begin());
623 };
624
625 // These offers only need to be removed if the payment is not
626 // successful
627 boost::container::flat_set<uint256> ofrsToRmOnFail;
628
629 while (remainingOut > beast::kZero && (!remainingIn || *remainingIn > beast::kZero))
630 {
631 ++curTry;
632 if (curTry >= maxTries)
633 {
634 return {telFAILED_PROCESSING, std::move(ofrsToRmOnFail)};
635 }
636
637 activeStrands.activateNext(sb, limitQuality);
638
639 ammContext.setMultiPath(activeStrands.size() > 1);
640
641 // Limit only if one strand and limitQuality
642 auto const limitRemainingOut = [&]() {
643 if (activeStrands.size() == 1 && limitQuality)
644 {
645 if (auto const strand = activeStrands.get(0))
646 return limitOut(sb, *strand, remainingOut, *limitQuality);
647 }
648 return remainingOut;
649 }();
650 auto const adjustedRemOut = limitRemainingOut != remainingOut;
651
652 boost::container::flat_set<uint256> ofrsToRm;
654 if (flowDebugInfo)
655 flowDebugInfo->newLiquidityPass();
656 for (size_t strandIndex = 0, sie = activeStrands.size(); strandIndex != sie; ++strandIndex)
657 {
658 Strand const* strand = activeStrands.get(strandIndex);
659 if (!strand)
660 {
661 // should not happen
662 continue;
663 }
664 // Clear AMM liquidity used flag. The flag might still be set if
665 // the previous strand execution failed. It has to be reset
666 // since this strand might not have AMM liquidity.
667 ammContext.clear();
668 if (offerCrossing != OfferCrossing::No && limitQuality)
669 {
670 auto const strandQ = qualityUpperBound(sb, *strand);
671 if (!strandQ || *strandQ < *limitQuality)
672 continue;
673 }
674 auto f = flow<TInAmt, TOutAmt>(sb, *strand, remainingIn, limitRemainingOut, j);
675
676 // rm bad offers even if the strand fails
677 setUnion(ofrsToRm, f.ofrsToRm);
678
679 offersConsidered += f.ofrsUsed;
680
681 if (!f.success || f.out == beast::kZero)
682 continue;
683
684 if (flowDebugInfo)
685 flowDebugInfo->pushLiquiditySrc(EitherAmount(f.in), EitherAmount(f.out));
686
687 XRPL_ASSERT(
688 f.out <= remainingOut && f.sandbox && (!remainingIn || f.in <= *remainingIn),
689 "xrpl::flow : remaining constraints");
690
691 Quality const q(f.out, f.in);
692
693 JLOG(j.trace()) << "New flow iter (iter, in, out): " << curTry - 1 << " "
694 << to_string(f.in) << " " << to_string(f.out);
695
696 // limitOut() finds output to generate exact requested
697 // limitQuality. But the actual limit quality might be slightly
698 // off due to the round off.
699 if (limitQuality && q < *limitQuality &&
700 (!adjustedRemOut || !withinRelativeDistance(q, *limitQuality, Number(1, -7))))
701 {
702 JLOG(j.trace()) << "Path rejected by limitQuality"
703 << " limit: " << *limitQuality << " path q: " << q;
704 continue;
705 }
706
707 XRPL_ASSERT(!best, "xrpl::flow : best is unset");
708 if (!f.inactive)
709 activeStrands.push(strand);
710 best.emplace(f.in, f.out, std::move(*f.sandbox), *strand, q);
711 activeStrands.pushRemainingCurToNext(strandIndex + 1);
712 break;
713 }
714
715 bool const shouldBreak = !best || offersConsidered >= maxOffersToConsider;
716
717 if (best)
718 {
719 savedIns.insert(best->in);
720 savedOuts.insert(best->out);
721 remainingOut = outReq - sum(savedOuts);
722 if (sendMax)
723 remainingIn = *sendMax - sum(savedIns);
724
725 if (flowDebugInfo)
726 {
727 flowDebugInfo->pushPass(
728 EitherAmount(best->in), EitherAmount(best->out), activeStrands.size());
729 }
730
731 JLOG(j.trace()) << "Best path: in: " << to_string(best->in)
732 << " out: " << to_string(best->out)
733 << " remainingOut: " << to_string(remainingOut);
734
735 best->sb.apply(sb);
736 ammContext.update();
737 }
738 else
739 {
740 JLOG(j.trace()) << "All strands dry.";
741 }
742
743 best.reset(); // view in best must be destroyed before modifying base view
744 if (!ofrsToRm.empty())
745 {
746 setUnion(ofrsToRmOnFail, ofrsToRm);
747 for (auto const& o : ofrsToRm)
748 {
749 if (auto ok = sb.peek(keylet::offer(o)))
750 offerDelete(sb, ok, j);
751 }
752 }
753
754 if (shouldBreak)
755 break;
756 }
757
758 auto const actualOut = sum(savedOuts);
759 auto const actualIn = sum(savedIns);
760
761 JLOG(j.trace()) << "Total flow: in: " << to_string(actualIn)
762 << " out: " << to_string(actualOut);
763
764 /* flowCross doesn't handle offer crossing with tfFillOrKill flag correctly.
765 * 1. If tfFillOrKill is set then the owner must receive the full
766 * TakerPays. We reverse pays and gets because during crossing
767 * we are taking, therefore the owner must deliver the full TakerPays and
768 * the entire TakerGets doesn't have to be spent.
769 * Pre-fixFillOrKill amendment code fails if the entire TakerGets
770 * is not spent. fixFillOrKill addresses this issue.
771 * 2. If tfSell is also set then the owner must spend the entire TakerGets
772 * even if it means obtaining more than TakerPays. Since the pays and gets
773 * are reversed, the owner must send the entire TakerGets.
774 */
775 bool const fillOrKillEnabled = baseView.rules().enabled(fixFillOrKill);
776
777 if (actualOut != outReq)
778 {
779 if (actualOut > outReq)
780 {
781 // Rounding in the payment engine is causing this assert to sometimes fire with "dust"
782 // amounts. This is causing issues when running debug builds of xrpld.
783 // While this issue still needs to be resolved, the assert is causing more harm than
784 // good at this point.
785 // UNREACHABLE("xrpl::flow : rounding error");
786
787 return {tefEXCEPTION, std::move(ofrsToRmOnFail)};
788 }
789 if (!partialPayment)
790 {
791 // If we're offerCrossing a !partialPayment, then we're handling tfFillOrKill.
792 // Pre-fixFillOrKill amendment:
793 // That case is handled below; not here.
794 // fixFillOrKill amendment:
795 // That case is handled here if tfSell is also not set; i.e, case 1.
796 if (offerCrossing == OfferCrossing::No ||
797 (fillOrKillEnabled && offerCrossing != OfferCrossing::Sell))
798 {
799 return {tecPATH_PARTIAL, actualIn, actualOut, std::move(ofrsToRmOnFail)};
800 }
801 }
802 else if (actualOut == beast::kZero)
803 {
804 return {tecPATH_DRY, std::move(ofrsToRmOnFail)};
805 }
806 }
807 if (offerCrossing != OfferCrossing::No &&
808 (!partialPayment && (!fillOrKillEnabled || offerCrossing == OfferCrossing::Sell)))
809 {
810 // If we're offer crossing and partialPayment is *not* true, then
811 // we're handling a FillOrKill offer. In this case remainingIn must
812 // be zero (all funds must be consumed) or else we kill the offer.
813 // Pre-fixFillOrKill amendment:
814 // Handles both cases 1. and 2.
815 // fixFillOrKill amendment:
816 // Handles 2. 1. is handled above and falls through for tfSell.
817 XRPL_ASSERT(remainingIn, "xrpl::flow : nonzero remainingIn");
818 if (remainingIn && *remainingIn != beast::kZero)
819 return {tecPATH_PARTIAL, actualIn, actualOut, std::move(ofrsToRmOnFail)};
820 }
821
822 return {actualIn, actualOut, std::move(sb), std::move(ofrsToRmOnFail)};
823}
824
825} // namespace xrpl
T accumulate(T... args)
T any_of(T... args)
T begin(T... args)
A generic endpoint for log messages.
Definition Journal.h:38
Stream fatal() const
Definition Journal.h:321
Stream trace() const
Severity stream access functions.
Definition Journal.h:291
Stream warn() const
Definition Journal.h:309
Maintains AMM info per overall payment engine execution and individual iteration.
Definition AMMContext.h:16
void setMultiPath(bool fs)
Definition AMMContext.h:50
void clear()
Strand execution may fail.
Definition AMMContext.h:91
Floating point representation of amounts with high dynamic range.
Definition IOUAmount.h:24
Number is a floating point type that can represent a wide range of values.
Definition Number.h:306
A wrapper which makes credits unavailable to balances.
Represents the logical ratio of output currency to input currency.
Definition Quality.h:91
A view into a ledger.
Definition ReadView.h:31
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:171
static std::uint64_t const kURateOne
Definition STAmount.h:64
SLE::pointer peek(Keylet const &k) override
Prepare to modify the SLE associated with key.
Rules const & rules() const override
Returns the tx processing rules.
T clear(T... args)
T emplace_back(T... args)
T emplace(T... args)
T empty(T... args)
T end(T... args)
T is_same_v
T make_optional(T... args)
T min(T... args)
T move(T... args)
STL namespace.
TER valid(STTx const &tx, ReadView const &view, AccountID const &src, beast::Journal j)
Keylet offer(AccountID const &id, std::uint32_t seq) noexcept
An offer from an account.
Definition Indexes.cpp:264
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ telFAILED_PROCESSING
Definition TER.h:40
static auto sum(TCollection const &col)
Definition BookStep.cpp:993
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:662
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
void setUnion(boost::container::flat_set< T > &dst, boost::container::flat_set< T > const &src)
Given two flat sets dst and src, compute dst = dst union src.
Definition FlatSets.h:15
@ tefEXCEPTION
Definition TER.h:162
DebtDirection
Definition Steps.h:21
boost::outcome_v2::result< T, std::error_code > Result
Definition b58_utils.h:17
TER offerDelete(ApplyView &view, SLE::ref sle, beast::Journal j)
Delete an offer.
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
T toAmount(STAmount const &amt)=delete
StrandResult< TInAmt, TOutAmt > flow(PaymentSandbox const &baseView, Strand const &strand, std::optional< TInAmt > const &maxIn, TOutAmt const &out, beast::Journal j)
Request out amount from a strand.
Definition StrandFlow.h:81
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Definition AMMHelpers.h:115
@ tecPATH_PARTIAL
Definition TER.h:280
@ tecPATH_DRY
Definition TER.h:292
OfferCrossing
Definition Steps.h:24
Quality composedQuality(Quality const &lhs, Quality const &rhs)
Calculate the quality of a two-hop path given the two hops.
Definition Quality.cpp:114
T next(T... args)
T push_back(T... args)
T reserve(T... args)
T reset(T... args)
T size(T... args)
T stable_sort(T... args)
Result of flow() execution of a single Strand.
Definition StrandFlow.h:28
TOutAmt out
Currency amount out.
Definition StrandFlow.h:31
boost::container::flat_set< uint256 > ofrsToRm
Offers to remove.
Definition StrandFlow.h:33
std::optional< PaymentSandbox > sandbox
Resulting Sandbox state.
Definition StrandFlow.h:32
bool success
Strand succeeded.
Definition StrandFlow.h:29
std::uint32_t ofrsUsed
Definition StrandFlow.h:36
StrandResult()=default
Strand result constructor.
StrandResult(Strand const &strand, TInAmt const &in, TOutAmt const &out, PaymentSandbox &&sandbox, boost::container::flat_set< uint256 > ofrsToRemoveMember, bool inactive)
Definition StrandFlow.h:45
StrandResult(Strand const &strand, boost::container::flat_set< uint256 > ofrsToRemoveMember)
Definition StrandFlow.h:62
TInAmt in
Currency amount in.
Definition StrandFlow.h:30
bool inactive
Strand should not considered as a further source of liquidity (dry).
Definition StrandFlow.h:39
T swap(T... args)
T tie(T... args)