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>
27#include <boost/container/flat_set.hpp>
46template <
class TInAmt,
class TOutAmt>
72 boost::container::flat_set<uint256> ofrsToRemoveMember,
84 StrandResult(Strand
const& strand, boost::container::flat_set<uint256> ofrsToRemoveMember)
101template <
class TInAmt,
class TOutAmt>
102StrandResult<TInAmt, TOutAmt>
105 Strand
const& strand,
113 JLOG(j.
warn()) <<
"Empty strand passed to Liquidity";
117 boost::container::flat_set<uint256> ofrsToRm;
119 if (isDirectXrpToXrp<TInAmt, TOutAmt>(strand))
121 return Result{strand, std::move(ofrsToRm)};
137 for (
auto i = s; i--;)
139 auto r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
140 if (strand[i]->isZero(r.second))
142 JLOG(j.
trace()) <<
"Strand found dry in rev";
143 return Result{strand, std::move(ofrsToRm)};
146 if (i == 0 && maxIn && *maxIn <
get<TInAmt>(r.first))
154 r = strand[i]->fwd(*sb, *afView, ofrsToRm,
EitherAmount(*maxIn));
157 if (strand[i]->isZero(r.second))
159 JLOG(j.
trace()) <<
"First step found dry";
160 return Result{strand, std::move(ofrsToRm)};
169 <<
"Re-executed limiting step failed. r.first: "
172 "xrpl::flow : first step re-executing the "
173 "limiting step failed");
174 return Result{strand, std::move(ofrsToRm)};
178 else if (!strand[i]->equalOut(r.second, stepOut))
188 r = strand[i]->rev(*sb, *afView, ofrsToRm, stepOut);
191 if (strand[i]->isZero(r.second))
195 JLOG(j.
trace()) <<
"Limiting step found dry";
196 return Result{strand, std::move(ofrsToRm)};
198 if (!strand[i]->equalOut(r.second, stepOut))
206 <<
"Re-executed limiting step failed. r.second: " << r.second
207 <<
" stepOut: " << stepOut;
209 JLOG(j.
fatal()) <<
"Re-executed limiting step failed";
212 "xrpl::flow : limiting step re-executing the "
213 "limiting step failed");
214 return Result{strand, std::move(ofrsToRm)};
226 for (
auto i = limitingStep + 1; i < s; ++i)
228 auto const r = strand[i]->fwd(*sb, *afView, ofrsToRm, stepIn);
229 if (strand[i]->isZero(r.second))
233 JLOG(j.
trace()) <<
"Non-limiting step found dry";
234 return Result{strand, std::move(ofrsToRm)};
236 if (!strand[i]->equalIn(r.first, stepIn))
243 JLOG(j.
fatal()) <<
"Re-executed forward pass failed. r.first: " << r.first
244 <<
" stepIn: " << stepIn;
246 JLOG(j.
fatal()) <<
"Re-executed forward pass failed";
249 "xrpl::flow : non-limiting step re-executing the "
250 "forward pass failed");
251 return Result{strand, std::move(ofrsToRm)};
260 auto const strandIn = *strand.front()->cachedIn();
261 auto const strandOut = *strand.back()->cachedOut();
271 *strand[0]->cachedIn());
272 for (
auto i = 0; i < s; ++i)
275 std::tie(
valid, stepIn) = strand[i]->validFwd(checkSB, checkAfView, stepIn);
278 JLOG(j.
warn()) <<
"Strand re-execute check failed. Step: " << i;
285 bool const inactive =
287 return step->inactive();
298 catch (FlowException
const&)
300 return Result{strand, std::move(ofrsToRm)};
305template <
class TInAmt,
class TOutAmt>
311 boost::container::flat_set<uint256> removableOffers;
312 TER ter = temUNKNOWN;
314 FlowResult() =
default;
319 PaymentSandbox&& sandbox,
320 boost::container::flat_set<uint256> ofrsToRm)
323 , sandbox(
std::move(sandbox))
324 , removableOffers(
std::move(ofrsToRm))
329 FlowResult(TER ter, boost::container::flat_set<uint256> ofrsToRm)
330 : removableOffers(std::
move(ofrsToRm)), ter(ter)
338 boost::container::flat_set<uint256> ofrsToRm)
339 : in(in), out(out), removableOffers(std::
move(ofrsToRm)), ter(ter)
346inline std::optional<Quality>
347qualityUpperBound(
ReadView const& v, Strand
const& strand)
350 std::optional<Quality> stepQ;
352 for (
auto const& step : strand)
354 if (
std::tie(stepQ, dir) = step->qualityUpperBound(v, dir); stepQ)
377template <StepAmount TOutAmt>
381 Strand
const& strand,
382 TOutAmt
const& remainingOut,
385 std::optional<QualityFunction> stepQualityFunc;
386 std::optional<QualityFunction> qf;
388 for (
auto const& step : strand)
390 if (
std::tie(stepQualityFunc, dir) = step->getQualityFunc(v, dir); stepQualityFunc)
394 qf = stepQualityFunc;
398 qf->combine(*stepQualityFunc);
408 if (!qf || qf->isConst())
411 auto const out = [&]() {
412 auto const out = qf->outFromAvgQ(limitQuality);
417 auto const roundedOut = TOutAmt{*out};
422 if (v.rules().enabled(featureMPTokensV2) && roundedOut > *out &&
423 !qf->satisfiesAvgQ(limitQuality, roundedOut))
426 return TOutAmt{*out};
437 static_assert(kAlwaysFalse,
"Unhandled StepAmount type");
459 std::vector<Strand const*> cur_;
461 std::vector<Strand const*> next_;
464 ActiveStrands(std::vector<Strand>
const& strands)
468 for (
auto& strand : strands)
475 activateNext(ReadView
const& v, std::optional<Quality>
const& limitQuality)
482 std::vector<std::pair<Quality, Strand const*>> strandQualities;
484 if (next_.
size() > 1)
486 for (Strand
const* strand : next_)
488 if (strand ==
nullptr)
493 if (
auto const qual = qualityUpperBound(v, *strand))
495 if (limitQuality && *qual < *limitQuality)
513 [](
auto const& lhs,
auto const& rhs) {
515 return std::get<Quality>(lhs) > std::get<Quality>(rhs);
518 next_.reserve(strandQualities.
size());
519 for (
auto const& sq : strandQualities)
521 next_.push_back(std::get<Strand const*>(sq));
528 [[nodiscard]] Strand
const*
531 if (i >= cur_.
size())
534 UNREACHABLE(
"xrpl::ActiveStrands::get : input out of range");
542 push(Strand
const* s)
549 pushRemainingCurToNext(
size_t i)
551 if (i >= cur_.
size())
584template <StepAmount TInAmt, StepAmount TOutAmt>
585FlowResult<TInAmt, TOutAmt>
589 TOutAmt
const& outReq,
605 Strand
const& strand;
612 Strand
const& strand,
614 : in(in), out(out), sb(std::move(sb)), strand(strand), quality(quality)
634 TOutAmt remainingOut(outReq);
639 ActiveStrands activeStrands(strands);
644 boost::container::flat_multiset<TInAmt> savedIns;
645 savedIns.reserve(maxTries);
646 boost::container::flat_multiset<TOutAmt> savedOuts;
647 savedOuts.reserve(maxTries);
649 auto sum = [](
auto const& col) {
658 boost::container::flat_set<uint256> ofrsToRmOnFail;
663 if (curTry >= maxTries)
668 activeStrands.activateNext(sb, limitQuality);
673 auto const limitRemainingOut = [&]() {
674 if (activeStrands.size() == 1 && limitQuality)
676 if (
auto const strand = activeStrands.get(0))
677 return limitOut(sb, *strand, remainingOut, *limitQuality);
681 auto const adjustedRemOut = limitRemainingOut != remainingOut;
683 boost::container::flat_set<uint256> ofrsToRm;
686 flowDebugInfo->newLiquidityPass();
687 for (
size_t strandIndex = 0, sie = activeStrands.size(); strandIndex != sie; ++strandIndex)
689 Strand
const* strand = activeStrands.get(strandIndex);
701 auto const strandQ = qualityUpperBound(sb, *strand);
702 if (!strandQ || *strandQ < *limitQuality)
710 offersConsidered += f.ofrsUsed;
719 f.out <= remainingOut && f.sandbox && (!remainingIn || f.in <= *remainingIn),
720 "xrpl::flow : remaining constraints");
724 JLOG(j.
trace()) <<
"New flow iter (iter, in, out): " << curTry - 1 <<
" "
730 if (limitQuality && q < *limitQuality &&
733 JLOG(j.
trace()) <<
"Path rejected by limitQuality"
734 <<
" limit: " << *limitQuality <<
" path q: " << q;
738 XRPL_ASSERT(!best,
"xrpl::flow : best is unset");
740 activeStrands.push(strand);
741 best.
emplace(f.in, f.out, std::move(*f.sandbox), *strand, q);
742 activeStrands.pushRemainingCurToNext(strandIndex + 1);
746 bool const shouldBreak = !best || offersConsidered >= maxOffersToConsider;
750 savedIns.insert(best->in);
751 savedOuts.insert(best->out);
752 remainingOut = outReq -
sum(savedOuts);
754 remainingIn = *sendMax -
sum(savedIns);
758 flowDebugInfo->pushPass(
764 <<
" remainingOut: " <<
to_string(remainingOut);
771 JLOG(j.
trace()) <<
"All strands dry.";
775 if (!ofrsToRm.empty())
778 for (
auto const& o : ofrsToRm)
789 auto const actualOut =
sum(savedOuts);
790 auto const actualIn =
sum(savedIns);
806 bool const fillOrKillEnabled = baseView.
rules().
enabled(fixFillOrKill);
808 if (actualOut != outReq)
810 if (actualOut > outReq)
830 return {
tecPATH_PARTIAL, actualIn, actualOut, std::move(ofrsToRmOnFail)};
848 XRPL_ASSERT(remainingIn,
"xrpl::flow : nonzero remainingIn");
850 return {
tecPATH_PARTIAL, actualIn, actualOut, std::move(ofrsToRmOnFail)};
853 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, SeqProxy const &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).