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:
Vlad
2025-07-23 14:57:51 +01:00
committed by GitHub
parent faa781b71f
commit 433eeabfa5
5 changed files with 14 additions and 3064 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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.

View File

@@ -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;

View File

@@ -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

View File

@@ -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