1#include <xrpl/basics/Log.h>
2#include <xrpl/basics/Number.h>
3#include <xrpl/basics/base_uint.h>
4#include <xrpl/basics/contract.h>
5#include <xrpl/beast/utility/Zero.h>
6#include <xrpl/beast/utility/instrumentation.h>
7#include <xrpl/json/json_writer.h>
8#include <xrpl/ledger/ReadView.h>
9#include <xrpl/protocol/AccountID.h>
10#include <xrpl/protocol/Asset.h>
11#include <xrpl/protocol/IOUAmount.h>
12#include <xrpl/protocol/Issue.h>
13#include <xrpl/protocol/MPTIssue.h>
14#include <xrpl/protocol/Quality.h>
15#include <xrpl/protocol/STPathSet.h>
16#include <xrpl/protocol/TER.h>
17#include <xrpl/protocol/UintTypes.h>
18#include <xrpl/tx/paths/detail/Steps.h>
20#include <boost/container/flat_set.hpp>
38 double const ratTol = 0.001;
52 double const diff = std::abs(a - b);
53 auto const r = diff /
std::max(std::abs(a), std::abs(b));
70 Asset const& curAsset)
100 return curAsset.
visit(
105 [&](
Issue const& issue) {
114 JLOG(j.error()) <<
"Found offer/account payment step. Aborting payment strand.";
115 UNREACHABLE(
"xrpl::toStep : offer/account payment payment strand");
123 "xrpl::toStep : currency or issuer");
132 JLOG(j.info()) <<
"Found xrp/xrp offer payment step";
136 XRPL_ASSERT(e2->
isOffer(),
"xrpl::toStep : is offer");
138 if (outAsset.
isXRP())
140 return curAsset.
visit(
147 return outAsset.
visit(
152 return curAsset.
visit(
154 return outAsset.
visit(
160 [&](
Issue const& issue) {
161 return outAsset.
visit(
174 Asset const& deliver,
178 bool ownerPaysTransferFee,
188 if ((sendMaxAsset && sendMaxAsset->getIssuer() ==
noAccount()) || (src ==
noAccount()) ||
193 (sendMaxAsset && sendMaxAsset->holds<
MPTIssue>() &&
194 sendMaxAsset->getIssuer() == beast::kZero))
199 auto const& pe =
path[i];
200 auto const t = pe.getNodeType();
211 if (hasAccount && (hasIssuer || hasCurrency))
214 if (hasIssuer &&
isXRP(pe.getIssuerID()))
217 if (hasAccount &&
isXRP(pe.getAccountID()))
220 if (hasCurrency && hasIssuer &&
isXRP(pe.getCurrency()) !=
isXRP(pe.getIssuerID()))
223 if (hasIssuer && (pe.getIssuerID() ==
noAccount()))
226 if (hasAccount && (pe.getAccountID() ==
noAccount()))
229 if (hasMPT && (hasCurrency || hasAccount))
232 if (hasMPT && hasIssuer && (pe.getIssuerID() !=
getMPTIssuer(pe.getMPTID())))
236 if (i > 0 &&
path[i - 1].hasMPT() && (hasAccount || (hasIssuer && !hasAsset)))
241 auto const& asset = sendMaxAsset ? *sendMaxAsset : deliver;
248 return Issue{issue.currency, src};
265 auto const t = [&]() {
267 return curAsset.
visit(
279 if (sendMaxAsset && sendMaxAsset->getIssuer() != src &&
280 (
path.empty() || !
path[0].isAccount() ||
281 path[0].getAccountID() != sendMaxAsset->getIssuer()))
283 normPath.
emplace_back(sendMaxAsset->getIssuer(), std::nullopt, std::nullopt);
286 for (
auto const& i :
path)
308 if (!((normPath.
back().isAccount() &&
317 if (!normPath.
back().isAccount() || normPath.
back().getAccountID() != dst)
323 if (normPath.
size() < 2)
326 auto const strandSrc = normPath.
front().getAccountID();
327 auto const strandDst = normPath.
back().getAccountID();
331 result.reserve(2 * normPath.
size());
340 boost::container::flat_set<Asset> seenBookOuts;
341 seenDirectAssets[0].reserve(normPath.
size());
342 seenDirectAssets[1].reserve(normPath.
size());
343 seenBookOuts.reserve(normPath.
size());
344 auto ctx = [&](
bool isLast =
false) {
353 ownerPaysTransferFee,
373 auto cur = &normPath[i];
374 auto const next = &normPath[i + 1];
388 if (cur->isAccount())
390 curAsset.
get<
Issue>().account = cur->getAccountID();
392 else if (cur->hasIssuer())
394 curAsset.
get<
Issue>().account = cur->getIssuerID();
399 if (cur->hasCurrency())
405 else if (cur->hasMPT())
407 curAsset = cur->getPathAsset().
get<
MPTID>();
411 auto getImpliedStep =
414 [&](
MPTIssue const&) -> ImpliedStepRet {
415 JLOG(j.
error()) <<
"MPT is invalid with rippling";
418 [&](
Issue const& issue) -> ImpliedStepRet {
423 if (cur->isAccount() && next->isAccount())
429 if (!
isXRP(curAsset) && curAsset.
getIssuer() != cur->getAccountID() &&
430 curAsset.
getIssuer() != next->getAccountID())
432 JLOG(j.
trace()) <<
"Inserting implied account";
433 auto msr = getImpliedStep(cur->getAccountID(), curAsset.
getIssuer(), curAsset);
435 return {msr.first, Strand{}};
436 result.push_back(std::move(msr.second));
442 else if (cur->isAccount() && next->isOffer())
445 if (curAsset.
getIssuer() != cur->getAccountID())
447 JLOG(j.
trace()) <<
"Inserting implied account before offer";
448 auto msr = getImpliedStep(cur->getAccountID(), curAsset.
getIssuer(), curAsset);
450 return {msr.first, Strand{}};
451 result.push_back(std::move(msr.second));
457 else if (cur->isOffer() && next->isAccount())
462 if (curAsset.
getIssuer() != next->getAccountID() && !
isXRP(next->getAccountID()))
466 if (i != normPath.
size() - 2)
472 return {msr.first, Strand{}};
473 result.push_back(std::move(msr.second));
477 JLOG(j.
trace()) <<
"Inserting implied account after offer";
478 auto msr = getImpliedStep(curAsset.
getIssuer(), next->getAccountID(), curAsset);
480 return {msr.first, Strand{}};
481 result.push_back(std::move(msr.second));
487 if (!next->isOffer() && next->hasAsset() && next->getPathAsset() != curAsset)
491 UNREACHABLE(
"xrpl::toStrand : offer currency mismatch");
496 auto s =
toStep(ctx( i == normPath.
size() - 2), cur, next, curAsset);
499 result.emplace_back(std::move(s.second));
503 JLOG(j.
debug()) <<
"toStep failed: " << s.first;
504 return {s.first, Strand{}};
508 auto checkStrand = [&]() ->
bool {
510 if (
auto r = s.directStepAccts())
512 if (
auto const r = s.bookStepBook())
519 auto curAsset = [&]() ->
Asset {
520 auto const& asset = sendMaxAsset ? *sendMaxAsset : deliver;
526 return Issue{issue.currency, src};
530 for (
auto const& s : result)
532 auto const accts = stepAccts(*s);
533 if (accts.first != curAcc)
536 if (
auto const b = s->bookStepBook())
538 if (curAsset != b->in)
544 curAsset.
get<
Issue>().account = accts.second;
547 curAcc = accts.second;
564 JLOG(j.
warn()) <<
"Flow check strand failed";
565 UNREACHABLE(
"xrpl::toStrand : invalid strand");
578 Asset const& deliver,
583 bool ownerPaysTransferFee,
592 auto insert = [&](Strand s) {
609 ownerPaysTransferFee,
614 auto const ter = sp.first;
615 auto& strand = sp.second;
619 JLOG(j.
trace()) <<
"failed to add default path";
625 else if (strand.empty())
627 JLOG(j.
trace()) <<
"toStrand failed";
632 insert(std::move(strand));
635 else if (paths.
empty())
637 JLOG(j.
debug()) <<
"Flow: Invalid transaction: No paths and direct "
638 "ripple not allowed.";
643 for (
auto const& p : paths)
653 ownerPaysTransferFee,
659 auto& strand = sp.second;
664 JLOG(j.
trace()) <<
"failed to add path: ter: " << ter
669 else if (strand.empty())
671 JLOG(j.
trace()) <<
"toStrand failed";
676 insert(std::move(strand));
681 return {lastFailTer, std::move(result)};
715 ,
prevStep(!strand.empty() ? strand.back().
get() : nullptr)
A generic endpoint for log messages.
Stream trace() const
Severity stream access functions.
Maintains AMM info per overall payment engine execution and individual iteration.
constexpr auto visit(Visitors &&... visitors) const -> decltype(auto)
constexpr TIss const & get() const
AccountID const & getIssuer() const
constexpr bool holds() const
Floating point representation of amounts with high dynamic range.
mantissa_type mantissa() const noexcept
exponent_type exponent() const noexcept
A currency issued by an account.
constexpr MPTID const & getMptID() const
constexpr bool isXRP() const
constexpr auto visit(Visitors &&... visitors) const -> decltype(auto)
AccountID const & getAccountID() const
PathAsset const & getPathAsset() const
AccountID const & getIssuerID() const
std::vector< STPath >::size_type size() const
A step in a payment path.
T emplace_back(T... args)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
std::pair< TER, std::unique_ptr< Step > > makeXrpEndpointStep(StrandContext const &ctx, AccountID const &acc)
std::pair< TER, std::vector< Strand > > toStrands(ReadView const &sb, AccountID const &src, AccountID const &dst, Asset const &deliver, std::optional< Quality > const &limitQuality, std::optional< Asset > const &sendMax, STPathSet const &paths, bool addDefaultPath, bool ownerPaysTransferFee, OfferCrossing offerCrossing, AMMContext &ammContext, std::optional< uint256 > const &domainID, beast::Journal j)
Create a Strand for each specified path (including the default path, if indicated).
Issue const & xrpIssue()
Returns an asset specifier that represents XRP.
std::pair< TER, std::unique_ptr< Step > > makeDirectStepI(StrandContext const &ctx, AccountID const &src, AccountID const &dst, Currency const &c)
std::pair< TER, std::unique_ptr< Step > > makeBookStepIi(StrandContext const &ctx, Issue const &in, Issue const &out)
bool isXRP(AccountID const &c)
T get(Section const §ion, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
std::pair< TER, Strand > toStrand(ReadView const &sb, AccountID const &src, AccountID const &dst, Asset const &deliver, std::optional< Quality > const &limitQuality, std::optional< Asset > const &sendMaxAsset, STPath const &path, bool ownerPaysTransferFee, OfferCrossing offerCrossing, AMMContext &ammContext, std::optional< uint256 > const &domainID, beast::Journal j)
Create a Strand for the specified path.
std::pair< TER, std::unique_ptr< Step > > makeBookStepMx(StrandContext const &ctx, MPTIssue const &in)
std::pair< TER, std::unique_ptr< Step > > makeMptEndpointStep(StrandContext const &ctx, AccountID const &src, AccountID const &dst, MPTID const &mpt)
BaseUInt< 160, detail::CurrencyTag > Currency
Currency is a hash representing a specific currency.
std::pair< TER, std::unique_ptr< Step > > makeBookStepMi(StrandContext const &ctx, MPTIssue const &in, Issue const &out)
AccountID getMPTIssuer(MPTID const &mptid)
std::pair< TER, std::unique_ptr< Step > > makeBookStepIx(StrandContext const &ctx, Issue const &in)
Currency const & xrpCurrency()
XRP currency.
static bool isDefaultPath(STPath const &path)
std::pair< TER, std::unique_ptr< Step > > makeBookStepXm(StrandContext const &ctx, MPTIssue const &out)
BaseUInt< 192 > MPTID
MPTID is a 192-bit value representing MPT Issuance ID, which is a concatenation of a 32-bit sequence ...
std::pair< TER, std::unique_ptr< Step > > makeBookStepXi(StrandContext const &ctx, Issue const &out)
bool checkNear(IOUAmount const &expected, IOUAmount const &actual)
constexpr Number abs(Number x) noexcept
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
AccountID const & noAccount()
A placeholder for empty accounts.
std::pair< TER, std::unique_ptr< Step > > makeBookStepIm(StrandContext const &ctx, Issue const &in, MPTIssue const &out)
bool isTesSuccess(TER x) noexcept
TERSubset< CanCvtToTER > TER
static std::pair< TER, std::unique_ptr< Step > > toStep(StrandContext const &ctx, STPathElement const *e1, STPathElement const *e2, Asset const &curAsset)
bool isConsistent(Asset const &asset)
AccountID const & xrpAccount()
Compute AccountID from public key.
static bool isXRPAccount(STPathElement const &pe)
bool isTemMalformed(TER x) noexcept
std::pair< TER, std::unique_ptr< Step > > makeBookStepMm(StrandContext const &ctx, MPTIssue const &in, MPTIssue const &out)
XRPL_NO_SANITIZE_ADDRESS void Throw(Args &&... args)
Context needed to build Strand Steps and for error checking.
StrandContext(ReadView const &view, std::vector< std::unique_ptr< Step > > const &strand, AccountID const &strandSrc, AccountID const &strandDst, Asset const &strandDeliver, std::optional< Quality > const &limitQuality, bool isLast, bool ownerPaysTransferFee, OfferCrossing offerCrossing, bool isDefaultPath, std::array< boost::container::flat_set< Asset >, 2 > &seenDirectAssets, boost::container::flat_set< Asset > &seenBookOuts, AMMContext &ammContext, std::optional< uint256 > const &domainID, beast::Journal j)
StrandContext constructor.
Asset const strandDeliver
Asset strand delivers.
std::optional< uint256 > domainID
std::optional< Quality > const limitQuality
Worst accepted quality.
size_t const strandSize
Length of Strand.
ReadView const & view
Current ReadView.
bool const isFirst
true if Step is first in Strand
std::array< boost::container::flat_set< Asset >, 2 > & seenDirectAssets
A strand may not include the same account node more than once in the same currency.
AccountID const strandSrc
Strand source account.
bool const ownerPaysTransferFee
true if owner, not sender, pays fee
bool const isLast
true if Step is last in Strand
bool const isDefaultPath
true if Strand is default path
boost::container::flat_set< Asset > & seenBookOuts
A strand may not include an offer that output the same issue more than once.
AccountID const strandDst
Strand destination account.
Step const *const prevStep
The previous step in the strand.
OfferCrossing const offerCrossing
Yes/Sell if offer crossing, not payment.