#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace xrpl { template class XRPEndpointStep : public StepImp> { private: AccountID acc_; bool const isLast_; beast::Journal const j_; // Since this step will always be an endpoint in a strand // (either the first or last step) the same cache is used // for cachedIn and cachedOut and only one will ever be used std::optional cache_; [[nodiscard]] std::optional cached() const { if (!cache_) return std::nullopt; return EitherAmount(*cache_); } XRPEndpointStep(StrandContext const& ctx, AccountID const& acc) : acc_(acc), isLast_(ctx.isLast), j_(ctx.j) { } public: [[nodiscard]] AccountID const& acc() const { return acc_; } [[nodiscard]] std::optional> directStepAccts() const override { if (isLast_) return std::make_pair(xrpAccount(), acc_); return std::make_pair(acc_, xrpAccount()); } [[nodiscard]] std::optional cachedIn() const override { return cached(); } [[nodiscard]] std::optional cachedOut() const override { return cached(); } [[nodiscard]] DebtDirection debtDirection(ReadView const& sb, StrandDirection dir) const override { return DebtDirection::Issues; } [[nodiscard]] std::pair, DebtDirection> qualityUpperBound(ReadView const& v, DebtDirection prevStepDir) const override; std::pair revImp( PaymentSandbox& sb, ApplyView& afView, boost::container::flat_set& ofrsToRm, XRPAmount const& out); std::pair fwdImp( PaymentSandbox& sb, ApplyView& afView, boost::container::flat_set& ofrsToRm, XRPAmount const& in); std::pair validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in) override; // Check for errors and violations of frozen constraints. [[nodiscard]] TER check(StrandContext const& ctx) const; protected: XRPAmount xrpLiquidImpl(ReadView& sb, std::int32_t reserveReduction) const { return xrpl::xrpLiquid(sb, acc_, reserveReduction, j_); } std::string logStringImpl(char const* name) const { std::ostringstream ostr; ostr << name << ": " << "\nAcc: " << acc_; return ostr.str(); } private: template friend bool operator==(XRPEndpointStep

const& lhs, XRPEndpointStep

