mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-23 15:10:34 +00:00
950 lines
30 KiB
C++
950 lines
30 KiB
C++
#include <xrpl/basics/Log.h>
|
|
#include <xrpl/basics/base_uint.h>
|
|
#include <xrpl/beast/utility/Journal.h>
|
|
#include <xrpl/beast/utility/Zero.h>
|
|
#include <xrpl/beast/utility/instrumentation.h>
|
|
#include <xrpl/ledger/PaymentSandbox.h>
|
|
#include <xrpl/ledger/helpers/AccountRootHelpers.h>
|
|
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
|
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
|
#include <xrpl/protocol/AccountID.h>
|
|
#include <xrpl/protocol/AmountConversions.h>
|
|
#include <xrpl/protocol/IOUAmount.h>
|
|
#include <xrpl/protocol/Indexes.h>
|
|
#include <xrpl/protocol/Issue.h>
|
|
#include <xrpl/protocol/LedgerFormats.h>
|
|
#include <xrpl/protocol/Quality.h>
|
|
#include <xrpl/protocol/SField.h>
|
|
#include <xrpl/protocol/STAmount.h>
|
|
#include <xrpl/protocol/STLedgerEntry.h>
|
|
#include <xrpl/protocol/TER.h>
|
|
#include <xrpl/protocol/UintTypes.h>
|
|
#include <xrpl/tx/paths/detail/EitherAmount.h>
|
|
#include <xrpl/tx/paths/detail/StepChecks.h>
|
|
#include <xrpl/tx/paths/detail/Steps.h>
|
|
|
|
#include <boost/container/flat_set.hpp>
|
|
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
namespace xrpl {
|
|
|
|
template <class TDerived>
|
|
class DirectStepI : public StepImp<IOUAmount, IOUAmount, DirectStepI<TDerived>>
|
|
{
|
|
protected:
|
|
AccountID src_;
|
|
AccountID dst_;
|
|
Currency currency_;
|
|
|
|
// Charge transfer fees when the prev step redeems
|
|
Step const* const prevStep_ = nullptr;
|
|
bool const isLast_;
|
|
beast::Journal const j_;
|
|
|
|
struct Cache
|
|
{
|
|
IOUAmount in;
|
|
IOUAmount srcToDst;
|
|
IOUAmount out;
|
|
DebtDirection srcDebtDir;
|
|
|
|
Cache(
|
|
IOUAmount const& in,
|
|
IOUAmount const& srcToDst,
|
|
IOUAmount const& out,
|
|
DebtDirection srcDebtDir)
|
|
: in(in), srcToDst(srcToDst), out(out), srcDebtDir(srcDebtDir)
|
|
{
|
|
}
|
|
};
|
|
|
|
std::optional<Cache> cache_;
|
|
|
|
// Compute the maximum value that can flow from src->dst at
|
|
// the best available quality.
|
|
// return: first element is max amount that can flow,
|
|
// second is the debt direction of the source w.r.t. the dst
|
|
[[nodiscard]] std::pair<IOUAmount, DebtDirection>
|
|
maxPaymentFlow(ReadView const& sb) const;
|
|
|
|
// Compute srcQOut and dstQIn when the source redeems.
|
|
[[nodiscard]] std::pair<std::uint32_t, std::uint32_t>
|
|
qualitiesSrcRedeems(ReadView const& sb) const;
|
|
|
|
// Compute srcQOut and dstQIn when the source issues.
|
|
[[nodiscard]] std::pair<std::uint32_t, std::uint32_t>
|
|
qualitiesSrcIssues(ReadView const& sb, DebtDirection prevStepDebtDirection) const;
|
|
|
|
// Returns srcQOut, dstQIn
|
|
[[nodiscard]] std::pair<std::uint32_t, std::uint32_t>
|
|
qualities(ReadView const& sb, DebtDirection srcDebtDir, StrandDirection strandDir) const;
|
|
|
|
private:
|
|
DirectStepI(
|
|
StrandContext const& ctx,
|
|
AccountID const& src,
|
|
AccountID const& dst,
|
|
Currency const& c)
|
|
: src_(src)
|
|
, dst_(dst)
|
|
, currency_(c)
|
|
, prevStep_(ctx.prevStep)
|
|
, isLast_(ctx.isLast)
|
|
, j_(ctx.j)
|
|
{
|
|
}
|
|
|
|
public:
|
|
[[nodiscard]] AccountID const&
|
|
src() const
|
|
{
|
|
return src_;
|
|
}
|
|
[[nodiscard]] AccountID const&
|
|
dst() const
|
|
{
|
|
return dst_;
|
|
}
|
|
[[nodiscard]] Currency const&
|
|
currency() const
|
|
{
|
|
return currency_;
|
|
}
|
|
|
|
[[nodiscard]] std::optional<EitherAmount>
|
|
cachedIn() const override
|
|
{
|
|
if (!cache_)
|
|
return std::nullopt;
|
|
return EitherAmount(cache_->in);
|
|
}
|
|
|
|
[[nodiscard]] std::optional<EitherAmount>
|
|
cachedOut() const override
|
|
{
|
|
if (!cache_)
|
|
return std::nullopt;
|
|
return EitherAmount(cache_->out);
|
|
}
|
|
|
|
[[nodiscard]] std::optional<AccountID>
|
|
directStepSrcAcct() const override
|
|
{
|
|
return src_;
|
|
}
|
|
|
|
[[nodiscard]] std::optional<std::pair<AccountID, AccountID>>
|
|
directStepAccts() const override
|
|
{
|
|
return std::make_pair(src_, dst_);
|
|
}
|
|
|
|
[[nodiscard]] DebtDirection
|
|
debtDirection(ReadView const& sb, StrandDirection dir) const override;
|
|
|
|
[[nodiscard]] std::uint32_t
|
|
lineQualityIn(ReadView const& v) const override;
|
|
|
|
[[nodiscard]] std::pair<std::optional<Quality>, DebtDirection>
|
|
qualityUpperBound(ReadView const& v, DebtDirection dir) const override;
|
|
|
|
std::pair<IOUAmount, IOUAmount>
|
|
revImp(
|
|
PaymentSandbox& sb,
|
|
ApplyView& afView,
|
|
boost::container::flat_set<uint256>& ofrsToRm,
|
|
IOUAmount const& out);
|
|
|
|
std::pair<IOUAmount, IOUAmount>
|
|
fwdImp(
|
|
PaymentSandbox& sb,
|
|
ApplyView& afView,
|
|
boost::container::flat_set<uint256>& ofrsToRm,
|
|
IOUAmount const& in);
|
|
|
|
std::pair<bool, EitherAmount>
|
|
validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in) override;
|
|
|
|
// Check for error, existing liquidity, and violations of auth/frozen
|
|
// constraints.
|
|
[[nodiscard]] TER
|
|
check(StrandContext const& ctx) const;
|
|
|
|
void
|
|
setCacheLimiting(
|
|
IOUAmount const& fwdIn,
|
|
IOUAmount const& fwdSrcToDst,
|
|
IOUAmount const& fwdOut,
|
|
DebtDirection srcDebtDir);
|
|
|
|
friend bool
|
|
operator==(DirectStepI const& lhs, DirectStepI const& rhs)
|
|
{
|
|
return lhs.src_ == rhs.src_ && lhs.dst_ == rhs.dst_ && lhs.currency_ == rhs.currency_;
|
|
}
|
|
|
|
friend bool
|
|
operator!=(DirectStepI const& lhs, DirectStepI const& rhs)
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
|
|
protected:
|
|
std::string
|
|
logStringImpl(char const* name) const
|
|
{
|
|
std::ostringstream ostr;
|
|
ostr << name << ": "
|
|
<< "\nSrc: " << src_ << "\nDst: " << dst_;
|
|
return ostr.str();
|
|
}
|
|
|
|
private:
|
|
[[nodiscard]] bool
|
|
equal(Step const& rhs) const override
|
|
{
|
|
if (auto ds = dynamic_cast<DirectStepI const*>(&rhs))
|
|
{
|
|
return *this == *ds;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
friend TDerived;
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
// Flow is used in two different circumstances for transferring funds:
|
|
// o Payments, and
|
|
// o Offer crossing.
|
|
// The rules for handling funds in these two cases are almost, but not
|
|
// quite, the same.
|
|
|
|
// Payment DirectStep class (not offer crossing).
|
|
class DirectIPaymentStep : public DirectStepI<DirectIPaymentStep>
|
|
{
|
|
public:
|
|
DirectIPaymentStep(
|
|
StrandContext const& ctx,
|
|
AccountID const& src,
|
|
AccountID const& dst,
|
|
Currency const& c)
|
|
: DirectStepI<DirectIPaymentStep>(ctx, src, dst, c)
|
|
{
|
|
}
|
|
|
|
using DirectStepI<DirectIPaymentStep>::check;
|
|
|
|
static bool
|
|
verifyPrevStepDebtDirection(DebtDirection)
|
|
{
|
|
// A payment doesn't care whether or not prevStepRedeems.
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
verifyDstQualityIn(std::uint32_t dstQIn)
|
|
{
|
|
// Payments have no particular expectations for what dstQIn will be.
|
|
return true;
|
|
}
|
|
|
|
[[nodiscard]] std::uint32_t
|
|
quality(ReadView const& sb, QualityDirection qDir) const;
|
|
|
|
// Compute the maximum value that can flow from src->dst at
|
|
// the best available quality.
|
|
// return: first element is max amount that can flow,
|
|
// second is the debt direction w.r.t. the source account
|
|
[[nodiscard]] std::pair<IOUAmount, DebtDirection>
|
|
maxFlow(ReadView const& sb, IOUAmount const& desired) const;
|
|
|
|
// Verify the consistency of the step. These checks are specific to
|
|
// payments and assume that general checks were already performed.
|
|
[[nodiscard]] TER
|
|
check(StrandContext const& ctx, SLE::const_ref sleSrc) const;
|
|
|
|
[[nodiscard]] std::string
|
|
logString() const override
|
|
{
|
|
return logStringImpl("DirectIPaymentStep");
|
|
}
|
|
};
|
|
|
|
// Offer crossing DirectStep class (not a payment).
|
|
class DirectIOfferCrossingStep : public DirectStepI<DirectIOfferCrossingStep>
|
|
{
|
|
public:
|
|
DirectIOfferCrossingStep(
|
|
StrandContext const& ctx,
|
|
AccountID const& src,
|
|
AccountID const& dst,
|
|
Currency const& c)
|
|
: DirectStepI<DirectIOfferCrossingStep>(ctx, src, dst, c)
|
|
{
|
|
}
|
|
|
|
using DirectStepI<DirectIOfferCrossingStep>::check;
|
|
|
|
static bool
|
|
verifyPrevStepDebtDirection(DebtDirection prevStepDir)
|
|
{
|
|
// During offer crossing we rely on the fact that prevStepRedeems
|
|
// will *always* issue. That's because:
|
|
// o If there's a prevStep_, it will always be a BookStep.
|
|
// o BookStep::debtDirection() always returns `issues` when offer
|
|
// crossing.
|
|
// An assert based on this return value will tell us if that
|
|
// behavior changes.
|
|
return issues(prevStepDir);
|
|
}
|
|
|
|
static bool
|
|
verifyDstQualityIn(std::uint32_t dstQIn)
|
|
{
|
|
// Due to a couple of factors dstQIn is always QUALITY_ONE for
|
|
// offer crossing. If that changes we need to know.
|
|
return dstQIn == QUALITY_ONE;
|
|
}
|
|
|
|
static std::uint32_t
|
|
quality(ReadView const& sb, QualityDirection qDir);
|
|
|
|
// Compute the maximum value that can flow from src->dst at
|
|
// the best available quality.
|
|
// return: first element is max amount that can flow,
|
|
// second is the debt direction w.r.t the source
|
|
[[nodiscard]] std::pair<IOUAmount, DebtDirection>
|
|
maxFlow(ReadView const& sb, IOUAmount const& desired) const;
|
|
|
|
// Verify the consistency of the step. These checks are specific to
|
|
// offer crossing and assume that general checks were already performed.
|
|
static TER
|
|
check(StrandContext const& ctx, SLE::const_ref sleSrc);
|
|
|
|
[[nodiscard]] std::string
|
|
logString() const override
|
|
{
|
|
return logStringImpl("DirectIOfferCrossingStep");
|
|
}
|
|
};
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
std::uint32_t
|
|
DirectIPaymentStep::quality(ReadView const& sb, QualityDirection qDir) const
|
|
{
|
|
if (src_ == dst_)
|
|
return QUALITY_ONE;
|
|
|
|
auto const sle = sb.read(keylet::trustLine(dst_, src_, currency_));
|
|
|
|
if (!sle)
|
|
return QUALITY_ONE;
|
|
|
|
auto const& field = [&, this]() -> SF_UINT32 const& {
|
|
if (qDir == QualityDirection::In)
|
|
{
|
|
// compute dst quality in
|
|
if (this->dst_ < this->src_)
|
|
{
|
|
return sfLowQualityIn;
|
|
}
|
|
|
|
return sfHighQualityIn;
|
|
}
|
|
|
|
// compute src quality out
|
|
if (this->src_ < this->dst_)
|
|
{
|
|
return sfLowQualityOut;
|
|
}
|
|
|
|
return sfHighQualityOut;
|
|
}();
|
|
|
|
if (!sle->isFieldPresent(field))
|
|
return QUALITY_ONE;
|
|
|
|
auto const q = (*sle)[field];
|
|
if (q == 0u)
|
|
return QUALITY_ONE;
|
|
return q;
|
|
}
|
|
|
|
std::uint32_t
|
|
DirectIOfferCrossingStep::quality(ReadView const&, QualityDirection qDir)
|
|
{
|
|
// If offer crossing then ignore trust line Quality fields. This
|
|
// preserves a long-standing tradition.
|
|
return QUALITY_ONE;
|
|
}
|
|
|
|
std::pair<IOUAmount, DebtDirection>
|
|
DirectIPaymentStep::maxFlow(ReadView const& sb, IOUAmount const&) const
|
|
{
|
|
return maxPaymentFlow(sb);
|
|
}
|
|
|
|
std::pair<IOUAmount, DebtDirection>
|
|
DirectIOfferCrossingStep::maxFlow(ReadView const& sb, IOUAmount const& desired) const
|
|
{
|
|
// When isLast and offer crossing then ignore trust line limits. Offer
|
|
// crossing has the ability to exceed the limit set by a trust line.
|
|
// We presume that if someone is creating an offer then they intend to
|
|
// fill as much of that offer as possible, even if the offer exceeds
|
|
// the limit that a trust line sets.
|
|
//
|
|
// A note on using "out" as the desired parameter for maxFlow. In some
|
|
// circumstances during payments we end up needing a value larger than
|
|
// "out" for "maxSrcToDst". But as of now (June 2016) that never happens
|
|
// during offer crossing. That's because, due to a couple of factors,
|
|
// "dstQIn" is always QUALITY_ONE for offer crossing.
|
|
|
|
if (isLast_)
|
|
return {desired, DebtDirection::Issues};
|
|
|
|
return maxPaymentFlow(sb);
|
|
}
|
|
|
|
TER
|
|
DirectIPaymentStep::check(StrandContext const& ctx, SLE::const_ref sleSrc) const
|
|
{
|
|
// Since this is a payment a trust line must be present. Perform all
|
|
// trust line related checks.
|
|
{
|
|
auto const sleLine = ctx.view.read(keylet::trustLine(src_, dst_, currency_));
|
|
if (!sleLine)
|
|
{
|
|
JLOG(j_.trace()) << "DirectStepI: No credit line. " << *this;
|
|
return terNO_LINE;
|
|
}
|
|
|
|
auto const authField = (src_ > dst_) ? lsfHighAuth : lsfLowAuth;
|
|
|
|
if (sleSrc->isFlag(lsfRequireAuth) && !sleLine->isFlag(authField) &&
|
|
(*sleLine)[sfBalance] == beast::kZero)
|
|
{
|
|
JLOG(j_.debug()) << "DirectStepI: can't receive IOUs from issuer without auth."
|
|
<< " src: " << src_;
|
|
return terNO_AUTH;
|
|
}
|
|
|
|
if (ctx.prevStep != nullptr)
|
|
{
|
|
if (ctx.prevStep->bookStepBook())
|
|
{
|
|
if (sleLine->isFlag((src_ > dst_) ? lsfHighNoRipple : lsfLowNoRipple))
|
|
return terNO_RIPPLE;
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
auto const owed = creditBalance(ctx.view, dst_, src_, currency_);
|
|
if (owed <= beast::kZero)
|
|
{
|
|
auto const limit = creditLimit(ctx.view, dst_, src_, currency_);
|
|
if (-owed >= limit)
|
|
{
|
|
JLOG(j_.debug()) << "DirectStepI: dry: owed: " << owed << " limit: " << limit;
|
|
return tecPATH_DRY;
|
|
}
|
|
}
|
|
}
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
TER
|
|
DirectIOfferCrossingStep::check(StrandContext const&, SLE::const_ref)
|
|
{
|
|
// The standard checks are all we can do because any remaining checks
|
|
// require the existence of a trust line. Offer crossing does not
|
|
// require a pre-existing trust line.
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
template <class TDerived>
|
|
std::pair<IOUAmount, DebtDirection>
|
|
DirectStepI<TDerived>::maxPaymentFlow(ReadView const& sb) const
|
|
{
|
|
auto const srcOwed = toAmount<IOUAmount>(
|
|
accountHolds(sb, src_, currency_, dst_, FreezeHandling::IgnoreFreeze, j_));
|
|
|
|
if (srcOwed.signum() > 0)
|
|
return {srcOwed, DebtDirection::Redeems};
|
|
|
|
// srcOwed is negative or zero
|
|
return {creditLimit2(sb, dst_, src_, currency_) + srcOwed, DebtDirection::Issues};
|
|
}
|
|
|
|
template <class TDerived>
|
|
DebtDirection
|
|
DirectStepI<TDerived>::debtDirection(ReadView const& sb, StrandDirection dir) const
|
|
{
|
|
if (dir == StrandDirection::Forward && cache_)
|
|
return cache_->srcDebtDir;
|
|
|
|
auto const srcOwed = accountHolds(sb, src_, currency_, dst_, FreezeHandling::IgnoreFreeze, j_);
|
|
return srcOwed.signum() > 0 ? DebtDirection::Redeems : DebtDirection::Issues;
|
|
}
|
|
|
|
template <class TDerived>
|
|
std::pair<IOUAmount, IOUAmount>
|
|
DirectStepI<TDerived>::revImp(
|
|
PaymentSandbox& sb,
|
|
ApplyView& /*afView*/,
|
|
boost::container::flat_set<uint256>& /*ofrsToRm*/,
|
|
IOUAmount const& out)
|
|
{
|
|
cache_.reset();
|
|
|
|
auto const [maxSrcToDst, srcDebtDir] = static_cast<TDerived const*>(this)->maxFlow(sb, out);
|
|
|
|
auto const [srcQOut, dstQIn] = qualities(sb, srcDebtDir, StrandDirection::Reverse);
|
|
XRPL_ASSERT(
|
|
static_cast<TDerived const*>(this)->verifyDstQualityIn(dstQIn),
|
|
"xrpl::DirectStepI : valid destination quality");
|
|
|
|
Issue const srcToDstIss(currency_, redeems(srcDebtDir) ? dst_ : src_);
|
|
|
|
JLOG(j_.trace()) << "DirectStepI::rev"
|
|
<< " srcRedeems: " << redeems(srcDebtDir) << " outReq: " << to_string(out)
|
|
<< " maxSrcToDst: " << to_string(maxSrcToDst) << " srcQOut: " << srcQOut
|
|
<< " dstQIn: " << dstQIn;
|
|
|
|
if (maxSrcToDst.signum() <= 0)
|
|
{
|
|
JLOG(j_.trace()) << "DirectStepI::rev: dry";
|
|
cache_.emplace(
|
|
IOUAmount(beast::kZero), IOUAmount(beast::kZero), IOUAmount(beast::kZero), srcDebtDir);
|
|
return {beast::kZero, beast::kZero};
|
|
}
|
|
|
|
IOUAmount const srcToDst = mulRatio(out, QUALITY_ONE, dstQIn, /*roundUp*/ true);
|
|
|
|
if (srcToDst <= maxSrcToDst)
|
|
{
|
|
IOUAmount const in = mulRatio(srcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
|
|
cache_.emplace(in, srcToDst, out, srcDebtDir);
|
|
directSendNoFee(
|
|
sb,
|
|
src_,
|
|
dst_,
|
|
toSTAmount(srcToDst, srcToDstIss),
|
|
/*checkIssuer*/ true,
|
|
j_);
|
|
JLOG(j_.trace()) << "DirectStepI::rev: Non-limiting"
|
|
<< " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(in)
|
|
<< " srcToDst: " << to_string(srcToDst) << " out: " << to_string(out);
|
|
return {in, out};
|
|
}
|
|
|
|
// limiting node
|
|
IOUAmount const in = mulRatio(maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
|
|
IOUAmount const actualOut = mulRatio(maxSrcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
|
|
cache_.emplace(in, maxSrcToDst, actualOut, srcDebtDir);
|
|
directSendNoFee(
|
|
sb,
|
|
src_,
|
|
dst_,
|
|
toSTAmount(maxSrcToDst, srcToDstIss),
|
|
/*checkIssuer*/ true,
|
|
j_);
|
|
JLOG(j_.trace()) << "DirectStepI::rev: Limiting"
|
|
<< " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(in)
|
|
<< " srcToDst: " << to_string(maxSrcToDst) << " out: " << to_string(out);
|
|
return {in, actualOut};
|
|
}
|
|
|
|
// The forward pass should never have more liquidity than the reverse
|
|
// pass. But sometimes rounding differences cause the forward pass to
|
|
// deliver more liquidity. Use the cached values from the reverse pass
|
|
// to prevent this.
|
|
template <class TDerived>
|
|
void
|
|
DirectStepI<TDerived>::setCacheLimiting(
|
|
IOUAmount const& fwdIn,
|
|
IOUAmount const& fwdSrcToDst,
|
|
IOUAmount const& fwdOut,
|
|
DebtDirection srcDebtDir)
|
|
{
|
|
// NOLINTBEGIN(bugprone-unchecked-optional-access) cache_ always set before setCacheLimiting is
|
|
// called
|
|
if (cache_->in < fwdIn)
|
|
{
|
|
IOUAmount const smallDiff(1, -9);
|
|
auto const diff = fwdIn - cache_->in;
|
|
if (diff > smallDiff)
|
|
{
|
|
if (fwdIn.exponent() != cache_->in.exponent() || !cache_->in.mantissa() ||
|
|
(double(fwdIn.mantissa()) / double(cache_->in.mantissa())) > 1.01)
|
|
{
|
|
// Detect large diffs on forward pass so they may be
|
|
// investigated
|
|
JLOG(j_.warn()) << "DirectStepI::fwd: setCacheLimiting"
|
|
<< " fwdIn: " << to_string(fwdIn)
|
|
<< " cacheIn: " << to_string(cache_->in)
|
|
<< " fwdSrcToDst: " << to_string(fwdSrcToDst)
|
|
<< " cacheSrcToDst: " << to_string(cache_->srcToDst)
|
|
<< " fwdOut: " << to_string(fwdOut)
|
|
<< " cacheOut: " << to_string(cache_->out);
|
|
cache_.emplace(fwdIn, fwdSrcToDst, fwdOut, srcDebtDir);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
cache_->in = fwdIn;
|
|
if (fwdSrcToDst < cache_->srcToDst)
|
|
cache_->srcToDst = fwdSrcToDst;
|
|
if (fwdOut < cache_->out)
|
|
cache_->out = fwdOut;
|
|
cache_->srcDebtDir = srcDebtDir;
|
|
// NOLINTEND(bugprone-unchecked-optional-access)
|
|
};
|
|
|
|
template <class TDerived>
|
|
std::pair<IOUAmount, IOUAmount>
|
|
DirectStepI<TDerived>::fwdImp(
|
|
PaymentSandbox& sb,
|
|
ApplyView& /*afView*/,
|
|
boost::container::flat_set<uint256>& /*ofrsToRm*/,
|
|
IOUAmount const& in)
|
|
{
|
|
XRPL_ASSERT(cache_, "xrpl::DirectStepI::fwdImp : cache is set");
|
|
// NOLINTBEGIN(bugprone-unchecked-optional-access) assert above
|
|
|
|
auto const [maxSrcToDst, srcDebtDir] =
|
|
static_cast<TDerived const*>(this)->maxFlow(sb, cache_->srcToDst);
|
|
|
|
auto const [srcQOut, dstQIn] = qualities(sb, srcDebtDir, StrandDirection::Forward);
|
|
|
|
Issue const srcToDstIss(currency_, redeems(srcDebtDir) ? dst_ : src_);
|
|
|
|
JLOG(j_.trace()) << "DirectStepI::fwd"
|
|
<< " srcRedeems: " << redeems(srcDebtDir) << " inReq: " << to_string(in)
|
|
<< " maxSrcToDst: " << to_string(maxSrcToDst) << " srcQOut: " << srcQOut
|
|
<< " dstQIn: " << dstQIn;
|
|
|
|
if (maxSrcToDst.signum() <= 0)
|
|
{
|
|
JLOG(j_.trace()) << "DirectStepI::fwd: dry";
|
|
cache_.emplace(
|
|
IOUAmount(beast::kZero), IOUAmount(beast::kZero), IOUAmount(beast::kZero), srcDebtDir);
|
|
return {beast::kZero, beast::kZero};
|
|
}
|
|
|
|
IOUAmount const srcToDst = mulRatio(in, QUALITY_ONE, srcQOut, /*roundUp*/ false);
|
|
|
|
if (srcToDst <= maxSrcToDst)
|
|
{
|
|
IOUAmount const out = mulRatio(srcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
|
|
setCacheLimiting(in, srcToDst, out, srcDebtDir);
|
|
directSendNoFee(
|
|
sb,
|
|
src_,
|
|
dst_,
|
|
toSTAmount(cache_->srcToDst, srcToDstIss),
|
|
/*checkIssuer*/ true,
|
|
j_);
|
|
JLOG(j_.trace()) << "DirectStepI::fwd: Non-limiting"
|
|
<< " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(in)
|
|
<< " srcToDst: " << to_string(srcToDst) << " out: " << to_string(out);
|
|
}
|
|
else
|
|
{
|
|
// limiting node
|
|
IOUAmount const actualIn = mulRatio(maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true);
|
|
IOUAmount const out = mulRatio(maxSrcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false);
|
|
setCacheLimiting(actualIn, maxSrcToDst, out, srcDebtDir);
|
|
directSendNoFee(
|
|
sb,
|
|
src_,
|
|
dst_,
|
|
toSTAmount(cache_->srcToDst, srcToDstIss),
|
|
/*checkIssuer*/ true,
|
|
j_);
|
|
JLOG(j_.trace()) << "DirectStepI::rev: Limiting"
|
|
<< " srcRedeems: " << redeems(srcDebtDir) << " in: " << to_string(actualIn)
|
|
<< " srcToDst: " << to_string(srcToDst) << " out: " << to_string(out);
|
|
}
|
|
return {cache_->in, cache_->out};
|
|
// NOLINTEND(bugprone-unchecked-optional-access)
|
|
}
|
|
|
|
template <class TDerived>
|
|
std::pair<bool, EitherAmount>
|
|
DirectStepI<TDerived>::validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in)
|
|
{
|
|
if (!cache_)
|
|
{
|
|
JLOG(j_.trace()) << "Expected valid cache in validFwd";
|
|
return {false, EitherAmount(IOUAmount(beast::kZero))};
|
|
}
|
|
|
|
auto const savCache = *cache_;
|
|
|
|
XRPL_ASSERT(in.holds<IOUAmount>(), "xrpl::DirectStepI::validFwd : input is IOU");
|
|
|
|
auto const [maxSrcToDst, srcDebtDir] =
|
|
static_cast<TDerived const*>(this)->maxFlow(sb, cache_->srcToDst);
|
|
(void)srcDebtDir;
|
|
|
|
try
|
|
{
|
|
boost::container::flat_set<uint256> dummy;
|
|
fwdImp(sb, afView, dummy, in.get<IOUAmount>()); // changes cache
|
|
}
|
|
catch (FlowException const&)
|
|
{
|
|
return {false, EitherAmount(IOUAmount(beast::kZero))};
|
|
}
|
|
|
|
// NOLINTBEGIN(bugprone-unchecked-optional-access) fwdImp sets cache_ on success
|
|
if (maxSrcToDst < cache_->srcToDst)
|
|
{
|
|
JLOG(j_.warn()) << "DirectStepI: Strand re-execute check failed."
|
|
<< " Exceeded max src->dst limit"
|
|
<< " max src->dst: " << to_string(maxSrcToDst)
|
|
<< " actual src->dst: " << to_string(cache_->srcToDst);
|
|
return {false, EitherAmount(cache_->out)};
|
|
}
|
|
|
|
if (!(checkNear(savCache.in, cache_->in) && checkNear(savCache.out, cache_->out)))
|
|
{
|
|
JLOG(j_.warn()) << "DirectStepI: Strand re-execute check failed."
|
|
<< " ExpectedIn: " << to_string(savCache.in)
|
|
<< " CachedIn: " << to_string(cache_->in)
|
|
<< " ExpectedOut: " << to_string(savCache.out)
|
|
<< " CachedOut: " << to_string(cache_->out);
|
|
return {false, EitherAmount(cache_->out)};
|
|
}
|
|
return {true, EitherAmount(cache_->out)};
|
|
// NOLINTEND(bugprone-unchecked-optional-access)
|
|
}
|
|
|
|
// Returns srcQOut, dstQIn
|
|
template <class TDerived>
|
|
std::pair<std::uint32_t, std::uint32_t>
|
|
DirectStepI<TDerived>::qualitiesSrcRedeems(ReadView const& sb) const
|
|
{
|
|
if (prevStep_ == nullptr)
|
|
return {QUALITY_ONE, QUALITY_ONE};
|
|
|
|
auto const prevStepQIn = prevStep_->lineQualityIn(sb);
|
|
auto srcQOut = static_cast<TDerived const*>(this)->quality(sb, QualityDirection::Out);
|
|
|
|
if (prevStepQIn > srcQOut)
|
|
srcQOut = prevStepQIn;
|
|
return {srcQOut, QUALITY_ONE};
|
|
}
|
|
|
|
// Returns srcQOut, dstQIn
|
|
template <class TDerived>
|
|
std::pair<std::uint32_t, std::uint32_t>
|
|
DirectStepI<TDerived>::qualitiesSrcIssues(ReadView const& sb, DebtDirection prevStepDebtDirection)
|
|
const
|
|
{
|
|
// Charge a transfer rate when issuing and previous step redeems
|
|
|
|
XRPL_ASSERT(
|
|
static_cast<TDerived const*>(this)->verifyPrevStepDebtDirection(prevStepDebtDirection),
|
|
"xrpl::DirectStepI::qualitiesSrcIssues : will prevStepDebtDirection "
|
|
"issue");
|
|
|
|
std::uint32_t const srcQOut =
|
|
redeems(prevStepDebtDirection) ? transferRate(sb, src_).value : QUALITY_ONE;
|
|
auto dstQIn = static_cast<TDerived const*>(this)->quality(sb, QualityDirection::In);
|
|
|
|
if (isLast_ && dstQIn > QUALITY_ONE)
|
|
dstQIn = QUALITY_ONE;
|
|
return {srcQOut, dstQIn};
|
|
}
|
|
|
|
// Returns srcQOut, dstQIn
|
|
template <class TDerived>
|
|
std::pair<std::uint32_t, std::uint32_t>
|
|
DirectStepI<TDerived>::qualities(
|
|
ReadView const& sb,
|
|
DebtDirection srcDebtDir,
|
|
StrandDirection strandDir) const
|
|
{
|
|
if (redeems(srcDebtDir))
|
|
{
|
|
return qualitiesSrcRedeems(sb);
|
|
}
|
|
|
|
auto const prevStepDebtDirection = [&] {
|
|
if (prevStep_)
|
|
return prevStep_->debtDirection(sb, strandDir);
|
|
return DebtDirection::Issues;
|
|
}();
|
|
return qualitiesSrcIssues(sb, prevStepDebtDirection);
|
|
}
|
|
|
|
template <class TDerived>
|
|
std::uint32_t
|
|
DirectStepI<TDerived>::lineQualityIn(ReadView const& v) const
|
|
{
|
|
// dst quality in
|
|
return static_cast<TDerived const*>(this)->quality(v, QualityDirection::In);
|
|
}
|
|
|
|
template <class TDerived>
|
|
std::pair<std::optional<Quality>, DebtDirection>
|
|
DirectStepI<TDerived>::qualityUpperBound(ReadView const& v, DebtDirection prevStepDir) const
|
|
{
|
|
auto const dir = this->debtDirection(v, StrandDirection::Forward);
|
|
|
|
auto const [srcQOut, dstQIn] =
|
|
redeems(dir) ? qualitiesSrcRedeems(v) : qualitiesSrcIssues(v, prevStepDir);
|
|
|
|
Issue const iss{currency_, src_};
|
|
// Be careful not to switch the parameters to `getRate`. The
|
|
// `getRate(offerOut, offerIn)` function is usually used for offers. It
|
|
// returns offerIn/offerOut. For a direct step, the rate is srcQOut/dstQIn
|
|
// (Input*dstQIn/srcQOut = Output; So rate = srcQOut/dstQIn). Although the
|
|
// first parameter is called `offerOut`, it should take the `dstQIn`
|
|
// variable.
|
|
return {Quality(getRate(STAmount(iss, dstQIn), STAmount(iss, srcQOut))), dir};
|
|
}
|
|
|
|
template <class TDerived>
|
|
TER
|
|
DirectStepI<TDerived>::check(StrandContext const& ctx) const
|
|
{
|
|
// The following checks apply for both payments and offer crossing.
|
|
if (!src_ || !dst_)
|
|
{
|
|
JLOG(j_.debug()) << "DirectStepI: specified bad account.";
|
|
return temBAD_PATH;
|
|
}
|
|
|
|
if (src_ == dst_)
|
|
{
|
|
JLOG(j_.debug()) << "DirectStepI: same src and dst.";
|
|
return temBAD_PATH;
|
|
}
|
|
|
|
auto const sleSrc = ctx.view.read(keylet::account(src_));
|
|
if (!sleSrc)
|
|
{
|
|
JLOG(j_.warn()) << "DirectStepI: can't receive IOUs from non-existent issuer: " << src_;
|
|
return terNO_ACCOUNT;
|
|
}
|
|
|
|
// pure issue/redeem can't be frozen
|
|
if (!(ctx.isLast && ctx.isFirst))
|
|
{
|
|
auto const ter = checkFreeze(ctx.view, src_, dst_, currency_);
|
|
if (!isTesSuccess(ter))
|
|
return ter;
|
|
}
|
|
|
|
// If previous step was a direct step then we need to check
|
|
// no ripple flags.
|
|
if (ctx.prevStep != nullptr)
|
|
{
|
|
if (auto prevSrc = ctx.prevStep->directStepSrcAcct())
|
|
{
|
|
auto const ter = checkNoRipple(ctx.view, *prevSrc, src_, dst_, currency_, j_);
|
|
if (!isTesSuccess(ter))
|
|
return ter;
|
|
}
|
|
}
|
|
{
|
|
Issue const srcIssue{currency_, src_};
|
|
Issue const dstIssue{currency_, dst_};
|
|
|
|
if (ctx.seenBookOuts.count(srcIssue) != 0u)
|
|
{
|
|
if (ctx.prevStep == nullptr)
|
|
{
|
|
// LCOV_EXCL_START
|
|
UNREACHABLE(
|
|
"xrpl::DirectStepI::check : prev seen book without a "
|
|
"prev step");
|
|
return temBAD_PATH_LOOP;
|
|
// LCOV_EXCL_STOP
|
|
}
|
|
|
|
// This is OK if the previous step is a book step that outputs this
|
|
// issue
|
|
if (auto book = ctx.prevStep->bookStepBook())
|
|
{
|
|
if (book->out.get<Issue>() != srcIssue)
|
|
return temBAD_PATH_LOOP;
|
|
}
|
|
}
|
|
|
|
if (!ctx.seenDirectAssets[0].insert(srcIssue).second ||
|
|
!ctx.seenDirectAssets[1].insert(dstIssue).second)
|
|
{
|
|
JLOG(j_.debug()) << "DirectStepI: loop detected: Index: " << ctx.strandSize << ' '
|
|
<< *this;
|
|
return temBAD_PATH_LOOP;
|
|
}
|
|
}
|
|
|
|
return static_cast<TDerived const*>(this)->check(ctx, sleSrc);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
namespace test {
|
|
// Needed for testing
|
|
bool
|
|
directStepEqual(
|
|
Step const& step,
|
|
AccountID const& src,
|
|
AccountID const& dst,
|
|
Currency const& currency)
|
|
{
|
|
if (auto ds = dynamic_cast<DirectStepI<DirectIPaymentStep> const*>(&step))
|
|
{
|
|
return ds->src() == src && ds->dst() == dst && ds->currency() == currency;
|
|
}
|
|
return false;
|
|
}
|
|
} // namespace test
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
std::pair<TER, std::unique_ptr<Step>>
|
|
makeDirectStepI(
|
|
StrandContext const& ctx,
|
|
AccountID const& src,
|
|
AccountID const& dst,
|
|
Currency const& c)
|
|
{
|
|
TER ter = tefINTERNAL;
|
|
std::unique_ptr<Step> r;
|
|
if (ctx.offerCrossing != OfferCrossing::No)
|
|
{
|
|
auto offerCrossingStep = std::make_unique<DirectIOfferCrossingStep>(ctx, src, dst, c);
|
|
ter = offerCrossingStep->check(ctx);
|
|
r = std::move(offerCrossingStep);
|
|
}
|
|
else // payment
|
|
{
|
|
auto paymentStep = std::make_unique<DirectIPaymentStep>(ctx, src, dst, c);
|
|
ter = paymentStep->check(ctx);
|
|
r = std::move(paymentStep);
|
|
}
|
|
if (!isTesSuccess(ter))
|
|
return {ter, nullptr};
|
|
|
|
return {tesSUCCESS, std::move(r)};
|
|
}
|
|
|
|
} // namespace xrpl
|