rippled
Loading...
Searching...
No Matches
StrandFlow.h
1#pragma once
2
3#include <xrpld/app/misc/AMMHelpers.h>
4#include <xrpld/app/paths/AMMContext.h>
5#include <xrpld/app/paths/Flow.h>
6#include <xrpld/app/paths/detail/AmountSpec.h>
7#include <xrpld/app/paths/detail/FlatSets.h>
8#include <xrpld/app/paths/detail/FlowDebugInfo.h>
9#include <xrpld/app/paths/detail/Steps.h>
10
11#include <xrpl/basics/Log.h>
12#include <xrpl/ledger/Credit.h>
13#include <xrpl/protocol/Feature.h>
14#include <xrpl/protocol/IOUAmount.h>
15#include <xrpl/protocol/XRPAmount.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;
30 TInAmt in = beast::zero;
31 TOutAmt out = beast::zero;
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_,
49 PaymentSandbox&& sandbox_,
50 boost::container::flat_set<uint256> ofrsToRm_,
51 bool inactive_)
52 : success(true)
53 , in(in_)
54 , out(out_)
55 , sandbox(std::move(sandbox_))
56 , ofrsToRm(std::move(ofrsToRm_))
57 , ofrsUsed(offersUsed(strand))
58 , inactive(inactive_)
59 {
60 }
61
62 StrandResult(Strand const& strand, boost::container::flat_set<uint256> ofrsToRm_)
63 : success(false), ofrsToRm(std::move(ofrsToRm_)), 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()) << "Re-executed limiting step failed. r.first: "
147 << to_string(get<TInAmt>(r.first)) << " maxIn: " << to_string(*maxIn);
148 UNREACHABLE(
149 "xrpl::flow : first step re-executing the "
150 "limiting step failed");
151 return Result{strand, std::move(ofrsToRm)};
152 // LCOV_EXCL_STOP
153 }
154 }
155 else if (!strand[i]->equalOut(r.second, stepOut))
156 {
157 // limiting
158 // Throw out previous results
159 sb.emplace(&baseView);
160 afView.emplace(&baseView);
161 limitingStep = i;
162
163 // re-execute the limiting step
164 stepOut = r.second;
165 r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
166 limitStepOut = r.second;
167
168 if (strand[i]->isZero(r.second))
169 {
170 // A tiny input amount can cause this step to output
171 // zero. I.e. 10^-80 IOU into an IOU -> XRP offer.
172 JLOG(j.trace()) << "Limiting step found dry";
173 return Result{strand, std::move(ofrsToRm)};
174 }
175 if (!strand[i]->equalOut(r.second, stepOut))
176 {
177 // Something is very wrong
178 // throwing out the sandbox can only increase liquidity
179 // yet the limiting is still limiting
180 // LCOV_EXCL_START
181#ifndef NDEBUG
182 JLOG(j.fatal()) << "Re-executed limiting step failed. r.second: " << r.second
183 << " stepOut: " << stepOut;
184#else
185 JLOG(j.fatal()) << "Re-executed limiting step failed";
186#endif
187 UNREACHABLE(
188 "xrpl::flow : limiting step re-executing the "
189 "limiting step failed");
190 return Result{strand, std::move(ofrsToRm)};
191 // LCOV_EXCL_STOP
192 }
193 }
194
195 // prev node needs to produce what this node wants to consume
196 stepOut = r.first;
197 }
198 }
199
200 {
202 for (auto i = limitingStep + 1; i < s; ++i)
203 {
204 auto const r = strand[i]->fwd(*sb, *afView, ofrsToRm, stepIn);
205 if (strand[i]->isZero(r.second))
206 {
207 // A tiny input amount can cause this step to output zero.
208 // I.e. 10^-80 IOU into an IOU -> XRP offer.
209 JLOG(j.trace()) << "Non-limiting step found dry";
210 return Result{strand, std::move(ofrsToRm)};
211 }
212 if (!strand[i]->equalIn(r.first, stepIn))
213 {
214 // The limits should already have been found, so executing a
215 // strand forward from the limiting step should not find a
216 // new limit
217 // LCOV_EXCL_START
218#ifndef NDEBUG
219 JLOG(j.fatal()) << "Re-executed forward pass failed. r.first: " << r.first << " stepIn: " << stepIn;
220#else
221 JLOG(j.fatal()) << "Re-executed forward pass failed";
222#endif
223 UNREACHABLE(
224 "xrpl::flow : non-limiting step re-executing the "
225 "forward pass failed");
226 return Result{strand, std::move(ofrsToRm)};
227 // LCOV_EXCL_STOP
228 }
229 stepIn = r.second;
230 }
231 }
232
233 auto const strandIn = *strand.front()->cachedIn();
234 auto const strandOut = *strand.back()->cachedOut();
235
236#ifndef NDEBUG
237 {
238 // Check that the strand will execute as intended
239 // Re-executing the strand will change the cached values
240 PaymentSandbox checkSB(&baseView);
241 PaymentSandbox checkAfView(&baseView);
242 EitherAmount stepIn(*strand[0]->cachedIn());
243 for (auto i = 0; i < s; ++i)
244 {
245 bool valid;
246 std::tie(valid, stepIn) = strand[i]->validFwd(checkSB, checkAfView, stepIn);
247 if (!valid)
248 {
249 JLOG(j.warn()) << "Strand re-execute check failed. Step: " << i;
250 break;
251 }
252 }
253 }
254#endif
255
256 bool const inactive = std::any_of(
257 strand.begin(), strand.end(), [](std::unique_ptr<Step> const& step) { return step->inactive(); });
258
259 return Result(
260 strand, get<TInAmt>(strandIn), get<TOutAmt>(strandOut), std::move(*sb), std::move(ofrsToRm), inactive);
261 }
262 catch (FlowException const&)
263 {
264 return Result{strand, std::move(ofrsToRm)};
265 }
266}
267
269template <class TInAmt, class TOutAmt>
270struct FlowResult
271{
272 TInAmt in = beast::zero;
273 TOutAmt out = beast::zero;
275 boost::container::flat_set<uint256> removableOffers;
276 TER ter = temUNKNOWN;
277
278 FlowResult() = default;
279
280 FlowResult(
281 TInAmt const& in_,
282 TOutAmt const& out_,
283 PaymentSandbox&& sandbox_,
284 boost::container::flat_set<uint256> ofrsToRm)
285 : in(in_), out(out_), sandbox(std::move(sandbox_)), removableOffers(std::move(ofrsToRm)), ter(tesSUCCESS)
286 {
287 }
288
289 FlowResult(TER ter_, boost::container::flat_set<uint256> ofrsToRm) : removableOffers(std::move(ofrsToRm)), ter(ter_)
290 {
291 }
292
293 FlowResult(TER ter_, TInAmt const& in_, TOutAmt const& out_, boost::container::flat_set<uint256> ofrsToRm)
294 : in(in_), out(out_), removableOffers(std::move(ofrsToRm)), ter(ter_)
295 {
296 }
297};
299
302qualityUpperBound(ReadView const& v, Strand const& strand)
303{
304 Quality q{STAmount::uRateOne};
307 for (auto const& step : strand)
308 {
309 if (std::tie(stepQ, dir) = step->qualityUpperBound(v, dir); stepQ)
310 q = composed_quality(q, *stepQ);
311 else
312 return std::nullopt;
313 }
314 return q;
315};
317
319
327template <typename TOutAmt>
328inline TOutAmt
329limitOut(ReadView const& v, Strand const& strand, TOutAmt const& remainingOut, Quality const& limitQuality)
330{
331 std::optional<QualityFunction> stepQualityFunc;
334 for (auto const& step : strand)
335 {
336 if (std::tie(stepQualityFunc, dir) = step->getQualityFunc(v, dir); stepQualityFunc)
337 {
338 if (!qf)
339 qf = stepQualityFunc;
340 else
341 qf->combine(*stepQualityFunc);
342 }
343 else
344 return remainingOut;
345 }
346
347 // QualityFunction is constant
348 if (!qf || qf->isConst())
349 return remainingOut;
350
351 auto const out = [&]() {
352 if (auto const out = qf->outFromAvgQ(limitQuality); !out)
353 return remainingOut;
354 else if constexpr (std::is_same_v<TOutAmt, XRPAmount>)
355 return XRPAmount{*out};
356 else if constexpr (std::is_same_v<TOutAmt, IOUAmount>)
357 return IOUAmount{*out};
358 else
359 return STAmount{remainingOut.issue(), out->mantissa(), out->exponent()};
360 }();
361 // A tiny difference could be due to the round off
362 if (withinRelativeDistance(out, remainingOut, Number(1, -9)))
363 return remainingOut;
364 return std::min(out, remainingOut);
365};
367
369/* Track the non-dry strands
370
371 flow will search the non-dry strands (stored in `cur_`) for the best
372 available liquidity If flow doesn't use all the liquidity of a strand, that
373 strand is added to `next_`. The strands in `next_` are searched after the
374 current best liquidity is used.
375 */
376class ActiveStrands
377{
378private:
379 // Strands to be explored for liquidity
381 // Strands that may be explored for liquidity on the next iteration
383
384public:
385 ActiveStrands(std::vector<Strand> const& strands)
386 {
387 cur_.reserve(strands.size());
388 next_.reserve(strands.size());
389 for (auto& strand : strands)
390 next_.push_back(&strand);
391 }
392
393 // Start a new iteration in the search for liquidity
394 // Set the current strands to the strands in `next_`
395 void
396 activateNext(ReadView const& v, std::optional<Quality> const& limitQuality)
397 {
398 // add the strands in `next_` to `cur_`, sorted by theoretical quality.
399 // Best quality first.
400 cur_.clear();
401 if (!next_.empty())
402 {
404 strandQualities.reserve(next_.size());
405 if (next_.size() > 1) // no need to sort one strand
406 {
407 for (Strand const* strand : next_)
408 {
409 if (!strand)
410 {
411 // should not happen
412 continue;
413 }
414 if (auto const qual = qualityUpperBound(v, *strand))
415 {
416 if (limitQuality && *qual < *limitQuality)
417 {
418 // If a strand's quality is ever over limitQuality
419 // it is no longer part of the candidate set. Note
420 // that when transfer fees are charged, and an
421 // account goes from redeeming to issuing then
422 // strand quality _can_ increase; However, this is
423 // an unusual corner case.
424 continue;
425 }
426 strandQualities.push_back({*qual, strand});
427 }
428 }
429 // must stable sort for deterministic order across different c++
430 // standard library implementations
431 std::stable_sort(strandQualities.begin(), strandQualities.end(), [](auto const& lhs, auto const& rhs) {
432 // higher qualities first
433 return std::get<Quality>(lhs) > std::get<Quality>(rhs);
434 });
435 next_.clear();
436 next_.reserve(strandQualities.size());
437 for (auto const& sq : strandQualities)
438 {
440 }
441 }
442 }
443 std::swap(cur_, next_);
444 }
445
446 Strand const*
447 get(size_t i) const
448 {
449 if (i >= cur_.size())
450 {
451 // LCOV_EXCL_START
452 UNREACHABLE("xrpl::ActiveStrands::get : input out of range");
453 return nullptr;
454 // LCOV_EXCL_STOP
455 }
456 return cur_[i];
457 }
458
459 void
460 push(Strand const* s)
461 {
462 next_.push_back(s);
463 }
464
465 // Push the strands from index i to the end of cur_ to next_
466 void
467 pushRemainingCurToNext(size_t i)
468 {
469 if (i >= cur_.size())
470 return;
471 next_.insert(next_.end(), std::next(cur_.begin(), i), cur_.end());
472 }
473
474 auto
475 size() const
476 {
477 return cur_.size();
478 }
479
480 void
481 removeIndex(std::size_t i)
482 {
483 if (i >= next_.size())
484 return;
485 next_.erase(next_.begin() + i);
486 }
487};
489
510template <class TInAmt, class TOutAmt>
511FlowResult<TInAmt, TOutAmt>
513 PaymentSandbox const& baseView,
514 std::vector<Strand> const& strands,
515 TOutAmt const& outReq,
516 bool partialPayment,
517 OfferCrossing offerCrossing,
518 std::optional<Quality> const& limitQuality,
519 std::optional<STAmount> const& sendMaxST,
521 AMMContext& ammContext,
522 path::detail::FlowDebugInfo* flowDebugInfo = nullptr)
523{
524 // Used to track the strand that offers the best quality (output/input
525 // ratio)
526 struct BestStrand
527 {
528 TInAmt in;
529 TOutAmt out;
531 Strand const& strand;
532 Quality quality;
533
534 BestStrand(
535 TInAmt const& in_,
536 TOutAmt const& out_,
537 PaymentSandbox&& sb_,
538 Strand const& strand_,
539 Quality const& quality_)
540 : in(in_), out(out_), sb(std::move(sb_)), strand(strand_), quality(quality_)
541 {
542 }
543 };
544
545 std::size_t const maxTries = 1000;
546 std::size_t curTry = 0;
547 std::uint32_t maxOffersToConsider = 1500;
548 std::uint32_t offersConsidered = 0;
549
550 // There is a bug in gcc that incorrectly warns about using uninitialized
551 // values if `remainingIn` is initialized through a copy constructor. We can
552 // get similar warnings for `sendMax` if it is initialized in the most
553 // natural way. Using `make_optional`, allows us to work around this bug.
554 TInAmt const sendMaxInit = sendMaxST ? toAmount<TInAmt>(*sendMaxST) : TInAmt{beast::zero};
555 std::optional<TInAmt> const sendMax =
556 (sendMaxST && sendMaxInit >= beast::zero) ? std::make_optional(sendMaxInit) : std::nullopt;
557 std::optional<TInAmt> remainingIn = !!sendMax ? std::make_optional(sendMaxInit) : std::nullopt;
558 // std::optional<TInAmt> remainingIn{sendMax};
559
560 TOutAmt remainingOut(outReq);
561
562 PaymentSandbox sb(&baseView);
563
564 // non-dry strands
565 ActiveStrands activeStrands(strands);
566
567 // Keeping a running sum of the amount in the order they are processed
568 // will not give the best precision. Keep a collection so they may be summed
569 // from smallest to largest
570 boost::container::flat_multiset<TInAmt> savedIns;
571 savedIns.reserve(maxTries);
572 boost::container::flat_multiset<TOutAmt> savedOuts;
573 savedOuts.reserve(maxTries);
574
575 auto sum = [](auto const& col) {
576 using TResult = std::decay_t<decltype(*col.begin())>;
577 if (col.empty())
578 return TResult{beast::zero};
579 return std::accumulate(col.begin() + 1, col.end(), *col.begin());
580 };
581
582 // These offers only need to be removed if the payment is not
583 // successful
584 boost::container::flat_set<uint256> ofrsToRmOnFail;
585
586 while (remainingOut > beast::zero && (!remainingIn || *remainingIn > beast::zero))
587 {
588 ++curTry;
589 if (curTry >= maxTries)
590 {
591 return {telFAILED_PROCESSING, std::move(ofrsToRmOnFail)};
592 }
593
594 activeStrands.activateNext(sb, limitQuality);
595
596 ammContext.setMultiPath(activeStrands.size() > 1);
597
598 // Limit only if one strand and limitQuality
599 auto const limitRemainingOut = [&]() {
600 if (activeStrands.size() == 1 && limitQuality)
601 if (auto const strand = activeStrands.get(0))
602 return limitOut(sb, *strand, remainingOut, *limitQuality);
603 return remainingOut;
604 }();
605 auto const adjustedRemOut = limitRemainingOut != remainingOut;
606
607 boost::container::flat_set<uint256> ofrsToRm;
609 if (flowDebugInfo)
610 flowDebugInfo->newLiquidityPass();
611 // Index of strand to mark as inactive (remove from the active list) if
612 // the liquidity is used. This is used for strands that consume too many
613 // offers Constructed as `false,0` to workaround a gcc warning about
614 // uninitialized variables
615 std::optional<std::size_t> markInactiveOnUse;
616 for (size_t strandIndex = 0, sie = activeStrands.size(); strandIndex != sie; ++strandIndex)
617 {
618 Strand const* strand = activeStrands.get(strandIndex);
619 if (!strand)
620 {
621 // should not happen
622 continue;
623 }
624 // Clear AMM liquidity used flag. The flag might still be set if
625 // the previous strand execution failed. It has to be reset
626 // since this strand might not have AMM liquidity.
627 ammContext.clear();
628 if (offerCrossing && limitQuality)
629 {
630 auto const strandQ = qualityUpperBound(sb, *strand);
631 if (!strandQ || *strandQ < *limitQuality)
632 continue;
633 }
634 auto f = flow<TInAmt, TOutAmt>(sb, *strand, remainingIn, limitRemainingOut, j);
635
636 // rm bad offers even if the strand fails
637 SetUnion(ofrsToRm, f.ofrsToRm);
638
639 offersConsidered += f.ofrsUsed;
640
641 if (!f.success || f.out == beast::zero)
642 continue;
643
644 if (flowDebugInfo)
645 flowDebugInfo->pushLiquiditySrc(EitherAmount(f.in), EitherAmount(f.out));
646
647 XRPL_ASSERT(
648 f.out <= remainingOut && f.sandbox && (!remainingIn || f.in <= *remainingIn),
649 "xrpl::flow : remaining constraints");
650
651 Quality const q(f.out, f.in);
652
653 JLOG(j.trace()) << "New flow iter (iter, in, out): " << curTry - 1 << " " << to_string(f.in) << " "
654 << to_string(f.out);
655
656 // limitOut() finds output to generate exact requested
657 // limitQuality. But the actual limit quality might be slightly
658 // off due to the round off.
659 if (limitQuality && q < *limitQuality &&
660 (!adjustedRemOut || !withinRelativeDistance(q, *limitQuality, Number(1, -7))))
661 {
662 JLOG(j.trace()) << "Path rejected by limitQuality"
663 << " limit: " << *limitQuality << " path q: " << q;
664 continue;
665 }
666
667 XRPL_ASSERT(!best, "xrpl::flow : best is unset");
668 if (!f.inactive)
669 activeStrands.push(strand);
670 best.emplace(f.in, f.out, std::move(*f.sandbox), *strand, q);
671 activeStrands.pushRemainingCurToNext(strandIndex + 1);
672 break;
673 }
674
675 bool const shouldBreak = !best || offersConsidered >= maxOffersToConsider;
676
677 if (best)
678 {
679 if (markInactiveOnUse)
680 {
681 activeStrands.removeIndex(*markInactiveOnUse);
682 markInactiveOnUse.reset();
683 }
684 savedIns.insert(best->in);
685 savedOuts.insert(best->out);
686 remainingOut = outReq - sum(savedOuts);
687 if (sendMax)
688 remainingIn = *sendMax - sum(savedIns);
689
690 if (flowDebugInfo)
691 flowDebugInfo->pushPass(EitherAmount(best->in), EitherAmount(best->out), activeStrands.size());
692
693 JLOG(j.trace()) << "Best path: in: " << to_string(best->in) << " out: " << to_string(best->out)
694 << " remainingOut: " << to_string(remainingOut);
695
696 best->sb.apply(sb);
697 ammContext.update();
698 }
699 else
700 {
701 JLOG(j.trace()) << "All strands dry.";
702 }
703
704 best.reset(); // view in best must be destroyed before modifying base
705 // view
706 if (!ofrsToRm.empty())
707 {
708 SetUnion(ofrsToRmOnFail, ofrsToRm);
709 for (auto const& o : ofrsToRm)
710 {
711 if (auto ok = sb.peek(keylet::offer(o)))
712 offerDelete(sb, ok, j);
713 }
714 }
715
716 if (shouldBreak)
717 break;
718 }
719
720 auto const actualOut = sum(savedOuts);
721 auto const actualIn = sum(savedIns);
722
723 JLOG(j.trace()) << "Total flow: in: " << to_string(actualIn) << " out: " << to_string(actualOut);
724
725 /* flowCross doesn't handle offer crossing with tfFillOrKill flag correctly.
726 * 1. If tfFillOrKill is set then the owner must receive the full
727 * TakerPays. We reverse pays and gets because during crossing
728 * we are taking, therefore the owner must deliver the full TakerPays and
729 * the entire TakerGets doesn't have to be spent.
730 * Pre-fixFillOrKill amendment code fails if the entire TakerGets
731 * is not spent. fixFillOrKill addresses this issue.
732 * 2. If tfSell is also set then the owner must spend the entire TakerGets
733 * even if it means obtaining more than TakerPays. Since the pays and gets
734 * are reversed, the owner must send the entire TakerGets.
735 */
736 bool const fillOrKillEnabled = baseView.rules().enabled(fixFillOrKill);
737
738 if (actualOut != outReq)
739 {
740 if (actualOut > outReq)
741 {
742 // Rounding in the payment engine is causing this assert to
743 // sometimes fire with "dust" amounts. This is causing issues when
744 // running debug builds of rippled. While this issue still needs to
745 // be resolved, the assert is causing more harm than good at this
746 // point.
747 // UNREACHABLE("xrpl::flow : rounding error");
748
749 return {tefEXCEPTION, std::move(ofrsToRmOnFail)};
750 }
751 if (!partialPayment)
752 {
753 // If we're offerCrossing a !partialPayment, then we're
754 // handling tfFillOrKill.
755 // Pre-fixFillOrKill amendment:
756 // That case is handled below; not here.
757 // fixFillOrKill amendment:
758 // That case is handled here if tfSell is also not set; i.e,
759 // case 1.
760 if (!offerCrossing || (fillOrKillEnabled && offerCrossing != OfferCrossing::sell))
761 return {tecPATH_PARTIAL, actualIn, actualOut, std::move(ofrsToRmOnFail)};
762 }
763 else if (actualOut == beast::zero)
764 {
765 return {tecPATH_DRY, std::move(ofrsToRmOnFail)};
766 }
767 }
768 if (offerCrossing && (!partialPayment && (!fillOrKillEnabled || offerCrossing == OfferCrossing::sell)))
769 {
770 // If we're offer crossing and partialPayment is *not* true, then
771 // we're handling a FillOrKill offer. In this case remainingIn must
772 // be zero (all funds must be consumed) or else we kill the offer.
773 // Pre-fixFillOrKill amendment:
774 // Handles both cases 1. and 2.
775 // fixFillOrKill amendment:
776 // Handles 2. 1. is handled above and falls through for tfSell.
777 XRPL_ASSERT(remainingIn, "xrpl::flow : nonzero remainingIn");
778 if (remainingIn && *remainingIn != beast::zero)
779 return {tecPATH_PARTIAL, actualIn, actualOut, std::move(ofrsToRmOnFail)};
780 }
781
782 return {actualIn, actualOut, std::move(sb), std::move(ofrsToRmOnFail)};
783}
784
785} // namespace xrpl
T accumulate(T... args)
T any_of(T... args)
T begin(T... args)
A generic endpoint for log messages.
Definition Journal.h:40
Stream fatal() const
Definition Journal.h:324
Stream trace() const
Severity stream access functions.
Definition Journal.h:294
Stream warn() const
Definition Journal.h:312
Maintains AMM info per overall payment engine execution and individual iteration.
Definition AMMContext.h:16
void setMultiPath(bool fs)
Definition AMMContext.h:49
void clear()
Strand execution may fail.
Definition AMMContext.h:90
Number is a floating point type that can represent a wide range of values.
Definition Number.h:207
A wrapper which makes credits unavailable to balances.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:118
static std::uint64_t const uRateOne
Definition STAmount.h:62
std::shared_ptr< SLE > 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(T... args)
T empty(T... args)
T end(T... args)
T erase(T... args)
T insert(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:235
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ telFAILED_PROCESSING
Definition TER.h:36
static auto sum(TCollection const &col)
Definition BookStep.cpp:872
Quality composed_quality(Quality const &lhs, Quality const &rhs)
Definition Quality.cpp:111
static void limitStepOut(Offer const &offer, TAmounts< TIn, TOut > &ofrAmt, TAmounts< TIn, TOut > &stpAmt, TOut &ownerGives, std::uint32_t transferRateIn, std::uint32_t transferRateOut, TOut const &limit)
Definition BookStep.cpp:595
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:597
T get(Section const &section, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
@ tefEXCEPTION
Definition TER.h:152
DebtDirection
Definition Steps.h:22
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
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
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
Definition View.cpp:1672
boost::outcome_v2::result< T, std::error_code > Result
Definition b58_utils.h:17
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:98
@ tecPATH_PARTIAL
Definition TER.h:263
@ tecPATH_DRY
Definition TER.h:275
OfferCrossing
Definition Steps.h:25
@ sell
Definition Steps.h:25
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 > ofrsToRm_, bool inactive_)
Definition StrandFlow.h:45
StrandResult(Strand const &strand, boost::container::flat_set< uint256 > ofrsToRm_)
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)