const& rhs); friend bool operator!=(XRPEndpointStep const& lhs, XRPEndpointStep const& rhs) { return !(lhs == rhs); } [[nodiscard]] bool equal(Step const& rhs) const override { if (auto ds = dynamic_cast(&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 XRPEndpointStep class (not offer crossing). class XRPEndpointPaymentStep : public XRPEndpointStep { public: XRPEndpointPaymentStep(StrandContext const& ctx, AccountID const& acc) : XRPEndpointStep(ctx, acc) { } XRPAmount xrpLiquid(ReadView& sb) const { return xrpLiquidImpl(sb, 0); ; } [[nodiscard]] std::string logString() const override { return logStringImpl("XRPEndpointPaymentStep"); } }; // Offer crossing XRPEndpointStep class (not a payment). class XRPEndpointOfferCrossingStep : public XRPEndpointStep { private: // For historical reasons, offer crossing is allowed to dig further // into the XRP reserve than an ordinary payment. (I believe it's // because the trust line was created after the XRP was removed.) // Return how much the reserve should be reduced. // // Note that reduced reserve only happens if the trust line or MPT does not // currently exist. static std::int32_t computeReserveReduction(StrandContext const& ctx, AccountID const& acc) { if (ctx.isFirst) { return ctx.strandDeliver.visit( [&](Issue const& issue) { if (!ctx.view.exists(keylet::line(acc, issue))) return -1; return 0; }, [&](MPTIssue const& issue) { if (!ctx.view.exists(keylet::mptoken(issue.getMptID(), acc))) return -1; return 0; }); } return 0; } public: XRPEndpointOfferCrossingStep(StrandContext const& ctx, AccountID const& acc) : XRPEndpointStep(ctx, acc) , reserveReduction_(computeReserveReduction(ctx, acc)) { } XRPAmount xrpLiquid(ReadView& sb) const { return xrpLiquidImpl(sb, reserveReduction_); } [[nodiscard]] std::string logString() const override { return logStringImpl("XRPEndpointOfferCrossingStep"); } private: std::int32_t const reserveReduction_; }; //------------------------------------------------------------------------------ template inline bool operator==(XRPEndpointStep const& lhs, XRPEndpointStep const& rhs) { return lhs.acc_ == rhs.acc_ && lhs.isLast_ == rhs.isLast_; } template std::pair, DebtDirection> XRPEndpointStep::qualityUpperBound(ReadView const& v, DebtDirection prevStepDir) const { return {Quality{STAmount::kURateOne}, this->debtDirection(v, StrandDirection::Forward)}; } template std::pair XRPEndpointStep::revImp( PaymentSandbox& sb, ApplyView& afView, boost::container::flat_set& ofrsToRm, XRPAmount const& out) { auto const balance = static_cast(this)->xrpLiquid(sb); auto const result = isLast_ ? out : std::min(balance, out); auto& sender = isLast_ ? xrpAccount() : acc_; auto& receiver = isLast_ ? acc_ : xrpAccount(); auto ter = accountSend(sb, sender, receiver, toSTAmount(result), j_); if (!isTesSuccess(ter)) return {XRPAmount{beast::kZero}, XRPAmount{beast::kZero}}; cache_.emplace(result); return {result, result}; } template std::pair XRPEndpointStep::fwdImp( PaymentSandbox& sb, ApplyView& afView, boost::container::flat_set& ofrsToRm, XRPAmount const& in) { XRPL_ASSERT(cache_, "xrpl::XRPEndpointStep::fwdImp : cache is set"); auto const balance = static_cast(this)->xrpLiquid(sb); auto const result = isLast_ ? in : std::min(balance, in); auto& sender = isLast_ ? xrpAccount() : acc_; auto& receiver = isLast_ ? acc_ : xrpAccount(); auto ter = accountSend(sb, sender, receiver, toSTAmount(result), j_); if (!isTesSuccess(ter)) return {XRPAmount{beast::kZero}, XRPAmount{beast::kZero}}; cache_.emplace(result); return {result, result}; } template std::pair XRPEndpointStep::validFwd(PaymentSandbox& sb, ApplyView& afView, EitherAmount const& in) { if (!cache_) { JLOG(j_.error()) << "Expected valid cache in validFwd"; return {false, EitherAmount(XRPAmount(beast::kZero))}; } XRPL_ASSERT(in.holds(), "xrpl::XRPEndpointStep::validFwd : input is XRP"); auto const& xrpIn = in.get(); auto const balance = static_cast(this)->xrpLiquid(sb); if (!isLast_ && balance < xrpIn) { JLOG(j_.warn()) << "XRPEndpointStep: Strand re-execute check failed." << " Insufficient balance: " << to_string(balance) << " Requested: " << to_string(xrpIn); return {false, EitherAmount(balance)}; } if (xrpIn != *cache_) { JLOG(j_.warn()) << "XRPEndpointStep: Strand re-execute check failed." << " ExpectedIn: " << to_string(*cache_) << " CachedIn: " << to_string(xrpIn); } return {true, in}; } template TER XRPEndpointStep::check(StrandContext const& ctx) const { if (!acc_) { JLOG(j_.debug()) << "XRPEndpointStep: specified bad account."; return temBAD_PATH; } auto sleAcc = ctx.view.read(keylet::account(acc_)); if (!sleAcc) { JLOG(j_.warn()) << "XRPEndpointStep: can't send or receive XRP from " "non-existent account: " << acc_; return terNO_ACCOUNT; } if (!ctx.isFirst && !ctx.isLast) { return temBAD_PATH; } auto& src = isLast_ ? xrpAccount() : acc_; auto& dst = isLast_ ? acc_ : xrpAccount(); auto ter = checkFreeze(ctx.view, src, dst, xrpCurrency()); if (!isTesSuccess(ter)) return ter; auto const issuesIndex = isLast_ ? 0 : 1; if (!ctx.seenDirectAssets[issuesIndex].insert(xrpIssue()).second) { JLOG(j_.debug()) << "XRPEndpointStep: loop detected: Index: " << ctx.strandSize << ' ' << *this; return temBAD_PATH_LOOP; } return tesSUCCESS; } //------------------------------------------------------------------------------ namespace test { // Needed for testing bool xrpEndpointStepEqual(Step const& step, AccountID const& acc) { if (auto xs = dynamic_cast const*>(&step)) { return xs->acc() == acc; } return false; } } // namespace test //------------------------------------------------------------------------------ std::pair> makeXrpEndpointStep(StrandContext const& ctx, AccountID const& acc) { TER ter = tefINTERNAL; std::unique_ptr r; if (ctx.offerCrossing != OfferCrossing::No) { auto offerCrossingStep = std::make_unique(ctx, acc); ter = offerCrossingStep->check(ctx); r = std::move(offerCrossingStep); } else // payment { auto paymentStep = std::make_unique(ctx, acc); ter = paymentStep->check(ctx); r = std::move(paymentStep); } if (!isTesSuccess(ter)) return {ter, nullptr}; return {tesSUCCESS, std::move(r)}; } } // namespace xrpl