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