3#include <xrpl/basics/Log.h>
4#include <xrpl/ledger/View.h>
5#include <xrpl/ledger/helpers/OfferHelpers.h>
6#include <xrpl/ledger/helpers/RippleStateHelpers.h>
7#include <xrpl/protocol/Feature.h>
8#include <xrpl/protocol/IOUAmount.h>
9#include <xrpl/protocol/XRPAmount.h>
10#include <xrpl/tx/paths/Flow.h>
11#include <xrpl/tx/paths/detail/AmountSpec.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#include <xrpl/tx/transactors/dex/AMMHelpers.h>
18#include <boost/container/flat_set.hpp>
27template <
class TInAmt,
class TOutAmt>
31 TInAmt
in = beast::zero;
32 TOutAmt
out = beast::zero;
51 boost::container::flat_set<uint256> ofrsToRm_,
63 StrandResult(Strand
const& strand, boost::container::flat_set<uint256> ofrsToRm_)
80template <
class TInAmt,
class TOutAmt>
81StrandResult<TInAmt, TOutAmt>
92 JLOG(j.
warn()) <<
"Empty strand passed to Liquidity";
96 boost::container::flat_set<uint256> ofrsToRm;
98 if (isDirectXrpToXrp<TInAmt, TOutAmt>(strand))
100 return Result{strand, std::move(ofrsToRm)};
116 for (
auto i = s; i--;)
118 auto r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
119 if (strand[i]->isZero(r.second))
121 JLOG(j.
trace()) <<
"Strand found dry in rev";
122 return Result{strand, std::move(ofrsToRm)};
125 if (i == 0 && maxIn && *maxIn < get<TInAmt>(r.first))
133 r = strand[i]->fwd(*sb, *afView, ofrsToRm,
EitherAmount(*maxIn));
136 if (strand[i]->isZero(r.second))
138 JLOG(j.
trace()) <<
"First step found dry";
139 return Result{strand, std::move(ofrsToRm)};
141 if (get<TInAmt>(r.first) != *maxIn)
148 <<
"Re-executed limiting step failed. r.first: "
151 "xrpl::flow : first step re-executing the "
152 "limiting step failed");
153 return Result{strand, std::move(ofrsToRm)};
157 else if (!strand[i]->equalOut(r.second, stepOut))
167 r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
170 if (strand[i]->isZero(r.second))
174 JLOG(j.
trace()) <<
"Limiting step found dry";
175 return Result{strand, std::move(ofrsToRm)};
177 if (!strand[i]->equalOut(r.second, stepOut))
185 <<
"Re-executed limiting step failed. r.second: " << r.second
186 <<
" stepOut: " << stepOut;
188 JLOG(j.
fatal()) <<
"Re-executed limiting step failed";
191 "xrpl::flow : limiting step re-executing the "
192 "limiting step failed");
193 return Result{strand, std::move(ofrsToRm)};
205 for (
auto i = limitingStep + 1; i < s; ++i)
207 auto const r = strand[i]->fwd(*sb, *afView, ofrsToRm, stepIn);
208 if (strand[i]->isZero(r.second))
212 JLOG(j.
trace()) <<
"Non-limiting step found dry";
213 return Result{strand, std::move(ofrsToRm)};
215 if (!strand[i]->equalIn(r.first, stepIn))
222 JLOG(j.
fatal()) <<
"Re-executed forward pass failed. r.first: " << r.first
223 <<
" stepIn: " << stepIn;
225 JLOG(j.
fatal()) <<
"Re-executed forward pass failed";
228 "xrpl::flow : non-limiting step re-executing the "
229 "forward pass failed");
230 return Result{strand, std::move(ofrsToRm)};
237 auto const strandIn = *strand.front()->cachedIn();
238 auto const strandOut = *strand.back()->cachedOut();
247 for (
auto i = 0; i < s; ++i)
250 std::tie(
valid, stepIn) = strand[i]->validFwd(checkSB, checkAfView, stepIn);
253 JLOG(j.
warn()) <<
"Strand re-execute check failed. Step: " << i;
260 bool const inactive =
262 return step->inactive();
267 get<TInAmt>(strandIn),
268 get<TOutAmt>(strandOut),
273 catch (FlowException
const&)
275 return Result{strand, std::move(ofrsToRm)};
280template <
class TInAmt,
class TOutAmt>
283 TInAmt in = beast::zero;
284 TOutAmt out = beast::zero;
286 boost::container::flat_set<uint256> removableOffers;
287 TER ter = temUNKNOWN;
289 FlowResult() =
default;
294 PaymentSandbox&& sandbox_,
295 boost::container::flat_set<uint256> ofrsToRm)
298 , sandbox(
std::move(sandbox_))
299 , removableOffers(
std::move(ofrsToRm))
304 FlowResult(TER ter_, boost::container::flat_set<uint256> ofrsToRm)
305 : removableOffers(
std::
move(ofrsToRm)), ter(ter_)
313 boost::container::flat_set<uint256> ofrsToRm)
314 :
in(in_),
out(out_), removableOffers(
std::
move(ofrsToRm)), ter(ter_)
322qualityUpperBound(ReadView
const& v, Strand
const& strand)
327 for (
auto const& step : strand)
329 if (
std::tie(stepQ, dir) = step->qualityUpperBound(v, dir); stepQ)
347template <
typename TOutAmt>
351 Strand
const& strand,
352 TOutAmt
const& remainingOut,
353 Quality
const& limitQuality)
358 for (
auto const& step : strand)
360 if (
std::tie(stepQualityFunc, dir) = step->getQualityFunc(v, dir); stepQualityFunc)
363 qf = stepQualityFunc;
365 qf->combine(*stepQualityFunc);
372 if (!qf || qf->isConst())
375 auto const out = [&]() {
376 if (
auto const out = qf->outFromAvgQ(limitQuality); !
out)
379 return XRPAmount{*
out};
381 return IOUAmount{*
out};
383 return STAmount{remainingOut.issue(),
out->mantissa(),
out->exponent()};
413 for (
auto& strand : strands)
414 next_.push_back(&strand);
429 if (next_.
size() > 1)
431 for (Strand
const* strand : next_)
438 if (
auto const qual = qualityUpperBound(v, *strand))
440 if (limitQuality && *qual < *limitQuality)
450 strandQualities.
push_back({*qual, strand});
456 strandQualities.
begin(),
457 strandQualities.
end(),
458 [](
auto const& lhs,
auto const& rhs) {
460 return std::get<Quality>(lhs) > std::get<Quality>(rhs);
464 for (
auto const& sq : strandQualities)
476 if (i >= cur_.
size())
479 UNREACHABLE(
"xrpl::ActiveStrands::get : input out of range");
487 push(Strand
const* s)
494 pushRemainingCurToNext(
size_t i)
496 if (i >= cur_.
size())
510 if (i >= next_.
size())
537template <
class TInAmt,
class TOutAmt>
538FlowResult<TInAmt, TOutAmt>
542 TOutAmt
const& outReq,
558 Strand
const& strand;
565 Strand
const& strand_,
566 Quality
const& quality_)
567 :
in(in_),
out(out_), sb(std::move(sb_)), strand(strand_), quality(quality_)
581 TInAmt
const sendMaxInit = sendMaxST ? toAmount<TInAmt>(*sendMaxST) : TInAmt{beast::zero};
587 TOutAmt remainingOut(outReq);
592 ActiveStrands activeStrands(strands);
597 boost::container::flat_multiset<TInAmt> savedIns;
598 savedIns.reserve(maxTries);
599 boost::container::flat_multiset<TOutAmt> savedOuts;
600 savedOuts.reserve(maxTries);
602 auto sum = [](
auto const& col) {
605 return TResult{beast::zero};
611 boost::container::flat_set<uint256> ofrsToRmOnFail;
613 while (remainingOut > beast::zero && (!remainingIn || *remainingIn > beast::zero))
616 if (curTry >= maxTries)
621 activeStrands.activateNext(sb, limitQuality);
626 auto const limitRemainingOut = [&]() {
627 if (activeStrands.size() == 1 && limitQuality)
628 if (
auto const strand = activeStrands.get(0))
629 return limitOut(sb, *strand, remainingOut, *limitQuality);
632 auto const adjustedRemOut = limitRemainingOut != remainingOut;
634 boost::container::flat_set<uint256> ofrsToRm;
637 flowDebugInfo->newLiquidityPass();
643 for (
size_t strandIndex = 0, sie = activeStrands.size(); strandIndex != sie; ++strandIndex)
645 Strand
const* strand = activeStrands.get(strandIndex);
655 if (offerCrossing && limitQuality)
657 auto const strandQ = qualityUpperBound(sb, *strand);
658 if (!strandQ || *strandQ < *limitQuality)
661 auto f = flow<TInAmt, TOutAmt>(sb, *strand, remainingIn, limitRemainingOut, j);
666 offersConsidered += f.ofrsUsed;
668 if (!f.success || f.out == beast::zero)
675 f.out <= remainingOut && f.sandbox && (!remainingIn || f.in <= *remainingIn),
676 "xrpl::flow : remaining constraints");
678 Quality
const q(f.out, f.in);
680 JLOG(j.
trace()) <<
"New flow iter (iter, in, out): " << curTry - 1 <<
" "
686 if (limitQuality && q < *limitQuality &&
689 JLOG(j.
trace()) <<
"Path rejected by limitQuality"
690 <<
" limit: " << *limitQuality <<
" path q: " << q;
694 XRPL_ASSERT(!best,
"xrpl::flow : best is unset");
696 activeStrands.push(strand);
697 best.
emplace(f.in, f.out, std::move(*f.sandbox), *strand, q);
698 activeStrands.pushRemainingCurToNext(strandIndex + 1);
702 bool const shouldBreak = !best || offersConsidered >= maxOffersToConsider;
706 if (markInactiveOnUse)
708 activeStrands.removeIndex(*markInactiveOnUse);
709 markInactiveOnUse.
reset();
711 savedIns.insert(best->in);
712 savedOuts.insert(best->out);
713 remainingOut = outReq -
sum(savedOuts);
715 remainingIn = *sendMax -
sum(savedIns);
718 flowDebugInfo->pushPass(
723 <<
" remainingOut: " <<
to_string(remainingOut);
730 JLOG(j.
trace()) <<
"All strands dry.";
735 if (!ofrsToRm.empty())
738 for (
auto const& o : ofrsToRm)
749 auto const actualOut =
sum(savedOuts);
750 auto const actualIn =
sum(savedIns);
766 bool const fillOrKillEnabled = baseView.
rules().
enabled(fixFillOrKill);
768 if (actualOut != outReq)
770 if (actualOut > outReq)
791 return {
tecPATH_PARTIAL, actualIn, actualOut, std::move(ofrsToRmOnFail)};
793 else if (actualOut == beast::zero)
808 XRPL_ASSERT(remainingIn,
"xrpl::flow : nonzero remainingIn");
809 if (remainingIn && *remainingIn != beast::zero)
810 return {
tecPATH_PARTIAL, actualIn, actualOut, std::move(ofrsToRmOnFail)};
813 return {actualIn, actualOut, std::move(sb), std::move(ofrsToRmOnFail)};
A generic endpoint for log messages.
Stream trace() const
Severity stream access functions.
Maintains AMM info per overall payment engine execution and individual iteration.
void setMultiPath(bool fs)
void clear()
Strand execution may fail.
Number is a floating point type that can represent a wide range of values.
A wrapper which makes credits unavailable to balances.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
static std::uint64_t const uRateOne
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 make_optional(T... args)
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.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
static auto sum(TCollection const &col)
Quality composed_quality(Quality const &lhs, Quality const &rhs)
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)
std::string to_string(base_uint< Bits, Tag > const &a)
T get(Section const §ion, 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.
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.
TER offerDelete(ApplyView &view, std::shared_ptr< SLE > const &sle, beast::Journal j)
Delete an offer.
boost::outcome_v2::result< T, std::error_code > Result
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Result of flow() execution of a single Strand.
TOutAmt out
Currency amount out.
boost::container::flat_set< uint256 > ofrsToRm
Offers to remove.
std::optional< PaymentSandbox > sandbox
Resulting Sandbox state.
bool success
Strand succeeded.
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_)
StrandResult(Strand const &strand, boost::container::flat_set< uint256 > ofrsToRm_)
TInAmt in
Currency amount in.
bool inactive
Strand should not considered as a further source of liquidity (dry)