diff --git a/Builds/CMake/RippledCore.cmake b/Builds/CMake/RippledCore.cmake index 369eda24a06..4ac9dc250eb 100644 --- a/Builds/CMake/RippledCore.cmake +++ b/Builds/CMake/RippledCore.cmake @@ -489,7 +489,6 @@ target_sources (rippled PRIVATE src/ripple/app/paths/impl/AMMOffer.cpp src/ripple/app/paths/impl/BookStep.cpp src/ripple/app/paths/impl/DirectStep.cpp - src/ripple/app/paths/impl/DirectStepCFT.cpp src/ripple/app/paths/impl/PaySteps.cpp src/ripple/app/paths/impl/XRPEndpointStep.cpp src/ripple/app/rdb/backend/detail/impl/Node.cpp diff --git a/src/ripple/app/paths/Flow.cpp b/src/ripple/app/paths/Flow.cpp index 814e220bd5f..83379d34e79 100644 --- a/src/ripple/app/paths/Flow.cpp +++ b/src/ripple/app/paths/Flow.cpp @@ -194,25 +194,6 @@ flow( } assert(!srcIsXRP && !dstIsXRP); - if (srcIssue.currency.isCFT()) - { - assert(dstIssue.currency.isCFT()); - return finishFlow( - sb, - srcIssue, - dstIssue, - flow( - sb, - strands, - asDeliver.cft, - partialPayment, - offerCrossing, - limitQuality, - sendMax, - j, - ammContext, - flowDebugInfo)); - } return finishFlow( sb, srcIssue, diff --git a/src/ripple/app/paths/Pathfinder.cpp b/src/ripple/app/paths/Pathfinder.cpp index 053efd75d45..d67f1426ade 100644 --- a/src/ripple/app/paths/Pathfinder.cpp +++ b/src/ripple/app/paths/Pathfinder.cpp @@ -944,7 +944,7 @@ Pathfinder::addLink( auto const& uEndCurrency = pathEnd.getCurrency(); auto const& uEndIssuer = pathEnd.getIssuerID(); auto const& uEndAccount = pathEnd.getAccountID(); - bool const bOnXRP = uEndCurrency.isXRP(); + bool const bOnXRP = isXRP(uEndCurrency); // Does pathfinding really need to get this to // a gateway (the issuer of the destination amount) diff --git a/src/ripple/app/paths/impl/DirectStepCFT.cpp b/src/ripple/app/paths/impl/DirectStepCFT.cpp deleted file mode 100644 index 5c663d57862..00000000000 --- a/src/ripple/app/paths/impl/DirectStepCFT.cpp +++ /dev/null @@ -1,959 +0,0 @@ -//------------------------------------------------------------------------------ -/* - This file is part of rippled: https://github.com/ripple/rippled - Copyright (c) 2012, 2013 Ripple Labs Inc. - - Permission to use, copy, modify, and/or distribute this software for any - purpose with or without fee is hereby granted, provided that the above - copyright notice and this permission notice appear in all copies. - - THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -*/ -//============================================================================== - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -namespace ripple { - -template -class DirectStepCFT - : public StepImp> -{ -protected: - AccountID const src_; - AccountID const dst_; - AccountID const issuer_; - uint256 cftID_; - - // Charge transfer fees when the prev step redeems - Step const* const prevStep_ = nullptr; - bool const isLast_; - beast::Journal const j_; - - struct Cache - { - CFTAmount in; - CFTAmount srcToDst; - CFTAmount out; - DebtDirection srcDebtDir; - - Cache( - CFTAmount const& in_, - CFTAmount const& srcToDst_, - CFTAmount const& out_, - DebtDirection srcDebtDir_) - : in(in_), srcToDst(srcToDst_), out(out_), srcDebtDir(srcDebtDir_) - { - } - }; - - std::optional 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 - std::pair - maxPaymentFlow(ReadView const& sb) const; - - // Compute srcQOut and dstQIn when the source redeems. - std::pair - qualitiesSrcRedeems(ReadView const& sb) const; - - // Compute srcQOut and dstQIn when the source issues. - std::pair - qualitiesSrcIssues(ReadView const& sb, DebtDirection prevStepDebtDirection) - const; - - // Returns srcQOut, dstQIn - std::pair - qualities( - ReadView const& sb, - DebtDirection srcDebtDir, - StrandDirection strandDir) const; - -public: - DirectStepCFT( - StrandContext const& ctx, - AccountID const& src, - AccountID const& dst, - uint256 const& asset) - : src_(src) - , dst_(dst) - , issuer_(ctx.strandDeliver.account) - , cftID_(asset) - , prevStep_(ctx.prevStep) - , isLast_(ctx.isLast) - , j_(ctx.j) - { - } - - AccountID const& - src() const - { - return src_; - } - AccountID const& - dst() const - { - return dst_; - } - uint256 const& - cftID() const - { - return cftID_; - } - - std::optional - cachedIn() const override - { - if (!cache_) - return std::nullopt; - return EitherAmount(cache_->in); - } - - std::optional - cachedOut() const override - { - if (!cache_) - return std::nullopt; - return EitherAmount(cache_->out); - } - - std::optional - directStepSrcAcct() const override - { - return src_; - } - - std::optional> - directStepAccts() const override - { - return std::make_pair(src_, dst_); - } - - DebtDirection - debtDirection(ReadView const& sb, StrandDirection dir) const override; - - std::uint32_t - lineQualityIn(ReadView const& v) const override; - - std::pair, DebtDirection> - qualityUpperBound(ReadView const& v, DebtDirection dir) const override; - - std::pair - revImp( - PaymentSandbox& sb, - ApplyView& afView, - boost::container::flat_set& ofrsToRm, - CFTAmount const& out); - - std::pair - fwdImp( - PaymentSandbox& sb, - ApplyView& afView, - boost::container::flat_set& ofrsToRm, - CFTAmount const& in); - - std::pair - validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in) - override; - - // Check for error, existing liquidity, and violations of auth/frozen - // constraints. - TER - check(StrandContext const& ctx) const; - - void - setCacheLimiting( - CFTAmount const& fwdIn, - CFTAmount const& fwdSrcToDst, - CFTAmount const& fwdOut, - DebtDirection srcDebtDir); - - friend bool - operator==(DirectStepCFT const& lhs, DirectStepCFT const& rhs) - { - return lhs.src_ == rhs.src_ && lhs.dst_ == rhs.dst_ && - lhs.cftID_ == rhs.cftID_; - } - - friend bool - operator!=(DirectStepCFT const& lhs, DirectStepCFT 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: - bool - equal(Step const& rhs) const override - { - if (auto ds = dynamic_cast(&rhs)) - { - return *this == *ds; - } - return false; - } -}; - -//------------------------------------------------------------------------------ - -// 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 DirectCFTPaymentStep : public DirectStepCFT -{ -public: - using DirectStepCFT::DirectStepCFT; - using DirectStepCFT::check; - - bool verifyPrevStepDebtDirection(DebtDirection) const - { - // A payment doesn't care whether or not prevStepRedeems. - return true; - } - - bool - verifyDstQualityIn(std::uint32_t dstQIn) const - { - // Payments have no particular expectations for what dstQIn will be. - return true; - } - - 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 - std::pair - maxFlow(ReadView const& sb, CFTAmount const& desired) const; - - // Verify the consistency of the step. These checks are specific to - // payments and assume that general checks were already performed. - TER - check(StrandContext const& ctx, std::shared_ptr const& sleSrc) - const; - - std::string - logString() const override - { - return logStringImpl("DirectCFTPaymentStep"); - } -}; - -// Offer crossing DirectStep class (not a payment). -class DirectCFTOfferCrossingStep - : public DirectStepCFT -{ -public: - using DirectStepCFT::DirectStepCFT; - using DirectStepCFT::check; - - bool - verifyPrevStepDebtDirection(DebtDirection prevStepDir) const - { - // 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() aways returns `issues` when offer - // crossing. - // An assert based on this return value will tell us if that - // behavior changes. - return issues(prevStepDir); - } - - bool - verifyDstQualityIn(std::uint32_t dstQIn) const - { - // 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; - } - - 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 - std::pair - maxFlow(ReadView const& sb, CFTAmount const& desired) const; - - // Verify the consistency of the step. These checks are specific to - // offer crossing and assume that general checks were already performed. - TER - check(StrandContext const& ctx, std::shared_ptr const& sleSrc) - const; - - std::string - logString() const override - { - return logStringImpl("DirectCFTOfferCrossingStep"); - } -}; - -//------------------------------------------------------------------------------ - -std::uint32_t -DirectCFTPaymentStep::quality(ReadView const& sb, QualityDirection qDir) const -{ - return QUALITY_ONE; -} - -std::uint32_t -DirectCFTOfferCrossingStep::quality(ReadView const&, QualityDirection qDir) - const -{ - // If offer crossing then ignore trust line Quality fields. This - // preserves a long-standing tradition. - return QUALITY_ONE; -} - -std::pair -DirectCFTPaymentStep::maxFlow(ReadView const& sb, CFTAmount const&) const -{ - return maxPaymentFlow(sb); -} - -std::pair -DirectCFTOfferCrossingStep::maxFlow( - ReadView const& sb, - CFTAmount 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 -DirectCFTPaymentStep::check( - StrandContext const& ctx, - std::shared_ptr const& sleSrc) const -{ - // Since this is a payment a CFToken must be present. Perform all - // CFToken related checks. - // TODO - if (!ctx.view.exists(keylet::cftIssuance(cftID_))) - return tecOBJECT_NOT_FOUND; - if (src_ != issuer_) - { - auto const cftokenID = keylet::cftoken(cftID_, src_); - if (!ctx.view.exists(cftokenID)) - return tecOBJECT_NOT_FOUND; - } - if (dst_ != issuer_) - { - auto const cftokenID = keylet::cftoken(cftID_, dst_); - if (!ctx.view.exists(cftokenID)) - return tecOBJECT_NOT_FOUND; - } -#if 0 - // Since this is a payment a trust line must be present. Perform all - // trust line related checks. - { - auto const sleLine = ctx.view.read(keylet::line(src_, dst_, asset_)); - if (!sleLine) - { - JLOG(j_.trace()) << "DirectStepCFT: No credit line. " << *this; - return terNO_LINE; - } - - auto const authField = (src_ > dst_) ? lsfHighAuth : lsfLowAuth; - - if (((*sleSrc)[sfFlags] & lsfRequireAuth) && - !((*sleLine)[sfFlags] & authField) && - (*sleLine)[sfBalance] == beast::zero) - { - JLOG(j_.warn()) - << "DirectStepCFT: can't receive IOUs from issuer without auth." - << " src: " << src_; - return terNO_AUTH; - } - - if (ctx.prevStep) - { - if (ctx.prevStep->bookStepBook()) - { - auto const noRippleSrcToDst = - ((*sleLine)[sfFlags] & - ((src_ > dst_) ? lsfHighNoRipple : lsfLowNoRipple)); - if (noRippleSrcToDst) - return terNO_RIPPLE; - } - } - } - - { - auto const owed = creditBalance(ctx.view, dst_, src_, cftID_); - if (owed <= beast::zero) - { - auto const limit = creditLimit(ctx.view, dst_, src_, cftID_); - if (-owed >= limit) - { - JLOG(j_.debug()) << "DirectStepCFT: dry: owed: " << owed - << " limit: " << limit; - return tecPATH_DRY; - } - } - } -#endif - return tesSUCCESS; -} - -TER -DirectCFTOfferCrossingStep::check( - StrandContext const&, - std::shared_ptr const&) const -{ - // 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 -std::pair -DirectStepCFT::maxPaymentFlow(ReadView const& sb) const -{ - // TODO - if (src_ != issuer_) - { - auto const srcOwed = toAmount( - accountHolds(sb, src_, cftID_, issuer_, fhIGNORE_FREEZE, j_)); - - return {srcOwed, DebtDirection::redeems}; - } - - if (auto const sle = sb.read(keylet::cftIssuance(cftID_))) - { - std::int64_t const max = - [&]() { - auto const max = sle->getFieldU64(sfMaximumAmount); - return max > 0 ? max : STAmount::cMaxNativeN; // TODO - }() - - sle->getFieldU64(sfOutstandingAmount); - return {CFTAmount{max}, DebtDirection::issues}; - } - - return {CFTAmount{0}, DebtDirection::issues}; - -#if 0 - // srcOwed is negative or zero - return { - creditLimit2(sb, dst_, src_, asset_) + srcOwed, - DebtDirection::issues}; -#endif -} - -template -DebtDirection -DirectStepCFT::debtDirection(ReadView const& sb, StrandDirection dir) - const -{ - if (dir == StrandDirection::forward && cache_) - return cache_->srcDebtDir; - - auto const srcOwed = - accountHolds(sb, src_, cftID_, dst_, fhIGNORE_FREEZE, j_); - return srcOwed.signum() > 0 ? DebtDirection::redeems - : DebtDirection::issues; -} - -template -std::pair -DirectStepCFT::revImp( - PaymentSandbox& sb, - ApplyView& /*afView*/, - boost::container::flat_set& /*ofrsToRm*/, - CFTAmount const& out) -{ - cache_.reset(); - - auto const [maxSrcToDst, srcDebtDir] = - static_cast(this)->maxFlow(sb, out); - - auto const [srcQOut, dstQIn] = - qualities(sb, srcDebtDir, StrandDirection::reverse); - assert(static_cast(this)->verifyDstQualityIn(dstQIn)); - - Issue const srcToDstIss(cftID_, redeems(srcDebtDir) ? dst_ : src_); - - JLOG(j_.trace()) << "DirectStepCFT::rev" - << " srcRedeems: " << redeems(srcDebtDir) - << " outReq: " << to_string(out) - << " maxSrcToDst: " << to_string(maxSrcToDst) - << " srcQOut: " << srcQOut << " dstQIn: " << dstQIn; - - if (maxSrcToDst.signum() <= 0) - { - JLOG(j_.trace()) << "DirectStepCFT::rev: dry"; - cache_.emplace( - CFTAmount(beast::zero), - CFTAmount(beast::zero), - CFTAmount(beast::zero), - srcDebtDir); - return {beast::zero, beast::zero}; - } - - CFTAmount const srcToDst = - mulRatio(out, QUALITY_ONE, dstQIn, /*roundUp*/ true); - - if (srcToDst <= maxSrcToDst) - { - CFTAmount const in = - mulRatio(srcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true); - cache_.emplace(in, srcToDst, srcToDst, srcDebtDir); - rippleCFTCredit(sb, src_, dst_, toSTAmount(srcToDst, srcToDstIss), j_); - JLOG(j_.trace()) << "DirectStepCFT::rev: Non-limiting" - << " srcRedeems: " << redeems(srcDebtDir) - << " in: " << to_string(in) - << " srcToDst: " << to_string(srcToDst) - << " out: " << to_string(out); - return {in, out}; - } - - // limiting node - CFTAmount const in = - mulRatio(maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true); - CFTAmount const actualOut = - mulRatio(maxSrcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false); - cache_.emplace(in, maxSrcToDst, actualOut, srcDebtDir); - - rippleCFTCredit(sb, src_, dst_, toSTAmount(maxSrcToDst, srcToDstIss), j_); - JLOG(j_.trace()) << "DirectStepCFT::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 -void -DirectStepCFT::setCacheLimiting( - CFTAmount const& fwdIn, - CFTAmount const& fwdSrcToDst, - CFTAmount const& fwdOut, - DebtDirection srcDebtDir) -{ - if (cache_->in < fwdIn) - { - CFTAmount const smallDiff(1); - auto const diff = fwdIn - cache_->in; - if (diff > smallDiff) - { - if (!cache_->in.cft() || - (double(fwdIn.cft()) / double(cache_->in.cft())) > 1.01) - { - // Detect large diffs on forward pass so they may be - // investigated - JLOG(j_.warn()) - << "DirectStepCFT::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; -}; - -template -std::pair -DirectStepCFT::fwdImp( - PaymentSandbox& sb, - ApplyView& /*afView*/, - boost::container::flat_set& /*ofrsToRm*/, - CFTAmount const& in) -{ - assert(cache_); - - auto const [maxSrcToDst, srcDebtDir] = - static_cast(this)->maxFlow(sb, cache_->srcToDst); - - auto const [srcQOut, dstQIn] = - qualities(sb, srcDebtDir, StrandDirection::forward); - - Issue const srcToDstIss(cftID_, redeems(srcDebtDir) ? dst_ : src_); - - JLOG(j_.trace()) << "DirectStepCFT::fwd" - << " srcRedeems: " << redeems(srcDebtDir) - << " inReq: " << to_string(in) - << " maxSrcToDst: " << to_string(maxSrcToDst) - << " srcQOut: " << srcQOut << " dstQIn: " << dstQIn; - - if (maxSrcToDst.signum() <= 0) - { - JLOG(j_.trace()) << "DirectStepCFT::fwd: dry"; - cache_.emplace( - CFTAmount(beast::zero), - CFTAmount(beast::zero), - CFTAmount(beast::zero), - srcDebtDir); - return {beast::zero, beast::zero}; - } - - CFTAmount const srcToDst = - mulRatio(in, QUALITY_ONE, srcQOut, /*roundUp*/ false); - - if (srcToDst <= maxSrcToDst) - { - CFTAmount const out = - mulRatio(srcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false); - setCacheLimiting(in, srcToDst, out, srcDebtDir); - rippleCFTCredit( - sb, src_, dst_, toSTAmount(cache_->srcToDst, srcToDstIss), j_); - JLOG(j_.trace()) << "DirectStepCFT::fwd: Non-limiting" - << " srcRedeems: " << redeems(srcDebtDir) - << " in: " << to_string(in) - << " srcToDst: " << to_string(srcToDst) - << " out: " << to_string(out); - } - else - { - // limiting node - CFTAmount const actualIn = - mulRatio(maxSrcToDst, srcQOut, QUALITY_ONE, /*roundUp*/ true); - CFTAmount const out = - mulRatio(maxSrcToDst, dstQIn, QUALITY_ONE, /*roundUp*/ false); - setCacheLimiting(actualIn, maxSrcToDst, out, srcDebtDir); - rippleCFTCredit( - sb, src_, dst_, toSTAmount(cache_->srcToDst, srcToDstIss), j_); - JLOG(j_.trace()) << "DirectStepCFT::rev: Limiting" - << " srcRedeems: " << redeems(srcDebtDir) - << " in: " << to_string(actualIn) - << " srcToDst: " << to_string(srcToDst) - << " out: " << to_string(out); - } - return {cache_->in, cache_->out}; -} - -template -std::pair -DirectStepCFT::validFwd( - PaymentSandbox& sb, - ApplyView& afView, - EitherAmount const& in) -{ - if (!cache_) - { - JLOG(j_.trace()) << "Expected valid cache in validFwd"; - return {false, EitherAmount(CFTAmount(beast::zero))}; - } - - auto const savCache = *cache_; - - assert(!in.native); - - auto const [maxSrcToDst, srcDebtDir] = - static_cast(this)->maxFlow(sb, cache_->srcToDst); - (void)srcDebtDir; - - try - { - boost::container::flat_set dummy; - fwdImp(sb, afView, dummy, in.cft); // changes cache - } - catch (FlowException const&) - { - return {false, EitherAmount(CFTAmount(beast::zero))}; - } - - if (maxSrcToDst < cache_->srcToDst) - { - JLOG(j_.warn()) << "DirectStepCFT: 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()) << "DirectStepCFT: 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)}; -} - -// Returns srcQOut, dstQIn -template -std::pair -DirectStepCFT::qualitiesSrcRedeems(ReadView const& sb) const -{ - if (!prevStep_) - return {QUALITY_ONE, QUALITY_ONE}; - - auto const prevStepQIn = prevStep_->lineQualityIn(sb); - auto srcQOut = - static_cast(this)->quality(sb, QualityDirection::out); - - if (prevStepQIn > srcQOut) - srcQOut = prevStepQIn; - return {srcQOut, QUALITY_ONE}; -} - -// Returns srcQOut, dstQIn -template -std::pair -DirectStepCFT::qualitiesSrcIssues( - ReadView const& sb, - DebtDirection prevStepDebtDirection) const -{ - // Charge a transfer rate when issuing and previous step redeems - - assert(static_cast(this)->verifyPrevStepDebtDirection( - prevStepDebtDirection)); - - std::uint32_t const srcQOut = redeems(prevStepDebtDirection) - ? transferRate(sb, src_).value - : QUALITY_ONE; - auto dstQIn = - static_cast(this)->quality(sb, QualityDirection::in); - - if (isLast_ && dstQIn > QUALITY_ONE) - dstQIn = QUALITY_ONE; - return {srcQOut, dstQIn}; -} - -// Returns srcQOut, dstQIn -template -std::pair -DirectStepCFT::qualities( - ReadView const& sb, - DebtDirection srcDebtDir, - StrandDirection strandDir) const -{ - if (redeems(srcDebtDir)) - { - return qualitiesSrcRedeems(sb); - } - else - { - auto const prevStepDebtDirection = [&] { - if (prevStep_) - return prevStep_->debtDirection(sb, strandDir); - return DebtDirection::issues; - }(); - return qualitiesSrcIssues(sb, prevStepDebtDirection); - } -} - -template -std::uint32_t -DirectStepCFT::lineQualityIn(ReadView const& v) const -{ - // dst quality in - return static_cast(this)->quality(v, QualityDirection::in); -} - -template -std::pair, DebtDirection> -DirectStepCFT::qualityUpperBound( - ReadView const& v, - DebtDirection prevStepDir) const -{ - auto const dir = this->debtDirection(v, StrandDirection::forward); - - if (!v.rules().enabled(fixQualityUpperBound)) - { - std::uint32_t const srcQOut = [&]() -> std::uint32_t { - if (redeems(prevStepDir) && issues(dir)) - return transferRate(v, src_).value; - return QUALITY_ONE; - }(); - auto dstQIn = static_cast(this)->quality( - v, QualityDirection::in); - - if (isLast_ && dstQIn > QUALITY_ONE) - dstQIn = QUALITY_ONE; - Issue const iss{cftID_, src_}; - return { - Quality(getRate(STAmount(iss, srcQOut), STAmount(iss, dstQIn))), - dir}; - } - - auto const [srcQOut, dstQIn] = redeems(dir) - ? qualitiesSrcRedeems(v) - : qualitiesSrcIssues(v, prevStepDir); - - Issue const iss{cftID_, 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 -TER -DirectStepCFT::check(StrandContext const& ctx) const -{ - // The following checks apply for both payments and offer crossing. - if (!src_ || !dst_) - { - JLOG(j_.debug()) << "DirectStepCFT: specified bad account."; - return temBAD_PATH; - } - - if (src_ == dst_) - { - JLOG(j_.debug()) << "DirectStepCFT: same src and dst."; - return temBAD_PATH; - } - - auto const sleSrc = ctx.view.read(keylet::account(src_)); - if (!sleSrc) - { - JLOG(j_.warn()) - << "DirectStepCFT: 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_, cftID_); - if (ter != tesSUCCESS) - return ter; - } - - { - Issue const srcIssue{cftID_, src_}; - Issue const dstIssue{cftID_, dst_}; - - if (ctx.seenBookOuts.count(srcIssue)) - { - if (!ctx.prevStep) - { - assert(0); // prev seen book without a prev step!?! - return temBAD_PATH_LOOP; - } - - // This is OK if the previous step is a book step that outputs this - // issue - if (auto book = ctx.prevStep->bookStepBook()) - { - if (book->out != srcIssue) - return temBAD_PATH_LOOP; - } - } - - if (!ctx.seenDirectIssues[0].insert(srcIssue).second || - !ctx.seenDirectIssues[1].insert(dstIssue).second) - { - JLOG(j_.debug()) - << "DirectStepCFT: loop detected: Index: " << ctx.strandSize - << ' ' << *this; - return temBAD_PATH_LOOP; - } - } - - return static_cast(this)->check(ctx, sleSrc); -} - -//------------------------------------------------------------------------------ - -std::pair> -make_DirectStepCFT( - StrandContext const& ctx, - AccountID const& src, - AccountID const& dst, - uint256 const& a) -{ - TER ter = tefINTERNAL; - std::unique_ptr r; - if (ctx.offerCrossing) - { - auto offerCrossingStep = - std::make_unique(ctx, src, dst, a); - ter = offerCrossingStep->check(ctx); - r = std::move(offerCrossingStep); - } - else // payment - { - auto paymentStep = - std::make_unique(ctx, src, dst, a); - ter = paymentStep->check(ctx); - r = std::move(paymentStep); - } - if (ter != tesSUCCESS) - return {ter, nullptr}; - - return {tesSUCCESS, std::move(r)}; -} - -} // namespace ripple diff --git a/src/ripple/app/paths/impl/PaySteps.cpp b/src/ripple/app/paths/impl/PaySteps.cpp index fa621a7f4e6..93867224117 100644 --- a/src/ripple/app/paths/impl/PaySteps.cpp +++ b/src/ripple/app/paths/impl/PaySteps.cpp @@ -62,12 +62,6 @@ checkNear(XRPAmount const& expected, XRPAmount const& actual) return expected == actual; }; -bool -checkNear(CFTAmount const& expected, CFTAmount const& actual) -{ - return expected == actual; -}; - static bool isXRPAccount(STPathElement const& pe) { @@ -86,7 +80,7 @@ toStep( auto& j = ctx.j; if (ctx.isFirst && e1->isAccount() && - (e1->getNodeType() & STPathElement::typeAsset) && + (e1->getNodeType() & STPathElement::typeCurrency) && isXRP(e1->getCurrency())) { return make_XRPEndpointStep(ctx, e1->getAccountID()); @@ -97,9 +91,6 @@ toStep( if (e1->isAccount() && e2->isAccount()) { - if (curIssue.currency.isCFT()) - return make_DirectStepCFT( - ctx, e1->getAccountID(), e2->getAccountID(), curIssue.currency); return make_DirectStepI( ctx, e1->getAccountID(), e2->getAccountID(), curIssue.currency); } @@ -114,11 +105,11 @@ toStep( } assert( - (e2->getNodeType() & STPathElement::typeAsset) || + (e2->getNodeType() & STPathElement::typeCurrency) || (e2->getNodeType() & STPathElement::typeIssuer)); - auto const outCurrency = e2->getNodeType() & STPathElement::typeAsset + auto const outCurrency = e2->getNodeType() & STPathElement::typeCurrency ? e2->getCurrency() - : curIssue.currency; + : static_cast(curIssue.currency); auto const outIssuer = e2->getNodeType() & STPathElement::typeIssuer ? e2->getIssuerID() : curIssue.account; @@ -132,43 +123,11 @@ toStep( assert(e2->isOffer()); if (isXRP(outCurrency)) - { - if (curIssue.isCFT()) - { - JLOG(j.info()) << "Found cft/xrp offer payment step"; - return {temBAD_PATH, std::unique_ptr{}}; - } return make_BookStepIX(ctx, curIssue); - } if (isXRP(curIssue.currency)) - { - if (e2->isCft()) - { - JLOG(j.info()) << "Found xrp/cft offer payment step"; - return {temBAD_PATH, std::unique_ptr{}}; - } return make_BookStepXI(ctx, {outCurrency, outIssuer}); - } - - // TODO, add CFT for CI or IC, once supported in BookStep - if (curIssue.isCFT() && !e2->isCft()) - { - JLOG(j.info()) << "Found cft/iou offer payment step"; - return {temBAD_PATH, std::unique_ptr{}}; - } - if (!curIssue.isCFT() && e2->isCft()) - { - JLOG(j.info()) << "Found iou/cft offer payment step"; - return {temBAD_PATH, std::unique_ptr{}}; - } - // TODO, add CFT for CC or II, once supported in BookStep - if (curIssue.isCFT()) - { - JLOG(j.info()) << "Found cft/cft offer payment step"; - return {temBAD_PATH, std::unique_ptr{}}; - } return make_BookStepII(ctx, curIssue, {outCurrency, outIssuer}); } @@ -205,7 +164,6 @@ toStrand( bool const hasAccount = t & STPathElement::typeAccount; bool const hasIssuer = t & STPathElement::typeIssuer; bool const hasCurrency = t & STPathElement::typeCurrency; - bool const isCFT = t & STPathElement::typeCFT; if (hasAccount && (hasIssuer || hasCurrency)) return {temBAD_PATH, Strand{}}; @@ -225,9 +183,6 @@ toStrand( if (hasAccount && (pe.getAccountID() == noAccount())) return {temBAD_PATH, Strand{}}; - - if (isCFT && (!hasIssuer || hasCurrency || hasAccount)) - return {temBAD_PATH, Strand{}}; } Issue curIssue = [&] { @@ -239,7 +194,7 @@ toStrand( }(); auto hasCurrency = [](STPathElement const pe) { - return pe.getNodeType() & STPathElement::typeAsset; + return pe.getNodeType() & STPathElement::typeCurrency; }; std::vector normPath; @@ -247,20 +202,9 @@ toStrand( // sendmax and deliver. normPath.reserve(4 + path.size()); { - // Implied step: sender of the transaction and either sendmax or deliver - // asset - auto const t = [&]() { - auto const t = - STPathElement::typeAccount | STPathElement::typeIssuer; - if (curIssue.isCFT()) - return t | STPathElement::typeCFT; - return t | STPathElement::typeCurrency; - }(); - normPath.emplace_back(t, src, curIssue.currency, curIssue.account); + normPath.emplace_back( + STPathElement::typeAll, src, curIssue.currency, curIssue.account); - // If transaction includes sendmax with the issuer, which is not - // the sender then the issuer is the second implied step, unless - // the path starts at address, which is the issuer of sendmax if (sendMaxIssue && sendMaxIssue->account != src && (path.empty() || !path[0].isAccount() || path[0].getAccountID() != sendMaxIssue->account)) @@ -357,21 +301,13 @@ toStrand( else if (cur->hasIssuer()) curIssue.account = cur->getIssuerID(); - if (cur->hasCurrency() || cur->isCft()) + if (cur->hasCurrency()) { curIssue.currency = cur->getCurrency(); if (isXRP(curIssue.currency)) curIssue.account = xrpAccount(); } - auto getImpliedStep = [&](AccountID const& src_, - AccountID const& dst_, - Asset const& currency_) { - if (currency_.isCFT()) - return make_DirectStepCFT(ctx(), src_, dst_, currency_); - return make_DirectStepI(ctx(), src_, dst_, currency_); - }; - if (cur->isAccount() && next->isAccount()) { if (!isXRP(curIssue.currency) && @@ -379,8 +315,11 @@ toStrand( curIssue.account != next->getAccountID()) { JLOG(j.trace()) << "Inserting implied account"; - auto msr = getImpliedStep( - cur->getAccountID(), curIssue.account, curIssue.currency); + auto msr = make_DirectStepI( + ctx(), + cur->getAccountID(), + curIssue.account, + curIssue.currency); if (msr.first != tesSUCCESS) return {msr.first, Strand{}}; result.push_back(std::move(msr.second)); @@ -397,8 +336,11 @@ toStrand( if (curIssue.account != cur->getAccountID()) { JLOG(j.trace()) << "Inserting implied account before offer"; - auto msr = getImpliedStep( - cur->getAccountID(), curIssue.account, curIssue.currency); + auto msr = make_DirectStepI( + ctx(), + cur->getAccountID(), + curIssue.account, + curIssue.currency); if (msr.first != tesSUCCESS) return {msr.first, Strand{}}; result.push_back(std::move(msr.second)); @@ -432,7 +374,8 @@ toStrand( else { JLOG(j.trace()) << "Inserting implied account after offer"; - auto msr = getImpliedStep( + auto msr = make_DirectStepI( + ctx(), curIssue.account, next->getAccountID(), curIssue.currency); @@ -444,7 +387,7 @@ toStrand( continue; } - if (!next->isOffer() && (next->hasCurrency() || next->isCft()) && + if (!next->isOffer() && next->hasCurrency() && next->getCurrency() != curIssue.currency) { // Should never happen diff --git a/src/ripple/app/paths/impl/StepChecks.h b/src/ripple/app/paths/impl/StepChecks.h index 7c50f96b76c..9d8664a8dc1 100644 --- a/src/ripple/app/paths/impl/StepChecks.h +++ b/src/ripple/app/paths/impl/StepChecks.h @@ -33,13 +33,10 @@ checkFreeze( ReadView const& view, AccountID const& src, AccountID const& dst, - Asset const& currency) + Currency const& currency) { assert(src != dst); - if (currency.isCFT()) // TODO - return tesSUCCESS; - // check freeze if (auto sle = view.read(keylet::account(dst))) { diff --git a/src/ripple/app/paths/impl/Steps.h b/src/ripple/app/paths/impl/Steps.h index ddcbe7d6da2..1ae2273929d 100644 --- a/src/ripple/app/paths/impl/Steps.h +++ b/src/ripple/app/paths/impl/Steps.h @@ -599,13 +599,6 @@ make_DirectStepI( AccountID const& dst, Currency const& c); -std::pair> -make_DirectStepCFT( - StrandContext const& ctx, - AccountID const& src, - AccountID const& dst, - uint256 const& a); - std::pair> make_BookStepII(StrandContext const& ctx, Issue const& in, Issue const& out); diff --git a/src/ripple/app/paths/impl/StrandFlow.h b/src/ripple/app/paths/impl/StrandFlow.h index 12c9476a90f..7817251560f 100644 --- a/src/ripple/app/paths/impl/StrandFlow.h +++ b/src/ripple/app/paths/impl/StrandFlow.h @@ -360,15 +360,6 @@ qualityUpperBound(ReadView const& v, Strand const& strand) * increases quality of AMM steps, increasing the strand's composite * quality as the result. */ -inline CFTAmount -limitOut( - ReadView const& v, - Strand const& strand, - CFTAmount const& remainingOut, - Quality const& limitQuality) -{ - return remainingOut; -} template inline TOutAmt limitOut( diff --git a/src/ripple/app/tx/impl/Payment.cpp b/src/ripple/app/tx/impl/Payment.cpp index dbf21b45c94..e1fd2777bfa 100644 --- a/src/ripple/app/tx/impl/Payment.cpp +++ b/src/ripple/app/tx/impl/Payment.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -353,7 +354,8 @@ Payment::doApply() bool const depositPreauth = view().rules().enabled(featureDepositPreauth); - bool const bRipple = paths || sendMax || !saDstAmount.native(); + bool const bRipple = + paths || sendMax || !(saDstAmount.native() || saDstAmount.isCFT()); // If the destination has lsfDepositAuth set, then only direct XRP // payments (no intermediate steps) are allowed to the destination. @@ -425,6 +427,14 @@ Payment::doApply() terResult = tecPATH_DRY; return terResult; } + else if (saDstAmount.isCFT()) + { + PaymentSandbox pv(&view()); + auto const res = + rippleCFTCredit(pv, account_, uDstAccountID, saDstAmount, j_); + pv.apply(ctx_.rawView()); + return res; + } assert(saDstAmount.native()); diff --git a/src/ripple/ledger/View.h b/src/ripple/ledger/View.h index 9b9629d3252..2184c5fa998 100644 --- a/src/ripple/ledger/View.h +++ b/src/ripple/ledger/View.h @@ -493,7 +493,7 @@ deleteAMMTrustLine( std::optional const& ammAccountID, beast::Journal j); -TER +[[nodiscard]] TER rippleCFTCredit( ApplyView& view, AccountID const& uSenderID, diff --git a/src/ripple/protocol/STPathSet.h b/src/ripple/protocol/STPathSet.h index 3e95c1cb0b2..8102bc76eb0 100644 --- a/src/ripple/protocol/STPathSet.h +++ b/src/ripple/protocol/STPathSet.h @@ -22,7 +22,6 @@ #include #include -#include #include #include #include @@ -36,7 +35,7 @@ class STPathElement final : public CountedObject { unsigned int mType; AccountID mAccountID; - Asset mAssetID; + Currency mCurrencyID; AccountID mIssuerID; bool is_offer_; @@ -49,10 +48,8 @@ class STPathElement final : public CountedObject 0x01, // Rippling through an account (vs taking an offer). typeCurrency = 0x10, // Currency follows. typeIssuer = 0x20, // Issuer follows. - typeCFT = 0x40, // CFT typeBoundary = 0xFF, // Boundary between alternate paths. - typeAsset = typeCurrency | typeCFT, - typeAll = typeAccount | typeCurrency | typeIssuer | typeCFT, + typeAll = typeAccount | typeCurrency | typeIssuer, // Combination of all types. }; @@ -63,19 +60,19 @@ class STPathElement final : public CountedObject STPathElement( std::optional const& account, - std::optional const& asset, + std::optional const& currency, std::optional const& issuer); STPathElement( AccountID const& account, - Asset const& asset, + Currency const& currency, AccountID const& issuer, bool forceCurrency = false); STPathElement( unsigned int uType, AccountID const& account, - Asset const& asset, + Currency const& currency, AccountID const& issuer); auto @@ -84,9 +81,6 @@ class STPathElement final : public CountedObject bool isOffer() const; - bool - isCft() const; - bool isAccount() const; @@ -104,7 +98,7 @@ class STPathElement final : public CountedObject AccountID const& getAccountID() const; - Asset const& + Currency const& getCurrency() const; AccountID const& @@ -146,7 +140,7 @@ class STPath final : public CountedObject bool hasSeen( AccountID const& account, - Asset const& currency, + Currency const& currency, AccountID const& issuer) const; Json::Value getJson(JsonOptions) const; @@ -250,7 +244,7 @@ inline STPathElement::STPathElement() : mType(typeNone), is_offer_(true) inline STPathElement::STPathElement( std::optional const& account, - std::optional const& asset, + std::optional const& currency, std::optional const& issuer) : mType(typeNone) { @@ -266,10 +260,10 @@ inline STPathElement::STPathElement( assert(mAccountID != noAccount()); } - if (asset) + if (currency) { - mAssetID = *asset; - mType |= asset->isCurrency() ? typeCurrency : typeCFT; + mCurrencyID = *currency; + mType |= typeCurrency; } if (issuer) @@ -284,38 +278,35 @@ inline STPathElement::STPathElement( inline STPathElement::STPathElement( AccountID const& account, - Asset const& asset, + Currency const& currency, AccountID const& issuer, bool forceCurrency) : mType(typeNone) , mAccountID(account) - , mAssetID(asset) + , mCurrencyID(currency) , mIssuerID(issuer) , is_offer_(isXRP(mAccountID)) { if (!is_offer_) mType |= typeAccount; - if ((asset.empty() || !asset.isCFT()) && (forceCurrency || !isXRP(asset))) + if (forceCurrency || !isXRP(currency)) mType |= typeCurrency; if (!isXRP(issuer)) mType |= typeIssuer; - if (!asset.empty() && asset.isCFT()) - mType |= typeCFT; - hash_value_ = get_hash(*this); } inline STPathElement::STPathElement( unsigned int uType, AccountID const& account, - Asset const& asset, + Currency const& currency, AccountID const& issuer) : mType(uType) , mAccountID(account) - , mAssetID(asset) + , mCurrencyID(currency) , mIssuerID(issuer) , is_offer_(isXRP(mAccountID)) { @@ -334,12 +325,6 @@ STPathElement::isOffer() const return is_offer_; } -inline bool -STPathElement::isCft() const -{ - return mType & typeCFT; -} - inline bool STPathElement::isAccount() const { @@ -372,10 +357,10 @@ STPathElement::getAccountID() const return mAccountID; } -inline Asset const& +inline Currency const& STPathElement::getCurrency() const { - return mAssetID; + return mCurrencyID; } inline AccountID const& @@ -389,7 +374,7 @@ STPathElement::operator==(const STPathElement& t) const { return (mType & typeAccount) == (t.mType & typeAccount) && hash_value_ == t.hash_value_ && mAccountID == t.mAccountID && - mAssetID == t.mAssetID && mIssuerID == t.mIssuerID; + mCurrencyID == t.mCurrencyID && mIssuerID == t.mIssuerID; } inline bool diff --git a/src/ripple/protocol/impl/STPathSet.cpp b/src/ripple/protocol/impl/STPathSet.cpp index 98e4d10d114..b92d77191f8 100644 --- a/src/ripple/protocol/impl/STPathSet.cpp +++ b/src/ripple/protocol/impl/STPathSet.cpp @@ -40,16 +40,8 @@ STPathElement::get_hash(STPathElement const& element) for (auto const x : element.getAccountID()) hash_account += (hash_account * 257) ^ x; - if (element.isCft()) - { - hash_currency += - beast::uhash<>{}(static_cast(element.getCurrency())); - } - else - { - for (auto const x : static_cast(element.getCurrency())) - hash_currency += (hash_currency * 509) ^ x; - } + for (auto const x : element.getCurrency()) + hash_currency += (hash_currency * 509) ^ x; for (auto const x : element.getIssuerID()) hash_issuer += (hash_issuer * 911) ^ x; @@ -90,21 +82,16 @@ STPathSet::STPathSet(SerialIter& sit, SField const& name) : STBase(name) auto hasAccount = iType & STPathElement::typeAccount; auto hasCurrency = iType & STPathElement::typeCurrency; auto hasIssuer = iType & STPathElement::typeIssuer; - auto hasCFT = iType & STPathElement::typeCFT; AccountID account; - Asset currency{Currency{beast::zero}}; + Currency currency; AccountID issuer; if (hasAccount) account = sit.get160(); - assert(!(hasCurrency && hasCFT)); if (hasCurrency) - currency = static_cast(sit.get160()); - - if (hasCFT) - currency = sit.get256(); + currency = sit.get160(); if (hasIssuer) issuer = sit.get160(); @@ -163,12 +150,12 @@ STPathSet::isDefault() const bool STPath::hasSeen( AccountID const& account, - Asset const& asset, + Currency const& currency, AccountID const& issuer) const { for (auto& p : mPath) { - if (p.getAccountID() == account && p.getCurrency() == asset && + if (p.getAccountID() == account && p.getCurrency() == currency && p.getIssuerID() == issuer) return true; } @@ -176,7 +163,8 @@ STPath::hasSeen( return false; } -Json::Value STPath::getJson(JsonOptions) const +Json::Value +STPath::getJson(JsonOptions) const { Json::Value ret(Json::arrayValue); @@ -190,15 +178,9 @@ Json::Value STPath::getJson(JsonOptions) const if (iType & STPathElement::typeAccount) elem[jss::account] = to_string(it.getAccountID()); - assert( - !(iType & STPathElement::typeCurrency && - iType & STPathElement::typeCFT)); if (iType & STPathElement::typeCurrency) elem[jss::currency] = to_string(it.getCurrency()); - if (iType & STPathElement::typeCFT) - elem[jss::cft_asset] = to_string(it.getCurrency()); - if (iType & STPathElement::typeIssuer) elem[jss::issuer] = to_string(it.getIssuerID()); @@ -245,12 +227,8 @@ STPathSet::add(Serializer& s) const if (iType & STPathElement::typeAccount) s.addBitString(speElement.getAccountID()); - if (iType & STPathElement::typeAsset) - { - std::visit( - [&](auto&& arg) { s.addBitString(arg); }, - speElement.getCurrency().asset()); - } + if (iType & STPathElement::typeCurrency) + s.addBitString(speElement.getCurrency()); if (iType & STPathElement::typeIssuer) s.addBitString(speElement.getIssuerID());