mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-18 18:15:50 +00:00
chore: Remove unused code after flow cross retirement (#5575)
After the `FlowCross` amendment was retired (#5562), there was still some unused code left. This change removes the remaining remnants.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -26,9 +26,9 @@
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/beast/utility/WrappedSink.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/st.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -311,374 +311,6 @@ CreateOffer::checkAcceptAsset(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
bool
|
||||
CreateOffer::dry_offer(ApplyView& view, Offer const& offer)
|
||||
{
|
||||
if (offer.fully_consumed())
|
||||
return true;
|
||||
auto const amount = accountFunds(
|
||||
view,
|
||||
offer.owner(),
|
||||
offer.amount().out,
|
||||
fhZERO_IF_FROZEN,
|
||||
ctx_.app.journal("View"));
|
||||
return (amount <= beast::zero);
|
||||
}
|
||||
|
||||
std::pair<bool, Quality>
|
||||
CreateOffer::select_path(
|
||||
bool have_direct,
|
||||
OfferStream const& direct,
|
||||
bool have_bridge,
|
||||
OfferStream const& leg1,
|
||||
OfferStream const& leg2)
|
||||
{
|
||||
// If we don't have any viable path, why are we here?!
|
||||
XRPL_ASSERT(
|
||||
have_direct || have_bridge,
|
||||
"ripple::CreateOffer::select_path : valid inputs");
|
||||
|
||||
// If there's no bridged path, the direct is the best by default.
|
||||
if (!have_bridge)
|
||||
return std::make_pair(true, direct.tip().quality());
|
||||
|
||||
Quality const bridged_quality(
|
||||
composed_quality(leg1.tip().quality(), leg2.tip().quality()));
|
||||
|
||||
if (have_direct)
|
||||
{
|
||||
// We compare the quality of the composed quality of the bridged
|
||||
// offers and compare it against the direct offer to pick the best.
|
||||
Quality const direct_quality(direct.tip().quality());
|
||||
|
||||
if (bridged_quality < direct_quality)
|
||||
return std::make_pair(true, direct_quality);
|
||||
}
|
||||
|
||||
// Either there was no direct offer, or it didn't have a better quality
|
||||
// than the bridge.
|
||||
return std::make_pair(false, bridged_quality);
|
||||
}
|
||||
|
||||
bool
|
||||
CreateOffer::reachedOfferCrossingLimit(Taker const& taker) const
|
||||
{
|
||||
auto const crossings =
|
||||
taker.get_direct_crossings() + (2 * taker.get_bridge_crossings());
|
||||
|
||||
// The crossing limit is part of the Ripple protocol and
|
||||
// changing it is a transaction-processing change.
|
||||
return crossings >= 850;
|
||||
}
|
||||
|
||||
std::pair<TER, Amounts>
|
||||
CreateOffer::bridged_cross(
|
||||
Taker& taker,
|
||||
ApplyView& view,
|
||||
ApplyView& view_cancel,
|
||||
NetClock::time_point const when)
|
||||
{
|
||||
auto const& takerAmount = taker.original_offer();
|
||||
|
||||
XRPL_ASSERT(
|
||||
!isXRP(takerAmount.in) && !isXRP(takerAmount.out),
|
||||
"ripple::CreateOffer::bridged_cross : neither is XRP");
|
||||
|
||||
if (isXRP(takerAmount.in) || isXRP(takerAmount.out))
|
||||
Throw<std::logic_error>("Bridging with XRP and an endpoint.");
|
||||
|
||||
OfferStream offers_direct(
|
||||
view,
|
||||
view_cancel,
|
||||
Book(taker.issue_in(), taker.issue_out(), std::nullopt),
|
||||
when,
|
||||
stepCounter_,
|
||||
j_);
|
||||
|
||||
OfferStream offers_leg1(
|
||||
view,
|
||||
view_cancel,
|
||||
Book(taker.issue_in(), xrpIssue(), std::nullopt),
|
||||
when,
|
||||
stepCounter_,
|
||||
j_);
|
||||
|
||||
OfferStream offers_leg2(
|
||||
view,
|
||||
view_cancel,
|
||||
Book(xrpIssue(), taker.issue_out(), std::nullopt),
|
||||
when,
|
||||
stepCounter_,
|
||||
j_);
|
||||
|
||||
TER cross_result = tesSUCCESS;
|
||||
|
||||
// Note the subtle distinction here: self-offers encountered in the
|
||||
// bridge are taken, but self-offers encountered in the direct book
|
||||
// are not.
|
||||
bool have_bridge = offers_leg1.step() && offers_leg2.step();
|
||||
bool have_direct = step_account(offers_direct, taker);
|
||||
int count = 0;
|
||||
|
||||
auto viewJ = ctx_.app.journal("View");
|
||||
|
||||
// Modifying the order or logic of the operations in the loop will cause
|
||||
// a protocol breaking change.
|
||||
while (have_direct || have_bridge)
|
||||
{
|
||||
bool leg1_consumed = false;
|
||||
bool leg2_consumed = false;
|
||||
bool direct_consumed = false;
|
||||
|
||||
auto const [use_direct, quality] = select_path(
|
||||
have_direct, offers_direct, have_bridge, offers_leg1, offers_leg2);
|
||||
|
||||
// We are always looking at the best quality; we are done with
|
||||
// crossing as soon as we cross the quality boundary.
|
||||
if (taker.reject(quality))
|
||||
break;
|
||||
|
||||
count++;
|
||||
|
||||
if (use_direct)
|
||||
{
|
||||
if (auto stream = j_.debug())
|
||||
{
|
||||
stream << count << " Direct:";
|
||||
stream << " offer: " << offers_direct.tip();
|
||||
stream << " in: " << offers_direct.tip().amount().in;
|
||||
stream << " out: " << offers_direct.tip().amount().out;
|
||||
stream << " owner: " << offers_direct.tip().owner();
|
||||
stream << " funds: "
|
||||
<< accountFunds(
|
||||
view,
|
||||
offers_direct.tip().owner(),
|
||||
offers_direct.tip().amount().out,
|
||||
fhIGNORE_FREEZE,
|
||||
viewJ);
|
||||
}
|
||||
|
||||
cross_result = taker.cross(offers_direct.tip());
|
||||
|
||||
JLOG(j_.debug()) << "Direct Result: " << transToken(cross_result);
|
||||
|
||||
if (dry_offer(view, offers_direct.tip()))
|
||||
{
|
||||
direct_consumed = true;
|
||||
have_direct = step_account(offers_direct, taker);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto stream = j_.debug())
|
||||
{
|
||||
auto const owner1_funds_before = accountFunds(
|
||||
view,
|
||||
offers_leg1.tip().owner(),
|
||||
offers_leg1.tip().amount().out,
|
||||
fhIGNORE_FREEZE,
|
||||
viewJ);
|
||||
|
||||
auto const owner2_funds_before = accountFunds(
|
||||
view,
|
||||
offers_leg2.tip().owner(),
|
||||
offers_leg2.tip().amount().out,
|
||||
fhIGNORE_FREEZE,
|
||||
viewJ);
|
||||
|
||||
stream << count << " Bridge:";
|
||||
stream << " offer1: " << offers_leg1.tip();
|
||||
stream << " in: " << offers_leg1.tip().amount().in;
|
||||
stream << " out: " << offers_leg1.tip().amount().out;
|
||||
stream << " owner: " << offers_leg1.tip().owner();
|
||||
stream << " funds: " << owner1_funds_before;
|
||||
stream << " offer2: " << offers_leg2.tip();
|
||||
stream << " in: " << offers_leg2.tip().amount().in;
|
||||
stream << " out: " << offers_leg2.tip().amount().out;
|
||||
stream << " owner: " << offers_leg2.tip().owner();
|
||||
stream << " funds: " << owner2_funds_before;
|
||||
}
|
||||
|
||||
cross_result = taker.cross(offers_leg1.tip(), offers_leg2.tip());
|
||||
|
||||
JLOG(j_.debug()) << "Bridge Result: " << transToken(cross_result);
|
||||
|
||||
if (view.rules().enabled(fixTakerDryOfferRemoval))
|
||||
{
|
||||
// have_bridge can be true the next time 'round only if
|
||||
// neither of the OfferStreams are dry.
|
||||
leg1_consumed = dry_offer(view, offers_leg1.tip());
|
||||
if (leg1_consumed)
|
||||
have_bridge &= offers_leg1.step();
|
||||
|
||||
leg2_consumed = dry_offer(view, offers_leg2.tip());
|
||||
if (leg2_consumed)
|
||||
have_bridge &= offers_leg2.step();
|
||||
}
|
||||
else
|
||||
{
|
||||
// This old behavior may leave an empty offer in the book for
|
||||
// the second leg.
|
||||
if (dry_offer(view, offers_leg1.tip()))
|
||||
{
|
||||
leg1_consumed = true;
|
||||
have_bridge = (have_bridge && offers_leg1.step());
|
||||
}
|
||||
if (dry_offer(view, offers_leg2.tip()))
|
||||
{
|
||||
leg2_consumed = true;
|
||||
have_bridge = (have_bridge && offers_leg2.step());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cross_result != tesSUCCESS)
|
||||
{
|
||||
cross_result = tecFAILED_PROCESSING;
|
||||
break;
|
||||
}
|
||||
|
||||
if (taker.done())
|
||||
{
|
||||
JLOG(j_.debug()) << "The taker reports he's done during crossing!";
|
||||
break;
|
||||
}
|
||||
|
||||
if (reachedOfferCrossingLimit(taker))
|
||||
{
|
||||
JLOG(j_.debug()) << "The offer crossing limit has been exceeded!";
|
||||
break;
|
||||
}
|
||||
|
||||
// Postcondition: If we aren't done, then we *must* have consumed at
|
||||
// least one offer fully.
|
||||
XRPL_ASSERT(
|
||||
direct_consumed || leg1_consumed || leg2_consumed,
|
||||
"ripple::CreateOffer::bridged_cross : consumed an offer");
|
||||
|
||||
if (!direct_consumed && !leg1_consumed && !leg2_consumed)
|
||||
Throw<std::logic_error>(
|
||||
"bridged crossing: nothing was fully consumed.");
|
||||
}
|
||||
|
||||
return std::make_pair(cross_result, taker.remaining_offer());
|
||||
}
|
||||
|
||||
std::pair<TER, Amounts>
|
||||
CreateOffer::direct_cross(
|
||||
Taker& taker,
|
||||
ApplyView& view,
|
||||
ApplyView& view_cancel,
|
||||
NetClock::time_point const when)
|
||||
{
|
||||
OfferStream offers(
|
||||
view,
|
||||
view_cancel,
|
||||
Book(taker.issue_in(), taker.issue_out(), std::nullopt),
|
||||
when,
|
||||
stepCounter_,
|
||||
j_);
|
||||
|
||||
TER cross_result(tesSUCCESS);
|
||||
int count = 0;
|
||||
|
||||
bool have_offer = step_account(offers, taker);
|
||||
|
||||
// Modifying the order or logic of the operations in the loop will cause
|
||||
// a protocol breaking change.
|
||||
while (have_offer)
|
||||
{
|
||||
bool direct_consumed = false;
|
||||
auto& offer(offers.tip());
|
||||
|
||||
// We are done with crossing as soon as we cross the quality boundary
|
||||
if (taker.reject(offer.quality()))
|
||||
break;
|
||||
|
||||
count++;
|
||||
|
||||
if (auto stream = j_.debug())
|
||||
{
|
||||
stream << count << " Direct:";
|
||||
stream << " offer: " << offer;
|
||||
stream << " in: " << offer.amount().in;
|
||||
stream << " out: " << offer.amount().out;
|
||||
stream << "quality: " << offer.quality();
|
||||
stream << " owner: " << offer.owner();
|
||||
stream << " funds: "
|
||||
<< accountFunds(
|
||||
view,
|
||||
offer.owner(),
|
||||
offer.amount().out,
|
||||
fhIGNORE_FREEZE,
|
||||
ctx_.app.journal("View"));
|
||||
}
|
||||
|
||||
cross_result = taker.cross(offer);
|
||||
|
||||
JLOG(j_.debug()) << "Direct Result: " << transToken(cross_result);
|
||||
|
||||
if (dry_offer(view, offer))
|
||||
{
|
||||
direct_consumed = true;
|
||||
have_offer = step_account(offers, taker);
|
||||
}
|
||||
|
||||
if (cross_result != tesSUCCESS)
|
||||
{
|
||||
cross_result = tecFAILED_PROCESSING;
|
||||
break;
|
||||
}
|
||||
|
||||
if (taker.done())
|
||||
{
|
||||
JLOG(j_.debug()) << "The taker reports he's done during crossing!";
|
||||
break;
|
||||
}
|
||||
|
||||
if (reachedOfferCrossingLimit(taker))
|
||||
{
|
||||
JLOG(j_.debug()) << "The offer crossing limit has been exceeded!";
|
||||
break;
|
||||
}
|
||||
|
||||
// Postcondition: If we aren't done, then we *must* have consumed the
|
||||
// offer on the books fully!
|
||||
XRPL_ASSERT(
|
||||
direct_consumed,
|
||||
"ripple::CreateOffer::direct_cross : consumed an offer");
|
||||
|
||||
if (!direct_consumed)
|
||||
Throw<std::logic_error>(
|
||||
"direct crossing: nothing was fully consumed.");
|
||||
}
|
||||
|
||||
return std::make_pair(cross_result, taker.remaining_offer());
|
||||
}
|
||||
|
||||
// Step through the stream for as long as possible, skipping any offers
|
||||
// that are from the taker or which cross the taker's threshold.
|
||||
// Return false if the is no offer in the book, true otherwise.
|
||||
bool
|
||||
CreateOffer::step_account(OfferStream& stream, Taker const& taker)
|
||||
{
|
||||
while (stream.step())
|
||||
{
|
||||
auto const& offer = stream.tip();
|
||||
|
||||
// This offer at the tip crosses the taker's threshold. We're done.
|
||||
if (taker.reject(offer.quality()))
|
||||
return true;
|
||||
|
||||
// This offer at the tip is not from the taker. We're done.
|
||||
if (offer.owner() != taker.account())
|
||||
return true;
|
||||
}
|
||||
|
||||
// We ran out of offers. Can't advance.
|
||||
return false;
|
||||
}
|
||||
|
||||
std::pair<TER, Amounts>
|
||||
CreateOffer::flowCross(
|
||||
PaymentSandbox& psb,
|
||||
@@ -883,21 +515,6 @@ CreateOffer::flowCross(
|
||||
return {tecINTERNAL, takerAmount};
|
||||
}
|
||||
|
||||
std::pair<TER, Amounts>
|
||||
CreateOffer::cross(
|
||||
Sandbox& sb,
|
||||
Sandbox& sbCancel,
|
||||
Amounts const& takerAmount,
|
||||
std::optional<uint256> const& domainID)
|
||||
{
|
||||
PaymentSandbox psbFlow{&sb};
|
||||
PaymentSandbox psbCancelFlow{&sbCancel};
|
||||
auto const ret = flowCross(psbFlow, psbCancelFlow, takerAmount, domainID);
|
||||
psbFlow.apply(sb);
|
||||
psbCancelFlow.apply(sbCancel);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string
|
||||
CreateOffer::format_amount(STAmount const& amount)
|
||||
{
|
||||
@@ -907,20 +524,6 @@ CreateOffer::format_amount(STAmount const& amount)
|
||||
return txt;
|
||||
}
|
||||
|
||||
void
|
||||
CreateOffer::preCompute()
|
||||
{
|
||||
cross_type_ = CrossType::IouToIou;
|
||||
bool const pays_xrp = ctx_.tx.getFieldAmount(sfTakerPays).native();
|
||||
bool const gets_xrp = ctx_.tx.getFieldAmount(sfTakerGets).native();
|
||||
if (pays_xrp && !gets_xrp)
|
||||
cross_type_ = CrossType::IouToXrp;
|
||||
else if (gets_xrp && !pays_xrp)
|
||||
cross_type_ = CrossType::XrpToIou;
|
||||
|
||||
return Transactor::preCompute();
|
||||
}
|
||||
|
||||
TER
|
||||
CreateOffer::applyHybrid(
|
||||
Sandbox& sb,
|
||||
@@ -1084,11 +687,6 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
// We reverse pays and gets because during crossing we are taking.
|
||||
Amounts const takerAmount(saTakerGets, saTakerPays);
|
||||
|
||||
// The amount of the offer that is unfilled after crossing has been
|
||||
// performed. It may be equal to the original amount (didn't cross),
|
||||
// empty (fully crossed), or something in-between.
|
||||
Amounts place_offer;
|
||||
|
||||
JLOG(j_.debug()) << "Attempting cross: "
|
||||
<< to_string(takerAmount.in.issue()) << " -> "
|
||||
<< to_string(takerAmount.out.issue());
|
||||
@@ -1101,8 +699,17 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel)
|
||||
stream << " out: " << format_amount(takerAmount.out);
|
||||
}
|
||||
|
||||
// The amount of the offer that is unfilled after crossing has been
|
||||
// performed. It may be equal to the original amount (didn't cross),
|
||||
// empty (fully crossed), or something in-between.
|
||||
Amounts place_offer;
|
||||
PaymentSandbox psbFlow{&sb};
|
||||
PaymentSandbox psbCancelFlow{&sbCancel};
|
||||
|
||||
std::tie(result, place_offer) =
|
||||
cross(sb, sbCancel, takerAmount, domainID);
|
||||
flowCross(psbFlow, psbCancelFlow, takerAmount, domainID);
|
||||
psbFlow.apply(sb);
|
||||
psbCancelFlow.apply(sbCancel);
|
||||
|
||||
// We expect the implementation of cross to succeed
|
||||
// or give a tec.
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
#ifndef RIPPLE_TX_CREATEOFFER_H_INCLUDED
|
||||
#define RIPPLE_TX_CREATEOFFER_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/tx/detail/OfferStream.h>
|
||||
#include <xrpld/app/tx/detail/Taker.h>
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class PaymentSandbox;
|
||||
@@ -36,8 +36,7 @@ public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Custom};
|
||||
|
||||
/** Construct a Transactor subclass that creates an offer in the ledger. */
|
||||
explicit CreateOffer(ApplyContext& ctx)
|
||||
: Transactor(ctx), stepCounter_(1000, j_)
|
||||
explicit CreateOffer(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -52,10 +51,6 @@ public:
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
/** Gather information beyond what the Transactor base class gathers. */
|
||||
void
|
||||
preCompute() override;
|
||||
|
||||
/** Precondition: fee collection is likely. Attempt to create the offer. */
|
||||
TER
|
||||
doApply() override;
|
||||
@@ -73,42 +68,6 @@ private:
|
||||
beast::Journal const j,
|
||||
Issue const& issue);
|
||||
|
||||
bool
|
||||
dry_offer(ApplyView& view, Offer const& offer);
|
||||
|
||||
static std::pair<bool, Quality>
|
||||
select_path(
|
||||
bool have_direct,
|
||||
OfferStream const& direct,
|
||||
bool have_bridge,
|
||||
OfferStream const& leg1,
|
||||
OfferStream const& leg2);
|
||||
|
||||
std::pair<TER, Amounts>
|
||||
bridged_cross(
|
||||
Taker& taker,
|
||||
ApplyView& view,
|
||||
ApplyView& view_cancel,
|
||||
NetClock::time_point const when);
|
||||
|
||||
std::pair<TER, Amounts>
|
||||
direct_cross(
|
||||
Taker& taker,
|
||||
ApplyView& view,
|
||||
ApplyView& view_cancel,
|
||||
NetClock::time_point const when);
|
||||
|
||||
// Step through the stream for as long as possible, skipping any offers
|
||||
// that are from the taker or which cross the taker's threshold.
|
||||
// Return false if the is no offer in the book, true otherwise.
|
||||
static bool
|
||||
step_account(OfferStream& stream, Taker const& taker);
|
||||
|
||||
// True if the number of offers that have been crossed
|
||||
// exceeds the limit.
|
||||
bool
|
||||
reachedOfferCrossingLimit(Taker const& taker) const;
|
||||
|
||||
// Use the payment flow code to perform offer crossing.
|
||||
std::pair<TER, Amounts>
|
||||
flowCross(
|
||||
@@ -117,17 +76,6 @@ private:
|
||||
Amounts const& takerAmount,
|
||||
std::optional<uint256> const& domainID);
|
||||
|
||||
// Temporary
|
||||
// This is a central location that invokes both versions of cross
|
||||
// so the results can be compared. Eventually this layer will be
|
||||
// removed once flowCross is determined to be stable.
|
||||
std::pair<TER, Amounts>
|
||||
cross(
|
||||
Sandbox& sb,
|
||||
Sandbox& sbCancel,
|
||||
Amounts const& takerAmount,
|
||||
std::optional<uint256> const& domainID);
|
||||
|
||||
static std::string
|
||||
format_amount(STAmount const& amount);
|
||||
|
||||
@@ -139,13 +87,6 @@ private:
|
||||
STAmount const& saTakerPays,
|
||||
STAmount const& saTakerGets,
|
||||
std::function<void(SLE::ref, std::optional<uint256>)> const& setDir);
|
||||
|
||||
private:
|
||||
// What kind of offer we are placing
|
||||
CrossType cross_type_;
|
||||
|
||||
// The number of steps to take through order books while crossing
|
||||
OfferStream::StepCounter stepCounter_;
|
||||
};
|
||||
|
||||
using OfferCreate = CreateOffer;
|
||||
|
||||
@@ -1,863 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2014 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 <xrpld/app/tx/detail/Taker.h>
|
||||
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/contract.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
static std::string
|
||||
format_amount(STAmount const& amount)
|
||||
{
|
||||
std::string txt = amount.getText();
|
||||
txt += "/";
|
||||
txt += to_string(amount.issue().currency);
|
||||
return txt;
|
||||
}
|
||||
|
||||
BasicTaker::BasicTaker(
|
||||
CrossType cross_type,
|
||||
AccountID const& account,
|
||||
Amounts const& amount,
|
||||
Quality const& quality,
|
||||
std::uint32_t flags,
|
||||
Rate const& rate_in,
|
||||
Rate const& rate_out,
|
||||
beast::Journal journal)
|
||||
: account_(account)
|
||||
, quality_(quality)
|
||||
, threshold_(quality_)
|
||||
, sell_(flags & tfSell)
|
||||
, original_(amount)
|
||||
, remaining_(amount)
|
||||
, issue_in_(remaining_.in.issue())
|
||||
, issue_out_(remaining_.out.issue())
|
||||
, m_rate_in(rate_in)
|
||||
, m_rate_out(rate_out)
|
||||
, cross_type_(cross_type)
|
||||
, journal_(journal)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
remaining_.in > beast::zero,
|
||||
"ripple::BasicTaker::BasicTaker : positive remaining in");
|
||||
XRPL_ASSERT(
|
||||
remaining_.out > beast::zero,
|
||||
"ripple::BasicTaker::BasicTaker : positive remaining out");
|
||||
|
||||
XRPL_ASSERT(
|
||||
m_rate_in.value, "ripple::BasicTaker::BasicTaker : nonzero rate in");
|
||||
XRPL_ASSERT(
|
||||
m_rate_out.value, "ripple::BasicTaker::BasicTaker : nonzero rate out");
|
||||
|
||||
// If we are dealing with a particular flavor, make sure that it's the
|
||||
// flavor we expect:
|
||||
XRPL_ASSERT(
|
||||
cross_type != CrossType::XrpToIou ||
|
||||
(isXRP(issue_in()) && !isXRP(issue_out())),
|
||||
"ripple::BasicTaker::BasicTaker : valid cross to IOU");
|
||||
|
||||
XRPL_ASSERT(
|
||||
cross_type != CrossType::IouToXrp ||
|
||||
(!isXRP(issue_in()) && isXRP(issue_out())),
|
||||
"ripple::BasicTaker::BasicTaker : valid cross to XRP");
|
||||
|
||||
// And make sure we're not crossing XRP for XRP
|
||||
XRPL_ASSERT(
|
||||
!isXRP(issue_in()) || !isXRP(issue_out()),
|
||||
"ripple::BasicTaker::BasicTaker : not crossing XRP for XRP");
|
||||
|
||||
// If this is a passive order, we adjust the quality so as to prevent offers
|
||||
// at the same quality level from being consumed.
|
||||
if (flags & tfPassive)
|
||||
++threshold_;
|
||||
}
|
||||
|
||||
Rate
|
||||
BasicTaker::effective_rate(
|
||||
Rate const& rate,
|
||||
Issue const& issue,
|
||||
AccountID const& from,
|
||||
AccountID const& to)
|
||||
{
|
||||
// If there's a transfer rate, the issuer is not involved
|
||||
// and the sender isn't the same as the recipient, return
|
||||
// the actual transfer rate.
|
||||
if (rate != parityRate && from != to && from != issue.account &&
|
||||
to != issue.account)
|
||||
{
|
||||
return rate;
|
||||
}
|
||||
|
||||
return parityRate;
|
||||
}
|
||||
|
||||
bool
|
||||
BasicTaker::unfunded() const
|
||||
{
|
||||
if (get_funds(account(), remaining_.in) > beast::zero)
|
||||
return false;
|
||||
|
||||
JLOG(journal_.debug()) << "Unfunded: taker is out of funds.";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BasicTaker::done() const
|
||||
{
|
||||
// We are done if we have consumed all the input currency
|
||||
if (remaining_.in <= beast::zero)
|
||||
{
|
||||
JLOG(journal_.debug())
|
||||
<< "Done: all the input currency has been consumed.";
|
||||
return true;
|
||||
}
|
||||
|
||||
// We are done if using buy semantics and we received the
|
||||
// desired amount of output currency
|
||||
if (!sell_ && (remaining_.out <= beast::zero))
|
||||
{
|
||||
JLOG(journal_.debug()) << "Done: the desired amount has been received.";
|
||||
return true;
|
||||
}
|
||||
|
||||
// We are done if the taker is out of funds
|
||||
if (unfunded())
|
||||
{
|
||||
JLOG(journal_.debug()) << "Done: taker out of funds.";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Amounts
|
||||
BasicTaker::remaining_offer() const
|
||||
{
|
||||
// If the taker is done, then there's no offer to place.
|
||||
if (done())
|
||||
return Amounts(remaining_.in.zeroed(), remaining_.out.zeroed());
|
||||
|
||||
// Avoid math altogether if we didn't cross.
|
||||
if (original_ == remaining_)
|
||||
return original_;
|
||||
|
||||
if (sell_)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
remaining_.in > beast::zero,
|
||||
"ripple::BasicTaker::remaining_offer : positive remaining in");
|
||||
|
||||
// We scale the output based on the remaining input:
|
||||
return Amounts(
|
||||
remaining_.in,
|
||||
divRound(remaining_.in, quality_.rate(), issue_out_, true));
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
remaining_.out > beast::zero,
|
||||
"ripple::BasicTaker::remaining_offer : positive remaining out");
|
||||
|
||||
// We scale the input based on the remaining output:
|
||||
return Amounts(
|
||||
mulRound(remaining_.out, quality_.rate(), issue_in_, true),
|
||||
remaining_.out);
|
||||
}
|
||||
|
||||
Amounts const&
|
||||
BasicTaker::original_offer() const
|
||||
{
|
||||
return original_;
|
||||
}
|
||||
|
||||
// TODO: the presence of 'output' is an artifact caused by the fact that
|
||||
// Amounts carry issue information which should be decoupled.
|
||||
static STAmount
|
||||
qual_div(STAmount const& amount, Quality const& quality, STAmount const& output)
|
||||
{
|
||||
auto result = divide(amount, quality.rate(), output.issue());
|
||||
return std::min(result, output);
|
||||
}
|
||||
|
||||
static STAmount
|
||||
qual_mul(STAmount const& amount, Quality const& quality, STAmount const& output)
|
||||
{
|
||||
auto result = multiply(amount, quality.rate(), output.issue());
|
||||
return std::min(result, output);
|
||||
}
|
||||
|
||||
void
|
||||
BasicTaker::log_flow(char const* description, Flow const& flow)
|
||||
{
|
||||
auto stream = journal_.debug();
|
||||
if (!stream)
|
||||
return;
|
||||
|
||||
stream << description;
|
||||
|
||||
if (isXRP(issue_in()))
|
||||
stream << " order in: " << format_amount(flow.order.in);
|
||||
else
|
||||
stream << " order in: " << format_amount(flow.order.in)
|
||||
<< " (issuer: " << format_amount(flow.issuers.in) << ")";
|
||||
|
||||
if (isXRP(issue_out()))
|
||||
stream << " order out: " << format_amount(flow.order.out);
|
||||
else
|
||||
stream << " order out: " << format_amount(flow.order.out)
|
||||
<< " (issuer: " << format_amount(flow.issuers.out) << ")";
|
||||
}
|
||||
|
||||
BasicTaker::Flow
|
||||
BasicTaker::flow_xrp_to_iou(
|
||||
Amounts const& order,
|
||||
Quality quality,
|
||||
STAmount const& owner_funds,
|
||||
STAmount const& taker_funds,
|
||||
Rate const& rate_out)
|
||||
{
|
||||
Flow f;
|
||||
f.order = order;
|
||||
f.issuers.out = multiply(f.order.out, rate_out);
|
||||
|
||||
log_flow("flow_xrp_to_iou", f);
|
||||
|
||||
// Clamp on owner balance
|
||||
if (owner_funds < f.issuers.out)
|
||||
{
|
||||
f.issuers.out = owner_funds;
|
||||
f.order.out = divide(f.issuers.out, rate_out);
|
||||
f.order.in = qual_mul(f.order.out, quality, f.order.in);
|
||||
log_flow("(clamped on owner balance)", f);
|
||||
}
|
||||
|
||||
// Clamp if taker wants to limit the output
|
||||
if (!sell_ && remaining_.out < f.order.out)
|
||||
{
|
||||
f.order.out = remaining_.out;
|
||||
f.order.in = qual_mul(f.order.out, quality, f.order.in);
|
||||
f.issuers.out = multiply(f.order.out, rate_out);
|
||||
log_flow("(clamped on taker output)", f);
|
||||
}
|
||||
|
||||
// Clamp on the taker's funds
|
||||
if (taker_funds < f.order.in)
|
||||
{
|
||||
f.order.in = taker_funds;
|
||||
f.order.out = qual_div(f.order.in, quality, f.order.out);
|
||||
f.issuers.out = multiply(f.order.out, rate_out);
|
||||
log_flow("(clamped on taker funds)", f);
|
||||
}
|
||||
|
||||
// Clamp on remaining offer if we are not handling the second leg
|
||||
// of an autobridge.
|
||||
if (cross_type_ == CrossType::XrpToIou && (remaining_.in < f.order.in))
|
||||
{
|
||||
f.order.in = remaining_.in;
|
||||
f.order.out = qual_div(f.order.in, quality, f.order.out);
|
||||
f.issuers.out = multiply(f.order.out, rate_out);
|
||||
log_flow("(clamped on taker input)", f);
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
BasicTaker::Flow
|
||||
BasicTaker::flow_iou_to_xrp(
|
||||
Amounts const& order,
|
||||
Quality quality,
|
||||
STAmount const& owner_funds,
|
||||
STAmount const& taker_funds,
|
||||
Rate const& rate_in)
|
||||
{
|
||||
Flow f;
|
||||
f.order = order;
|
||||
f.issuers.in = multiply(f.order.in, rate_in);
|
||||
|
||||
log_flow("flow_iou_to_xrp", f);
|
||||
|
||||
// Clamp on owner's funds
|
||||
if (owner_funds < f.order.out)
|
||||
{
|
||||
f.order.out = owner_funds;
|
||||
f.order.in = qual_mul(f.order.out, quality, f.order.in);
|
||||
f.issuers.in = multiply(f.order.in, rate_in);
|
||||
log_flow("(clamped on owner funds)", f);
|
||||
}
|
||||
|
||||
// Clamp if taker wants to limit the output and we are not the
|
||||
// first leg of an autobridge.
|
||||
if (!sell_ && cross_type_ == CrossType::IouToXrp)
|
||||
{
|
||||
if (remaining_.out < f.order.out)
|
||||
{
|
||||
f.order.out = remaining_.out;
|
||||
f.order.in = qual_mul(f.order.out, quality, f.order.in);
|
||||
f.issuers.in = multiply(f.order.in, rate_in);
|
||||
log_flow("(clamped on taker output)", f);
|
||||
}
|
||||
}
|
||||
|
||||
// Clamp on the taker's input offer
|
||||
if (remaining_.in < f.order.in)
|
||||
{
|
||||
f.order.in = remaining_.in;
|
||||
f.issuers.in = multiply(f.order.in, rate_in);
|
||||
f.order.out = qual_div(f.order.in, quality, f.order.out);
|
||||
log_flow("(clamped on taker input)", f);
|
||||
}
|
||||
|
||||
// Clamp on the taker's input balance
|
||||
if (taker_funds < f.issuers.in)
|
||||
{
|
||||
f.issuers.in = taker_funds;
|
||||
f.order.in = divide(f.issuers.in, rate_in);
|
||||
f.order.out = qual_div(f.order.in, quality, f.order.out);
|
||||
log_flow("(clamped on taker funds)", f);
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
BasicTaker::Flow
|
||||
BasicTaker::flow_iou_to_iou(
|
||||
Amounts const& order,
|
||||
Quality quality,
|
||||
STAmount const& owner_funds,
|
||||
STAmount const& taker_funds,
|
||||
Rate const& rate_in,
|
||||
Rate const& rate_out)
|
||||
{
|
||||
Flow f;
|
||||
f.order = order;
|
||||
f.issuers.in = multiply(f.order.in, rate_in);
|
||||
f.issuers.out = multiply(f.order.out, rate_out);
|
||||
|
||||
log_flow("flow_iou_to_iou", f);
|
||||
|
||||
// Clamp on owner balance
|
||||
if (owner_funds < f.issuers.out)
|
||||
{
|
||||
f.issuers.out = owner_funds;
|
||||
f.order.out = divide(f.issuers.out, rate_out);
|
||||
f.order.in = qual_mul(f.order.out, quality, f.order.in);
|
||||
f.issuers.in = multiply(f.order.in, rate_in);
|
||||
log_flow("(clamped on owner funds)", f);
|
||||
}
|
||||
|
||||
// Clamp on taker's offer
|
||||
if (!sell_ && remaining_.out < f.order.out)
|
||||
{
|
||||
f.order.out = remaining_.out;
|
||||
f.order.in = qual_mul(f.order.out, quality, f.order.in);
|
||||
f.issuers.out = multiply(f.order.out, rate_out);
|
||||
f.issuers.in = multiply(f.order.in, rate_in);
|
||||
log_flow("(clamped on taker output)", f);
|
||||
}
|
||||
|
||||
// Clamp on the taker's input offer
|
||||
if (remaining_.in < f.order.in)
|
||||
{
|
||||
f.order.in = remaining_.in;
|
||||
f.issuers.in = multiply(f.order.in, rate_in);
|
||||
f.order.out = qual_div(f.order.in, quality, f.order.out);
|
||||
f.issuers.out = multiply(f.order.out, rate_out);
|
||||
log_flow("(clamped on taker input)", f);
|
||||
}
|
||||
|
||||
// Clamp on the taker's input balance
|
||||
if (taker_funds < f.issuers.in)
|
||||
{
|
||||
f.issuers.in = taker_funds;
|
||||
f.order.in = divide(f.issuers.in, rate_in);
|
||||
f.order.out = qual_div(f.order.in, quality, f.order.out);
|
||||
f.issuers.out = multiply(f.order.out, rate_out);
|
||||
log_flow("(clamped on taker funds)", f);
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
// Calculates the direct flow through the specified offer
|
||||
BasicTaker::Flow
|
||||
BasicTaker::do_cross(Amounts offer, Quality quality, AccountID const& owner)
|
||||
{
|
||||
auto const owner_funds = get_funds(owner, offer.out);
|
||||
auto const taker_funds = get_funds(account(), offer.in);
|
||||
|
||||
Flow result;
|
||||
|
||||
if (cross_type_ == CrossType::XrpToIou)
|
||||
{
|
||||
result = flow_xrp_to_iou(
|
||||
offer,
|
||||
quality,
|
||||
owner_funds,
|
||||
taker_funds,
|
||||
out_rate(owner, account()));
|
||||
}
|
||||
else if (cross_type_ == CrossType::IouToXrp)
|
||||
{
|
||||
result = flow_iou_to_xrp(
|
||||
offer,
|
||||
quality,
|
||||
owner_funds,
|
||||
taker_funds,
|
||||
in_rate(owner, account()));
|
||||
}
|
||||
else
|
||||
{
|
||||
result = flow_iou_to_iou(
|
||||
offer,
|
||||
quality,
|
||||
owner_funds,
|
||||
taker_funds,
|
||||
in_rate(owner, account()),
|
||||
out_rate(owner, account()));
|
||||
}
|
||||
|
||||
if (!result.sanity_check())
|
||||
Throw<std::logic_error>("Computed flow fails sanity check.");
|
||||
|
||||
remaining_.out -= result.order.out;
|
||||
remaining_.in -= result.order.in;
|
||||
|
||||
XRPL_ASSERT(
|
||||
remaining_.in >= beast::zero,
|
||||
"ripple::BasicTaker::do_cross : minimum remaining in");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Calculates the bridged flow through the specified offers
|
||||
std::pair<BasicTaker::Flow, BasicTaker::Flow>
|
||||
BasicTaker::do_cross(
|
||||
Amounts offer1,
|
||||
Quality quality1,
|
||||
AccountID const& owner1,
|
||||
Amounts offer2,
|
||||
Quality quality2,
|
||||
AccountID const& owner2)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
!offer1.in.native(),
|
||||
"ripple::BasicTaker::do_cross : offer1 in is not XRP");
|
||||
XRPL_ASSERT(
|
||||
offer1.out.native(),
|
||||
"ripple::BasicTaker::do_cross : offer1 out is XRP");
|
||||
XRPL_ASSERT(
|
||||
offer2.in.native(), "ripple::BasicTaker::do_cross : offer2 in is XRP");
|
||||
XRPL_ASSERT(
|
||||
!offer2.out.native(),
|
||||
"ripple::BasicTaker::do_cross : offer2 out is not XRP");
|
||||
|
||||
// If the taker owns the first leg of the offer, then the taker's available
|
||||
// funds aren't the limiting factor for the input - the offer itself is.
|
||||
auto leg1_in_funds = get_funds(account(), offer1.in);
|
||||
|
||||
if (account() == owner1)
|
||||
{
|
||||
JLOG(journal_.trace()) << "The taker owns the first leg of a bridge.";
|
||||
leg1_in_funds = std::max(leg1_in_funds, offer1.in);
|
||||
}
|
||||
|
||||
// If the taker owns the second leg of the offer, then the taker's available
|
||||
// funds are not the limiting factor for the output - the offer itself is.
|
||||
auto leg2_out_funds = get_funds(owner2, offer2.out);
|
||||
|
||||
if (account() == owner2)
|
||||
{
|
||||
JLOG(journal_.trace()) << "The taker owns the second leg of a bridge.";
|
||||
leg2_out_funds = std::max(leg2_out_funds, offer2.out);
|
||||
}
|
||||
|
||||
// The amount available to flow via XRP is the amount that the owner of the
|
||||
// first leg of the bridge has, up to the first leg's output.
|
||||
//
|
||||
// But, when both legs of a bridge are owned by the same person, the amount
|
||||
// of XRP that can flow between the two legs is, essentially, infinite
|
||||
// since all the owner is doing is taking out XRP of his left pocket
|
||||
// and putting it in his right pocket. In that case, we set the available
|
||||
// XRP to the largest of the two offers.
|
||||
auto xrp_funds = get_funds(owner1, offer1.out);
|
||||
|
||||
if (owner1 == owner2)
|
||||
{
|
||||
JLOG(journal_.trace())
|
||||
<< "The bridge endpoints are owned by the same account.";
|
||||
xrp_funds = std::max(offer1.out, offer2.in);
|
||||
}
|
||||
|
||||
if (auto stream = journal_.debug())
|
||||
{
|
||||
stream << "Available bridge funds:";
|
||||
stream << " leg1 in: " << format_amount(leg1_in_funds);
|
||||
stream << " leg2 out: " << format_amount(leg2_out_funds);
|
||||
stream << " xrp: " << format_amount(xrp_funds);
|
||||
}
|
||||
|
||||
auto const leg1_rate = in_rate(owner1, account());
|
||||
auto const leg2_rate = out_rate(owner2, account());
|
||||
|
||||
// Attempt to determine the maximal flow that can be achieved across each
|
||||
// leg independent of the other.
|
||||
auto flow1 =
|
||||
flow_iou_to_xrp(offer1, quality1, xrp_funds, leg1_in_funds, leg1_rate);
|
||||
|
||||
if (!flow1.sanity_check())
|
||||
Throw<std::logic_error>("Computed flow1 fails sanity check.");
|
||||
|
||||
auto flow2 =
|
||||
flow_xrp_to_iou(offer2, quality2, leg2_out_funds, xrp_funds, leg2_rate);
|
||||
|
||||
if (!flow2.sanity_check())
|
||||
Throw<std::logic_error>("Computed flow2 fails sanity check.");
|
||||
|
||||
// We now have the maximal flows across each leg individually. We need to
|
||||
// equalize them, so that the amount of XRP that flows out of the first leg
|
||||
// is the same as the amount of XRP that flows into the second leg. We take
|
||||
// the side which is the limiting factor (if any) and adjust the other.
|
||||
if (flow1.order.out < flow2.order.in)
|
||||
{
|
||||
// Adjust the second leg of the offer down:
|
||||
flow2.order.in = flow1.order.out;
|
||||
flow2.order.out = qual_div(flow2.order.in, quality2, flow2.order.out);
|
||||
flow2.issuers.out = multiply(flow2.order.out, leg2_rate);
|
||||
log_flow("Balancing: adjusted second leg down", flow2);
|
||||
}
|
||||
else if (flow1.order.out > flow2.order.in)
|
||||
{
|
||||
// Adjust the first leg of the offer down:
|
||||
flow1.order.out = flow2.order.in;
|
||||
flow1.order.in = qual_mul(flow1.order.out, quality1, flow1.order.in);
|
||||
flow1.issuers.in = multiply(flow1.order.in, leg1_rate);
|
||||
log_flow("Balancing: adjusted first leg down", flow2);
|
||||
}
|
||||
|
||||
if (flow1.order.out != flow2.order.in)
|
||||
Throw<std::logic_error>("Bridged flow is out of balance.");
|
||||
|
||||
remaining_.out -= flow2.order.out;
|
||||
remaining_.in -= flow1.order.in;
|
||||
|
||||
return std::make_pair(flow1, flow2);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
||||
Taker::Taker(
|
||||
CrossType cross_type,
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
Amounts const& offer,
|
||||
std::uint32_t flags,
|
||||
beast::Journal journal)
|
||||
: BasicTaker(
|
||||
cross_type,
|
||||
account,
|
||||
offer,
|
||||
Quality(offer),
|
||||
flags,
|
||||
calculateRate(view, offer.in.getIssuer(), account),
|
||||
calculateRate(view, offer.out.getIssuer(), account),
|
||||
journal)
|
||||
, view_(view)
|
||||
, xrp_flow_(0)
|
||||
, direct_crossings_(0)
|
||||
, bridge_crossings_(0)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
issue_in() == offer.in.issue(),
|
||||
"ripple::Taker::Taker : issue in is a match");
|
||||
XRPL_ASSERT(
|
||||
issue_out() == offer.out.issue(),
|
||||
"ripple::Taker::Taker : issue out is a match");
|
||||
|
||||
if (auto stream = journal_.debug())
|
||||
{
|
||||
stream << "Crossing as: " << to_string(account);
|
||||
|
||||
if (isXRP(issue_in()))
|
||||
stream << " Offer in: " << format_amount(offer.in);
|
||||
else
|
||||
stream << " Offer in: " << format_amount(offer.in)
|
||||
<< " (issuer: " << issue_in().account << ")";
|
||||
|
||||
if (isXRP(issue_out()))
|
||||
stream << " Offer out: " << format_amount(offer.out);
|
||||
else
|
||||
stream << " Offer out: " << format_amount(offer.out)
|
||||
<< " (issuer: " << issue_out().account << ")";
|
||||
|
||||
stream << " Balance: "
|
||||
<< format_amount(get_funds(account, offer.in));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Taker::consume_offer(Offer& offer, Amounts const& order)
|
||||
{
|
||||
if (order.in < beast::zero)
|
||||
Throw<std::logic_error>("flow with negative input.");
|
||||
|
||||
if (order.out < beast::zero)
|
||||
Throw<std::logic_error>("flow with negative output.");
|
||||
|
||||
JLOG(journal_.debug()) << "Consuming from offer " << offer;
|
||||
|
||||
if (auto stream = journal_.trace())
|
||||
{
|
||||
auto const& available = offer.amount();
|
||||
|
||||
stream << " in:" << format_amount(available.in);
|
||||
stream << " out:" << format_amount(available.out);
|
||||
}
|
||||
|
||||
offer.consume(view_, order);
|
||||
}
|
||||
|
||||
STAmount
|
||||
Taker::get_funds(AccountID const& account, STAmount const& amount) const
|
||||
{
|
||||
return accountFunds(view_, account, amount, fhZERO_IF_FROZEN, journal_);
|
||||
}
|
||||
|
||||
TER
|
||||
Taker::transferXRP(
|
||||
AccountID const& from,
|
||||
AccountID const& to,
|
||||
STAmount const& amount)
|
||||
{
|
||||
if (!isXRP(amount))
|
||||
Throw<std::logic_error>("Using transferXRP with IOU");
|
||||
|
||||
if (from == to)
|
||||
return tesSUCCESS;
|
||||
|
||||
// Transferring zero is equivalent to not doing a transfer
|
||||
if (amount == beast::zero)
|
||||
return tesSUCCESS;
|
||||
|
||||
return ripple::transferXRP(view_, from, to, amount, journal_);
|
||||
}
|
||||
|
||||
TER
|
||||
Taker::redeemIOU(
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue)
|
||||
{
|
||||
if (isXRP(amount))
|
||||
Throw<std::logic_error>("Using redeemIOU with XRP");
|
||||
|
||||
if (account == issue.account)
|
||||
return tesSUCCESS;
|
||||
|
||||
// Transferring zero is equivalent to not doing a transfer
|
||||
if (amount == beast::zero)
|
||||
return tesSUCCESS;
|
||||
|
||||
// If we are trying to redeem some amount, then the account
|
||||
// must have a credit balance.
|
||||
if (get_funds(account, amount) <= beast::zero)
|
||||
Throw<std::logic_error>("redeemIOU has no funds to redeem");
|
||||
|
||||
auto ret = ripple::redeemIOU(view_, account, amount, issue, journal_);
|
||||
|
||||
if (get_funds(account, amount) < beast::zero)
|
||||
Throw<std::logic_error>("redeemIOU redeemed more funds than available");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
TER
|
||||
Taker::issueIOU(
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue)
|
||||
{
|
||||
if (isXRP(amount))
|
||||
Throw<std::logic_error>("Using issueIOU with XRP");
|
||||
|
||||
if (account == issue.account)
|
||||
return tesSUCCESS;
|
||||
|
||||
// Transferring zero is equivalent to not doing a transfer
|
||||
if (amount == beast::zero)
|
||||
return tesSUCCESS;
|
||||
|
||||
return ripple::issueIOU(view_, account, amount, issue, journal_);
|
||||
}
|
||||
|
||||
// Performs funds transfers to fill the given offer and adjusts offer.
|
||||
TER
|
||||
Taker::fill(BasicTaker::Flow const& flow, Offer& offer)
|
||||
{
|
||||
// adjust offer
|
||||
consume_offer(offer, flow.order);
|
||||
|
||||
TER result = tesSUCCESS;
|
||||
|
||||
if (cross_type() != CrossType::XrpToIou)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
!isXRP(flow.order.in), "ripple::Taker::fill : order in is not XRP");
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
result =
|
||||
redeemIOU(account(), flow.issuers.in, flow.issuers.in.issue());
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
result =
|
||||
issueIOU(offer.owner(), flow.order.in, flow.order.in.issue());
|
||||
}
|
||||
else
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
isXRP(flow.order.in), "ripple::Taker::fill : order in is XRP");
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
result = transferXRP(account(), offer.owner(), flow.order.in);
|
||||
}
|
||||
|
||||
// Now send funds from the account whose offer we're taking
|
||||
if (cross_type() != CrossType::IouToXrp)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
!isXRP(flow.order.out),
|
||||
"ripple::Taker::fill : order out is not XRP");
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
result = redeemIOU(
|
||||
offer.owner(), flow.issuers.out, flow.issuers.out.issue());
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
result =
|
||||
issueIOU(account(), flow.order.out, flow.order.out.issue());
|
||||
}
|
||||
else
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
isXRP(flow.order.out), "ripple::Taker::fill : order out is XRP");
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
result = transferXRP(offer.owner(), account(), flow.order.out);
|
||||
}
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
direct_crossings_++;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Performs bridged funds transfers to fill the given offers and adjusts offers.
|
||||
TER
|
||||
Taker::fill(
|
||||
BasicTaker::Flow const& flow1,
|
||||
Offer& leg1,
|
||||
BasicTaker::Flow const& flow2,
|
||||
Offer& leg2)
|
||||
{
|
||||
// Adjust offers accordingly
|
||||
consume_offer(leg1, flow1.order);
|
||||
consume_offer(leg2, flow2.order);
|
||||
|
||||
TER result = tesSUCCESS;
|
||||
|
||||
// Taker to leg1: IOU
|
||||
if (leg1.owner() != account())
|
||||
{
|
||||
if (result == tesSUCCESS)
|
||||
result = redeemIOU(
|
||||
account(), flow1.issuers.in, flow1.issuers.in.issue());
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
result =
|
||||
issueIOU(leg1.owner(), flow1.order.in, flow1.order.in.issue());
|
||||
}
|
||||
|
||||
// leg1 to leg2: bridging over XRP
|
||||
if (result == tesSUCCESS)
|
||||
result = transferXRP(leg1.owner(), leg2.owner(), flow1.order.out);
|
||||
|
||||
// leg2 to Taker: IOU
|
||||
if (leg2.owner() != account())
|
||||
{
|
||||
if (result == tesSUCCESS)
|
||||
result = redeemIOU(
|
||||
leg2.owner(), flow2.issuers.out, flow2.issuers.out.issue());
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
result =
|
||||
issueIOU(account(), flow2.order.out, flow2.order.out.issue());
|
||||
}
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
{
|
||||
bridge_crossings_++;
|
||||
xrp_flow_ += flow1.order.out;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TER
|
||||
Taker::cross(Offer& offer)
|
||||
{
|
||||
// In direct crossings, at least one leg must not be XRP.
|
||||
if (isXRP(offer.amount().in) && isXRP(offer.amount().out))
|
||||
return tefINTERNAL;
|
||||
|
||||
auto const amount =
|
||||
do_cross(offer.amount(), offer.quality(), offer.owner());
|
||||
|
||||
return fill(amount, offer);
|
||||
}
|
||||
|
||||
TER
|
||||
Taker::cross(Offer& leg1, Offer& leg2)
|
||||
{
|
||||
// In bridged crossings, XRP must can't be the input to the first leg
|
||||
// or the output of the second leg.
|
||||
if (isXRP(leg1.amount().in) || isXRP(leg2.amount().out))
|
||||
return tefINTERNAL;
|
||||
|
||||
auto ret = do_cross(
|
||||
leg1.amount(),
|
||||
leg1.quality(),
|
||||
leg1.owner(),
|
||||
leg2.amount(),
|
||||
leg2.quality(),
|
||||
leg2.owner());
|
||||
|
||||
return fill(ret.first, leg1, ret.second, leg2);
|
||||
}
|
||||
|
||||
Rate
|
||||
Taker::calculateRate(
|
||||
ApplyView const& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& account)
|
||||
{
|
||||
return isXRP(issuer) || (account == issuer) ? parityRate
|
||||
: transferRate(view, issuer);
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
@@ -1,341 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2014 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.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_APP_BOOK_TAKER_H_INCLUDED
|
||||
#define RIPPLE_APP_BOOK_TAKER_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/tx/detail/Offer.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
|
||||
#include <xrpl/beast/utility/Journal.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/Rate.h>
|
||||
#include <xrpl/protocol/TER.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** The flavor of an offer crossing */
|
||||
enum class CrossType { XrpToIou, IouToXrp, IouToIou };
|
||||
|
||||
/** State for the active party during order book or payment operations. */
|
||||
class BasicTaker
|
||||
{
|
||||
private:
|
||||
AccountID account_;
|
||||
Quality quality_;
|
||||
Quality threshold_;
|
||||
|
||||
bool sell_;
|
||||
|
||||
// The original in and out quantities.
|
||||
Amounts const original_;
|
||||
|
||||
// The amounts still left over for us to try and take.
|
||||
Amounts remaining_;
|
||||
|
||||
// The issuers for the input and output
|
||||
Issue const& issue_in_;
|
||||
Issue const& issue_out_;
|
||||
|
||||
// The rates that will be paid when the input and output currencies are
|
||||
// transfered and the currency issuer isn't involved:
|
||||
Rate const m_rate_in;
|
||||
Rate const m_rate_out;
|
||||
|
||||
// The type of crossing that we are performing
|
||||
CrossType cross_type_;
|
||||
|
||||
protected:
|
||||
beast::Journal const journal_;
|
||||
|
||||
struct Flow
|
||||
{
|
||||
explicit Flow() = default;
|
||||
|
||||
Amounts order;
|
||||
Amounts issuers;
|
||||
|
||||
bool
|
||||
sanity_check() const
|
||||
{
|
||||
using beast::zero;
|
||||
|
||||
if (isXRP(order.in) && isXRP(order.out))
|
||||
return false;
|
||||
|
||||
return order.in >= zero && order.out >= zero &&
|
||||
issuers.in >= zero && issuers.out >= zero;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
void
|
||||
log_flow(char const* description, Flow const& flow);
|
||||
|
||||
Flow
|
||||
flow_xrp_to_iou(
|
||||
Amounts const& offer,
|
||||
Quality quality,
|
||||
STAmount const& owner_funds,
|
||||
STAmount const& taker_funds,
|
||||
Rate const& rate_out);
|
||||
|
||||
Flow
|
||||
flow_iou_to_xrp(
|
||||
Amounts const& offer,
|
||||
Quality quality,
|
||||
STAmount const& owner_funds,
|
||||
STAmount const& taker_funds,
|
||||
Rate const& rate_in);
|
||||
|
||||
Flow
|
||||
flow_iou_to_iou(
|
||||
Amounts const& offer,
|
||||
Quality quality,
|
||||
STAmount const& owner_funds,
|
||||
STAmount const& taker_funds,
|
||||
Rate const& rate_in,
|
||||
Rate const& rate_out);
|
||||
|
||||
// Calculates the transfer rate that we should use when calculating
|
||||
// flows for a particular issue between two accounts.
|
||||
static Rate
|
||||
effective_rate(
|
||||
Rate const& rate,
|
||||
Issue const& issue,
|
||||
AccountID const& from,
|
||||
AccountID const& to);
|
||||
|
||||
// The transfer rate for the input currency between the given accounts
|
||||
Rate
|
||||
in_rate(AccountID const& from, AccountID const& to) const
|
||||
{
|
||||
return effective_rate(m_rate_in, original_.in.issue(), from, to);
|
||||
}
|
||||
|
||||
// The transfer rate for the output currency between the given accounts
|
||||
Rate
|
||||
out_rate(AccountID const& from, AccountID const& to) const
|
||||
{
|
||||
return effective_rate(m_rate_out, original_.out.issue(), from, to);
|
||||
}
|
||||
|
||||
public:
|
||||
BasicTaker() = delete;
|
||||
BasicTaker(BasicTaker const&) = delete;
|
||||
|
||||
BasicTaker(
|
||||
CrossType cross_type,
|
||||
AccountID const& account,
|
||||
Amounts const& amount,
|
||||
Quality const& quality,
|
||||
std::uint32_t flags,
|
||||
Rate const& rate_in,
|
||||
Rate const& rate_out,
|
||||
beast::Journal journal = beast::Journal{beast::Journal::getNullSink()});
|
||||
|
||||
virtual ~BasicTaker() = default;
|
||||
|
||||
/** Returns the amount remaining on the offer.
|
||||
This is the amount at which the offer should be placed. It may either
|
||||
be for the full amount when there were no crossing offers, or for zero
|
||||
when the offer fully crossed, or any amount in between.
|
||||
It is always at the original offer quality (quality_)
|
||||
*/
|
||||
Amounts
|
||||
remaining_offer() const;
|
||||
|
||||
/** Returns the amount that the offer was originally placed at. */
|
||||
Amounts const&
|
||||
original_offer() const;
|
||||
|
||||
/** Returns the account identifier of the taker. */
|
||||
AccountID const&
|
||||
account() const noexcept
|
||||
{
|
||||
return account_;
|
||||
}
|
||||
|
||||
/** Returns `true` if the quality does not meet the taker's requirements. */
|
||||
bool
|
||||
reject(Quality const& quality) const noexcept
|
||||
{
|
||||
return quality < threshold_;
|
||||
}
|
||||
|
||||
/** Returns the type of crossing that is being performed */
|
||||
CrossType
|
||||
cross_type() const
|
||||
{
|
||||
return cross_type_;
|
||||
}
|
||||
|
||||
/** Returns the Issue associated with the input of the offer */
|
||||
Issue const&
|
||||
issue_in() const
|
||||
{
|
||||
return issue_in_;
|
||||
}
|
||||
|
||||
/** Returns the Issue associated with the output of the offer */
|
||||
Issue const&
|
||||
issue_out() const
|
||||
{
|
||||
return issue_out_;
|
||||
}
|
||||
|
||||
/** Returns `true` if the taker has run out of funds. */
|
||||
bool
|
||||
unfunded() const;
|
||||
|
||||
/** Returns `true` if order crossing should not continue.
|
||||
Order processing is stopped if the taker's order quantities have
|
||||
been reached, or if the taker has run out of input funds.
|
||||
*/
|
||||
bool
|
||||
done() const;
|
||||
|
||||
/** Perform direct crossing through given offer.
|
||||
@return an `Amounts` describing the flow achieved during cross
|
||||
*/
|
||||
BasicTaker::Flow
|
||||
do_cross(Amounts offer, Quality quality, AccountID const& owner);
|
||||
|
||||
/** Perform bridged crossing through given offers.
|
||||
@return a pair of `Amounts` describing the flow achieved during cross
|
||||
*/
|
||||
std::pair<BasicTaker::Flow, BasicTaker::Flow>
|
||||
do_cross(
|
||||
Amounts offer1,
|
||||
Quality quality1,
|
||||
AccountID const& owner1,
|
||||
Amounts offer2,
|
||||
Quality quality2,
|
||||
AccountID const& owner2);
|
||||
|
||||
virtual STAmount
|
||||
get_funds(AccountID const& account, STAmount const& funds) const = 0;
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
class Taker : public BasicTaker
|
||||
{
|
||||
public:
|
||||
Taker() = delete;
|
||||
Taker(Taker const&) = delete;
|
||||
|
||||
Taker(
|
||||
CrossType cross_type,
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
Amounts const& offer,
|
||||
std::uint32_t flags,
|
||||
beast::Journal journal);
|
||||
~Taker() = default;
|
||||
|
||||
void
|
||||
consume_offer(Offer& offer, Amounts const& order);
|
||||
|
||||
STAmount
|
||||
get_funds(AccountID const& account, STAmount const& funds) const override;
|
||||
|
||||
STAmount const&
|
||||
get_xrp_flow() const
|
||||
{
|
||||
return xrp_flow_;
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
get_direct_crossings() const
|
||||
{
|
||||
return direct_crossings_;
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
get_bridge_crossings() const
|
||||
{
|
||||
return bridge_crossings_;
|
||||
}
|
||||
|
||||
/** Perform a direct or bridged offer crossing as appropriate.
|
||||
Funds will be transferred accordingly, and offers will be adjusted.
|
||||
@return tesSUCCESS if successful, or an error code otherwise.
|
||||
*/
|
||||
/** @{ */
|
||||
TER
|
||||
cross(Offer& offer);
|
||||
|
||||
TER
|
||||
cross(Offer& leg1, Offer& leg2);
|
||||
/** @} */
|
||||
|
||||
private:
|
||||
static Rate
|
||||
calculateRate(
|
||||
ApplyView const& view,
|
||||
AccountID const& issuer,
|
||||
AccountID const& account);
|
||||
|
||||
TER
|
||||
fill(BasicTaker::Flow const& flow, Offer& offer);
|
||||
|
||||
TER
|
||||
fill(
|
||||
BasicTaker::Flow const& flow1,
|
||||
Offer& leg1,
|
||||
BasicTaker::Flow const& flow2,
|
||||
Offer& leg2);
|
||||
|
||||
TER
|
||||
transferXRP(
|
||||
AccountID const& from,
|
||||
AccountID const& to,
|
||||
STAmount const& amount);
|
||||
|
||||
TER
|
||||
redeemIOU(
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue);
|
||||
|
||||
TER
|
||||
issueIOU(
|
||||
AccountID const& account,
|
||||
STAmount const& amount,
|
||||
Issue const& issue);
|
||||
|
||||
private:
|
||||
// The underlying ledger entry we are dealing with
|
||||
ApplyView& view_;
|
||||
|
||||
// The amount of XRP that flowed if we were autobridging
|
||||
STAmount xrp_flow_;
|
||||
|
||||
// The number direct crossings that we performed
|
||||
std::uint32_t direct_crossings_;
|
||||
|
||||
// The number autobridged crossings that we performed
|
||||
std::uint32_t bridge_crossings_;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user