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>
17#include <boost/container/flat_set.hpp>
26template <
class TInAmt,
class TOutAmt>
30 TInAmt
in = beast::kZero;
31 TOutAmt
out = beast::kZero;
50 boost::container::flat_set<uint256> ofrsToRemoveMember,
62 StrandResult(Strand
const& strand, boost::container::flat_set<uint256> ofrsToRemoveMember)
79template <
class TInAmt,
class TOutAmt>
80StrandResult<TInAmt, TOutAmt>
91 JLOG(j.
warn()) <<
"Empty strand passed to Liquidity";
95 boost::container::flat_set<uint256> ofrsToRm;
97 if (isDirectXrpToXrp<TInAmt, TOutAmt>(strand))
99 return Result{strand, std::move(ofrsToRm)};
115 for (
auto i = s; i--;)
117 auto r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
118 if (strand[i]->isZero(r.second))
120 JLOG(j.
trace()) <<
"Strand found dry in rev";
121 return Result{strand, std::move(ofrsToRm)};
124 if (i == 0 && maxIn && *maxIn <
get<TInAmt>(r.first))
132 r = strand[i]->fwd(*sb, *afView, ofrsToRm,
EitherAmount(*maxIn));
135 if (strand[i]->isZero(r.second))
137 JLOG(j.
trace()) <<
"First step found dry";
138 return Result{strand, std::move(ofrsToRm)};
147 <<
"Re-executed limiting step failed. r.first: "
150 "xrpl::flow : first step re-executing the "
151 "limiting step failed");
152 return Result{strand, std::move(ofrsToRm)};
156 else if (!strand[i]->equalOut(r.second, stepOut))
166 r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
169 if (strand[i]->isZero(r.second))
173 JLOG(j.
trace()) <<
"Limiting step found dry";
174 return Result{strand, std::move(ofrsToRm)};
176 if (!strand[i]->equalOut(r.second, stepOut))
184 <<
"Re-executed limiting step failed. r.second: " << r.second
185 <<
" stepOut: " << stepOut;
187 JLOG(j.
fatal()) <<
"Re-executed limiting step failed";
190 "xrpl::flow : limiting step re-executing the "
191 "limiting step failed");
192 return Result{strand, std::move(ofrsToRm)};
204 for (
auto i = limitingStep + 1; i < s; ++i)
206 auto const r = strand[i]->fwd(*sb, *afView, ofrsToRm, stepIn);
207 if (strand[i]->isZero(r.second))
211 JLOG(j.
trace()) <<
"Non-limiting step found dry";
212 return Result{strand, std::move(ofrsToRm)};
214 if (!strand[i]->equalIn(r.first, stepIn))
221 JLOG(j.
fatal()) <<
"Re-executed forward pass failed. r.first: " << r.first
222 <<
" stepIn: " << stepIn;
224 JLOG(j.
fatal()) <<
"Re-executed forward pass failed";
227 "xrpl::flow : non-limiting step re-executing the "
228 "forward pass failed");
229 return Result{strand, std::move(ofrsToRm)};
238 auto const strandIn = *strand.front()->cachedIn();
239 auto const strandOut = *strand.back()->cachedOut();
249 *strand[0]->cachedIn());
250 for (
auto i = 0; i < s; ++i)
253 std::tie(
valid, stepIn) = strand[i]->validFwd(checkSB, checkAfView, stepIn);
256 JLOG(j.
warn()) <<
"Strand re-execute check failed. Step: " << i;
263 bool const inactive =
265 return step->inactive();
276 catch (FlowException
const&)
278 return Result{strand, std::move(ofrsToRm)};
283template <
class TInAmt,
class TOutAmt>
286 TInAmt in = beast::kZero;
287 TOutAmt out = beast::kZero;
289 boost::container::flat_set<uint256> removableOffers;
290 TER ter = temUNKNOWN;
292 FlowResult() =
default;
297 PaymentSandbox&& sandbox,
298 boost::container::flat_set<uint256> ofrsToRm)
301 , sandbox(
std::move(sandbox))
302 , removableOffers(
std::move(ofrsToRm))
307 FlowResult(TER ter, boost::container::flat_set<uint256> ofrsToRm)
308 : removableOffers(std::
move(ofrsToRm)), ter(ter)
316 boost::container::flat_set<uint256> ofrsToRm)
317 : in(in), out(out), removableOffers(std::
move(ofrsToRm)), ter(ter)
324inline std::optional<Quality>
325qualityUpperBound(
ReadView const& v, Strand
const& strand)
328 std::optional<Quality> stepQ;
330 for (
auto const& step : strand)
332 if (
std::tie(stepQ, dir) = step->qualityUpperBound(v, dir); stepQ)
354template <
typename TOutAmt>
358 Strand
const& strand,
359 TOutAmt
const& remainingOut,
362 std::optional<QualityFunction> stepQualityFunc;
363 std::optional<QualityFunction> qf;
365 for (
auto const& step : strand)
367 if (
std::tie(stepQualityFunc, dir) = step->getQualityFunc(v, dir); stepQualityFunc)
371 qf = stepQualityFunc;
375 qf->combine(*stepQualityFunc);
385 if (!qf || qf->isConst())
388 auto const out = [&]() {
389 auto const out = qf->outFromAvgQ(limitQuality);
406 return STAmount{remainingOut.asset(), out->mantissa(), out->exponent()};
428 std::vector<Strand const*> cur_;
430 std::vector<Strand const*> next_;
433 ActiveStrands(std::vector<Strand>
const& strands)
437 for (
auto& strand : strands)
444 activateNext(ReadView
const& v, std::optional<Quality>
const& limitQuality)
451 std::vector<std::pair<Quality, Strand const*>> strandQualities;
453 if (next_.
size() > 1)
455 for (Strand
const* strand : next_)
457 if (strand ==
nullptr)
462 if (
auto const qual = qualityUpperBound(v, *strand))
464 if (limitQuality && *qual < *limitQuality)
482 [](
auto const& lhs,
auto const& rhs) {
484 return std::get<Quality>(lhs) > std::get<Quality>(rhs);
487 next_.reserve(strandQualities.
size());
488 for (
auto const& sq : strandQualities)
490 next_.push_back(std::get<Strand const*>(sq));
497 [[nodiscard]] Strand
const*
500 if (i >= cur_.
size())
503 UNREACHABLE(
"xrpl::ActiveStrands::get : input out of range");
511 push(Strand
const* s)
518 pushRemainingCurToNext(
size_t i)
520 if (i >= cur_.
size())
553template <StepAmount TInAmt, StepAmount TOutAmt>
554FlowResult<TInAmt, TOutAmt>
558 TOutAmt
const& outReq,
574 Strand
const& strand;
581 Strand
const& strand,
583 : in(in), out(out), sb(std::move(sb)), strand(strand), quality(quality)
597 TInAmt
const sendMaxInit = sendMaxST ?
toAmount<TInAmt>(*sendMaxST) : TInAmt{beast::kZero};
599 (sendMaxST && sendMaxInit >= beast::kZero) ?
std::make_optional(sendMaxInit) : std::nullopt;
603 TOutAmt remainingOut(outReq);
608 ActiveStrands activeStrands(strands);
613 boost::container::flat_multiset<TInAmt> savedIns;
614 savedIns.reserve(maxTries);
615 boost::container::flat_multiset<TOutAmt> savedOuts;
616 savedOuts.reserve(maxTries);
618 auto sum = [](
auto const& col) {
621 return TResult{beast::kZero};
627 boost::container::flat_set<uint256> ofrsToRmOnFail;
629 while (remainingOut > beast::kZero && (!remainingIn || *remainingIn > beast::kZero))
632 if (curTry >= maxTries)
637 activeStrands.activateNext(sb, limitQuality);
642 auto const limitRemainingOut = [&]() {
643 if (activeStrands.size() == 1 && limitQuality)
645 if (
auto const strand = activeStrands.get(0))
646 return limitOut(sb, *strand, remainingOut, *limitQuality);
650 auto const adjustedRemOut = limitRemainingOut != remainingOut;
652 boost::container::flat_set<uint256> ofrsToRm;
655 flowDebugInfo->newLiquidityPass();
656 for (
size_t strandIndex = 0, sie = activeStrands.size(); strandIndex != sie; ++strandIndex)
658 Strand
const* strand = activeStrands.get(strandIndex);
670 auto const strandQ = qualityUpperBound(sb, *strand);
671 if (!strandQ || *strandQ < *limitQuality)
679 offersConsidered += f.ofrsUsed;
681 if (!f.success || f.out == beast::kZero)
688 f.out <= remainingOut && f.sandbox && (!remainingIn || f.in <= *remainingIn),
689 "xrpl::flow : remaining constraints");
693 JLOG(j.
trace()) <<
"New flow iter (iter, in, out): " << curTry - 1 <<
" "
699 if (limitQuality && q < *limitQuality &&
702 JLOG(j.
trace()) <<
"Path rejected by limitQuality"
703 <<
" limit: " << *limitQuality <<
" path q: " << q;
707 XRPL_ASSERT(!best,
"xrpl::flow : best is unset");
709 activeStrands.push(strand);
710 best.
emplace(f.in, f.out, std::move(*f.sandbox), *strand, q);
711 activeStrands.pushRemainingCurToNext(strandIndex + 1);
715 bool const shouldBreak = !best || offersConsidered >= maxOffersToConsider;
719 savedIns.insert(best->in);
720 savedOuts.insert(best->out);
721 remainingOut = outReq -
sum(savedOuts);
723 remainingIn = *sendMax -
sum(savedIns);
727 flowDebugInfo->pushPass(
733 <<
" remainingOut: " <<
to_string(remainingOut);
740 JLOG(j.
trace()) <<
"All strands dry.";
744 if (!ofrsToRm.empty())
747 for (
auto const& o : ofrsToRm)
758 auto const actualOut =
sum(savedOuts);
759 auto const actualIn =
sum(savedIns);
775 bool const fillOrKillEnabled = baseView.
rules().
enabled(fixFillOrKill);
777 if (actualOut != outReq)
779 if (actualOut > outReq)
799 return {
tecPATH_PARTIAL, actualIn, actualOut, std::move(ofrsToRmOnFail)};
802 else if (actualOut == beast::kZero)
817 XRPL_ASSERT(remainingIn,
"xrpl::flow : nonzero remainingIn");
818 if (remainingIn && *remainingIn != beast::kZero)
819 return {
tecPATH_PARTIAL, actualIn, actualOut, std::move(ofrsToRmOnFail)};
822 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.
Floating point representation of amounts with high dynamic range.
Number is a floating point type that can represent a wide range of values.
A wrapper which makes credits unavailable to balances.
Represents the logical ratio of output currency to input currency.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
static std::uint64_t const kURateOne
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 emplace_back(T... args)
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)
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)
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.
boost::outcome_v2::result< T, std::error_code > Result
TER offerDelete(ApplyView &view, SLE::ref sle, beast::Journal j)
Delete an offer.
std::string to_string(BaseUInt< Bits, Tag > const &a)
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.
bool withinRelativeDistance(Quality const &calcQuality, Quality const &reqQuality, Number const &dist)
Check if the relative distance between the qualities is within the requested distance.
Quality composedQuality(Quality const &lhs, Quality const &rhs)
Calculate the quality of a two-hop path given the two hops.
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 > ofrsToRemoveMember, bool inactive)
StrandResult(Strand const &strand, boost::container::flat_set< uint256 > ofrsToRemoveMember)
TInAmt in
Currency amount in.
bool inactive
Strand should not considered as a further source of liquidity (dry).