mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 02:55:50 +00:00
Finalize autobridging implementation (RIPD-179):
Autobridging uses XRP as a natural bridge currency to allow IOU-to-IOU orders to be satisfied not only from the direct IOU-to-IOU books but also over the combined IOU-to-XRP and XRP-to-IOU books. This commit addresses the following issues: * RIPD-486: Refactoring the taker into a unit-testable architecture * RIPD-659: Asset-aware offer crossing * RIPD-491: Unit tests for IOU to XRP, XRP to IOU and IOU to IOU * RIPD-441: Handle case when autobridging over same owner offers * RIPD-665: Handle case when autobridging over own offers * RIPD-273: Groom order books while crossing
This commit is contained in:
@@ -1779,6 +1779,9 @@
|
||||
<ClCompile Include="..\..\src\ripple\app\book\tests\Quality.test.cpp">
|
||||
<ExcludedFromBuild>True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\book\tests\Taker.test.cpp">
|
||||
<ExcludedFromBuild>True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\app\book\Types.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\consensus\DisputedTx.cpp">
|
||||
@@ -2119,14 +2122,6 @@
|
||||
<ClCompile Include="..\..\src\ripple\app\transactors\CreateOffer.cpp">
|
||||
<ExcludedFromBuild>True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\app\transactors\CreateOffer.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\transactors\CreateOfferBridged.cpp">
|
||||
<ExcludedFromBuild>True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\transactors\CreateOfferDirect.cpp">
|
||||
<ExcludedFromBuild>True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\transactors\CreateTicket.cpp">
|
||||
<ExcludedFromBuild>True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
|
||||
@@ -2709,6 +2709,9 @@
|
||||
<ClCompile Include="..\..\src\ripple\app\book\tests\Quality.test.cpp">
|
||||
<Filter>ripple\app\book\tests</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\book\tests\Taker.test.cpp">
|
||||
<Filter>ripple\app\book\tests</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\app\book\Types.h">
|
||||
<Filter>ripple\app\book</Filter>
|
||||
</ClInclude>
|
||||
@@ -3114,15 +3117,6 @@
|
||||
<ClCompile Include="..\..\src\ripple\app\transactors\CreateOffer.cpp">
|
||||
<Filter>ripple\app\transactors</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\app\transactors\CreateOffer.h">
|
||||
<Filter>ripple\app\transactors</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\transactors\CreateOfferBridged.cpp">
|
||||
<Filter>ripple\app\transactors</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\transactors\CreateOfferDirect.cpp">
|
||||
<Filter>ripple\app\transactors</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\transactors\CreateTicket.cpp">
|
||||
<Filter>ripple\app\transactors</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
This determines whether ripple implements offer autobridging via XRP.
|
||||
*/
|
||||
#ifndef RIPPLE_ENABLE_AUTOBRIDGING
|
||||
#define RIPPLE_ENABLE_AUTOBRIDGING 0
|
||||
#define RIPPLE_ENABLE_AUTOBRIDGING 1
|
||||
#endif
|
||||
|
||||
/** Config: RIPPLE_SINGLE_IO_SERVICE_THREAD
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <beast/utility/noexcept.h>
|
||||
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace ripple {
|
||||
namespace core {
|
||||
@@ -42,6 +43,9 @@ public:
|
||||
private:
|
||||
SLE::pointer m_entry;
|
||||
Quality m_quality;
|
||||
Account m_account;
|
||||
|
||||
mutable Amounts m_amounts;
|
||||
|
||||
public:
|
||||
Offer() = default;
|
||||
@@ -49,6 +53,10 @@ public:
|
||||
Offer (SLE::pointer const& entry, Quality quality)
|
||||
: m_entry (entry)
|
||||
, m_quality (quality)
|
||||
, m_account (m_entry->getFieldAccount160 (sfAccount))
|
||||
, m_amounts (
|
||||
m_entry->getFieldAmount (sfTakerPays),
|
||||
m_entry->getFieldAmount (sfTakerGets))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -62,46 +70,60 @@ public:
|
||||
original quality.
|
||||
*/
|
||||
Quality const
|
||||
quality() const noexcept
|
||||
quality () const noexcept
|
||||
{
|
||||
return m_quality;
|
||||
}
|
||||
|
||||
/** Returns the account id of the offer's owner. */
|
||||
Account const
|
||||
account() const
|
||||
Account const&
|
||||
owner () const
|
||||
{
|
||||
return m_entry->getFieldAccount160 (sfAccount);
|
||||
return m_account;
|
||||
}
|
||||
|
||||
/** Returns the in and out amounts.
|
||||
Some or all of the out amount may be unfunded.
|
||||
*/
|
||||
Amounts const
|
||||
amount() const
|
||||
Amounts const&
|
||||
amount () const
|
||||
{
|
||||
return Amounts (
|
||||
m_entry->getFieldAmount (sfTakerPays),
|
||||
m_entry->getFieldAmount (sfTakerGets));
|
||||
return m_amounts;
|
||||
}
|
||||
|
||||
/** Returns `true` if no more funds can flow through this offer. */
|
||||
bool
|
||||
fully_consumed() const
|
||||
fully_consumed () const
|
||||
{
|
||||
if (m_entry->getFieldAmount (sfTakerPays) <= zero)
|
||||
if (m_amounts.in <= zero)
|
||||
return true;
|
||||
if (m_entry->getFieldAmount (sfTakerGets) <= zero)
|
||||
if (m_amounts.out <= zero)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns the ledger entry underlying the offer. */
|
||||
// AVOID USING THIS
|
||||
SLE::pointer
|
||||
entry() const noexcept
|
||||
/** Adjusts the offer to indicate that we consumed some (or all) of it. */
|
||||
void
|
||||
consume (LedgerView& view, Amounts const& consumed) const
|
||||
{
|
||||
return m_entry;
|
||||
if (consumed.in > m_amounts.in)
|
||||
throw std::logic_error ("can't consume more than is available.");
|
||||
|
||||
if (consumed.out > m_amounts.out)
|
||||
throw std::logic_error ("can't produce more than is available.");
|
||||
|
||||
m_amounts.in -= consumed.in;
|
||||
m_amounts.out -= consumed.out;
|
||||
|
||||
m_entry->setFieldAmount (sfTakerPays, m_amounts.in);
|
||||
m_entry->setFieldAmount (sfTakerGets, m_amounts.out);
|
||||
|
||||
view.entryModify (m_entry);
|
||||
}
|
||||
|
||||
std::string id () const
|
||||
{
|
||||
return to_string (m_entry->getIndex());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -109,7 +131,7 @@ inline
|
||||
std::ostream&
|
||||
operator<< (std::ostream& os, Offer const& offer)
|
||||
{
|
||||
return os << offer.entry()->getIndex();
|
||||
return os << offer.id ();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -104,17 +104,6 @@ public:
|
||||
*/
|
||||
bool
|
||||
step ();
|
||||
|
||||
/** Advance to the next valid offer that is not from the specified account.
|
||||
This automatically removes:
|
||||
- Offers with missing ledger entries
|
||||
- Offers found unfunded
|
||||
- Offers from the same account
|
||||
- Expired offers
|
||||
@return `true` if there is a valid offer.
|
||||
*/
|
||||
bool
|
||||
step_account (Account const& account);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <ripple/app/book/Offer.h>
|
||||
#include <ripple/app/book/Types.h>
|
||||
#include <ripple/protocol/TxFlags.h>
|
||||
|
||||
#include <beast/utility/noexcept.h>
|
||||
#include <functional>
|
||||
|
||||
@@ -32,85 +33,167 @@ namespace ripple {
|
||||
namespace core {
|
||||
|
||||
/** State for the active party during order book or payment operations. */
|
||||
class Taker
|
||||
class BasicTaker
|
||||
{
|
||||
public:
|
||||
struct Options
|
||||
private:
|
||||
class Rate
|
||||
{
|
||||
Options() = delete;
|
||||
private:
|
||||
std::uint32_t quality_;
|
||||
Amount rate_;
|
||||
|
||||
explicit
|
||||
Options (std::uint32_t tx_flags)
|
||||
: sell (tx_flags & tfSell)
|
||||
, passive (tx_flags & tfPassive)
|
||||
, fill_or_kill (tx_flags & tfFillOrKill)
|
||||
, immediate_or_cancel (tx_flags & tfImmediateOrCancel)
|
||||
public:
|
||||
Rate (std::uint32_t quality)
|
||||
: quality_ (quality)
|
||||
{
|
||||
assert (quality_ != 0);
|
||||
rate_ = amountFromRate (quality_);
|
||||
}
|
||||
|
||||
bool const sell;
|
||||
bool const passive;
|
||||
bool const fill_or_kill;
|
||||
bool const immediate_or_cancel;
|
||||
Amount
|
||||
divide (Amount const& amount) const;
|
||||
|
||||
Amount
|
||||
multiply (Amount const& amount) const;
|
||||
};
|
||||
|
||||
private:
|
||||
std::reference_wrapper <LedgerView> m_view;
|
||||
Account m_account;
|
||||
Options m_options;
|
||||
Quality m_quality;
|
||||
Quality m_threshold;
|
||||
Account account_;
|
||||
Quality quality_;
|
||||
Quality threshold_;
|
||||
|
||||
bool sell_;
|
||||
|
||||
// The original in and out quantities.
|
||||
Amounts const m_amount;
|
||||
Amounts const original_;
|
||||
|
||||
// The amounts still left over for us to try and take.
|
||||
Amounts m_remain;
|
||||
Amounts remaining_;
|
||||
|
||||
Amounts
|
||||
flow (Amounts amount, Offer const& offer, Account const& taker);
|
||||
// The issuers for the input and output
|
||||
Issue const& issue_in_;
|
||||
Issue const& issue_out_;
|
||||
|
||||
TER
|
||||
fill (Offer const& offer, Amounts const& amount);
|
||||
// The rates that will be paid when the input and output currencies are
|
||||
// transfer when the currency issuer isn't involved:
|
||||
std::uint32_t const m_rate_in;
|
||||
std::uint32_t const m_rate_out;
|
||||
|
||||
TER
|
||||
fill (Offer const& leg1, Amounts const& amount1,
|
||||
Offer const& leg2, Amounts const& amount2);
|
||||
// The type of crossing that we are performing
|
||||
CrossType cross_type_;
|
||||
|
||||
void
|
||||
consume (Offer const& offer, Amounts const& consumed) const;
|
||||
protected:
|
||||
struct Flow
|
||||
{
|
||||
Amounts order;
|
||||
Amounts issuers;
|
||||
|
||||
bool sanity_check () const
|
||||
{
|
||||
if (isXRP (order.in) && isXRP (order.out))
|
||||
return false;
|
||||
|
||||
return order.in >= zero &&
|
||||
order.out >= zero &&
|
||||
issuers.in >= zero &&
|
||||
issuers.out >= zero;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
Flow
|
||||
flow_xrp_to_iou (Amounts const& offer, Quality quality,
|
||||
Amount const& owner_funds, Amount const& taker_funds,
|
||||
Rate const& rate_out);
|
||||
|
||||
Flow
|
||||
flow_iou_to_xrp (Amounts const& offer, Quality quality,
|
||||
Amount const& owner_funds, Amount const& taker_funds,
|
||||
Rate const& rate_in);
|
||||
|
||||
Flow
|
||||
flow_iou_to_iou (Amounts const& offer, Quality quality,
|
||||
Amount const& owner_funds, Amount 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 (std::uint32_t rate, Issue const &issue,
|
||||
Account const& from, Account const& to);
|
||||
|
||||
// The transfer rate for the input currency between the given accounts
|
||||
Rate
|
||||
in_rate (Account const& from, Account 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 (Account const& from, Account const& to) const
|
||||
{
|
||||
return effective_rate (m_rate_out, original_.out.issue (), from, to);
|
||||
}
|
||||
|
||||
public:
|
||||
Taker (LedgerView& view, Account const& account,
|
||||
Amounts const& amount, Options const& options);
|
||||
BasicTaker () = delete;
|
||||
BasicTaker (BasicTaker const&) = delete;
|
||||
|
||||
LedgerView&
|
||||
view () const noexcept
|
||||
{
|
||||
return m_view;
|
||||
}
|
||||
BasicTaker (
|
||||
CrossType cross_type, Account const& account, Amounts const& amount,
|
||||
Quality const& quality, std::uint32_t flags, std::uint32_t rate_in,
|
||||
std::uint32_t rate_out);
|
||||
|
||||
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 (m_quality)
|
||||
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. */
|
||||
Account const&
|
||||
account () const noexcept
|
||||
{
|
||||
return m_account;
|
||||
return account_;
|
||||
}
|
||||
|
||||
/** Returns `true` if the quality does not meet the taker's requirements. */
|
||||
bool
|
||||
reject (Quality const& quality) const noexcept
|
||||
{
|
||||
return quality < m_threshold;
|
||||
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 order crossing should not continue.
|
||||
@@ -121,25 +204,106 @@ public:
|
||||
done () const;
|
||||
|
||||
/** Perform direct crossing through given offer.
|
||||
@return tesSUCCESS on success, error code otherwise.
|
||||
@return an `Amounts` describing the flow achieved during cross
|
||||
*/
|
||||
BasicTaker::Flow
|
||||
do_cross (Amounts offer, Quality quality, Account 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, Account const& owner1,
|
||||
Amounts offer2, Quality quality2, Account const& owner2);
|
||||
|
||||
virtual
|
||||
Amount
|
||||
get_funds (Account const& account, Amount const& funds) const = 0;
|
||||
};
|
||||
|
||||
class Taker
|
||||
: public BasicTaker
|
||||
{
|
||||
private:
|
||||
static
|
||||
std::uint32_t
|
||||
calculateRate (LedgerView& view, Account const& issuer, Account const& account);
|
||||
|
||||
// The underlying ledger entry we are dealing with
|
||||
LedgerView& m_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_;
|
||||
|
||||
TER
|
||||
fill (BasicTaker::Flow const& flow, Offer const& offer);
|
||||
|
||||
TER
|
||||
fill (
|
||||
BasicTaker::Flow const& flow1, Offer const& leg1,
|
||||
BasicTaker::Flow const& flow2, Offer const& leg2);
|
||||
|
||||
TER
|
||||
transfer_xrp (Account const& from, Account const& to, Amount const& amount);
|
||||
|
||||
TER
|
||||
redeem_iou (Account const& account, Amount const& amount, Issue const& issue);
|
||||
|
||||
TER
|
||||
issue_iou (Account const& account, Amount const& amount, Issue const& issue);
|
||||
|
||||
public:
|
||||
Taker () = delete;
|
||||
Taker (Taker const&) = delete;
|
||||
|
||||
Taker (CrossType cross_type, LedgerView& view, Account const& account,
|
||||
Amounts const& offer, std::uint32_t flags);
|
||||
~Taker () = default;
|
||||
|
||||
void
|
||||
consume_offer (Offer const& offer, Amounts const& order);
|
||||
|
||||
Amount
|
||||
get_funds (Account const& account, Amount const& funds) const;
|
||||
|
||||
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 const& offer);
|
||||
|
||||
/** Perform bridged crossing through given offers.
|
||||
@return tesSUCCESS on success, error code otherwise.
|
||||
*/
|
||||
TER
|
||||
cross (Offer const& leg1, Offer const& leg2);
|
||||
/** @} */
|
||||
};
|
||||
|
||||
inline
|
||||
std::ostream&
|
||||
operator<< (std::ostream& os, Taker const& taker)
|
||||
{
|
||||
return os << taker.account();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,14 @@
|
||||
namespace ripple {
|
||||
namespace core {
|
||||
|
||||
/** The flavor of an offer crossing */
|
||||
enum class CrossType
|
||||
{
|
||||
XrpToIou,
|
||||
IouToXrp,
|
||||
IouToIou
|
||||
};
|
||||
|
||||
/** A mutable view that overlays an immutable ledger to track changes. */
|
||||
typedef LedgerEntrySet LedgerView;
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ OfferStream::step ()
|
||||
// NIKB NOTE The calling code also checks the funds, how expensive is
|
||||
// looking up the funds twice?
|
||||
Amount const owner_funds (view().accountFunds (
|
||||
m_offer.account(), m_offer.amount().out, fhZERO_IF_FROZEN));
|
||||
m_offer.owner(), amount.out, fhZERO_IF_FROZEN));
|
||||
|
||||
// Check for unfunded offer
|
||||
if (owner_funds <= zero)
|
||||
@@ -133,8 +133,10 @@ OfferStream::step ()
|
||||
// If the owner's balance in the pristine view is the same,
|
||||
// we haven't modified the balance and therefore the
|
||||
// offer is "found unfunded" versus "became unfunded"
|
||||
if (view_cancel().accountFunds (m_offer.account(),
|
||||
m_offer.amount().out, fhZERO_IF_FROZEN) == owner_funds)
|
||||
auto const original_funds = view_cancel().accountFunds (
|
||||
m_offer.owner(), amount.out, fhZERO_IF_FROZEN);
|
||||
|
||||
if (original_funds == owner_funds)
|
||||
{
|
||||
view_cancel().offerDelete (entry->getIndex());
|
||||
if (m_journal.trace) m_journal.trace <<
|
||||
@@ -155,17 +157,5 @@ OfferStream::step ()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
OfferStream::step_account (Account const& account)
|
||||
{
|
||||
while (step ())
|
||||
{
|
||||
if (tip ().account () != account)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,261 +23,626 @@
|
||||
namespace ripple {
|
||||
namespace core {
|
||||
|
||||
Taker::Taker (LedgerView& view, Account const& account,
|
||||
Amounts const& amount, Options const& options)
|
||||
: m_view (view)
|
||||
, m_account (account)
|
||||
, m_options (options)
|
||||
, m_quality (amount)
|
||||
, m_threshold (m_quality)
|
||||
, m_amount (amount)
|
||||
, m_remain (amount)
|
||||
Amount
|
||||
BasicTaker::Rate::divide (Amount const& amount) const
|
||||
{
|
||||
assert (m_remain.in > zero);
|
||||
assert (m_remain.out > zero);
|
||||
if (quality_ == QUALITY_ONE)
|
||||
return amount;
|
||||
|
||||
// If this is a passive order (tfPassive), this prevents
|
||||
// offers at the same quality level from being consumed.
|
||||
if (m_options.passive)
|
||||
++m_threshold;
|
||||
return ripple::divide (amount, rate_, amount.issue ());
|
||||
}
|
||||
|
||||
Amounts
|
||||
Taker::remaining_offer () const
|
||||
Amount
|
||||
BasicTaker::Rate::multiply (Amount const& amount) const
|
||||
{
|
||||
// If the taker is done, then there's no offer to place.
|
||||
if (done ())
|
||||
return Amounts (m_amount.in.zeroed(), m_amount.out.zeroed());
|
||||
if (quality_ == QUALITY_ONE)
|
||||
return amount;
|
||||
|
||||
// Avoid math altogether if we didn't cross.
|
||||
if (m_amount == m_remain)
|
||||
return m_amount;
|
||||
|
||||
if (m_options.sell)
|
||||
{
|
||||
assert (m_remain.in > zero);
|
||||
|
||||
// We scale the output based on the remaining input:
|
||||
return Amounts (m_remain.in, divRound (
|
||||
m_remain.in, m_quality.rate (), m_remain.out, true));
|
||||
}
|
||||
|
||||
assert (m_remain.out > zero);
|
||||
|
||||
// We scale the input based on the remaining output:
|
||||
return Amounts (mulRound (
|
||||
m_remain.out, m_quality.rate (), m_remain.in, true), m_remain.out);
|
||||
return ripple::multiply (amount, rate_, amount.issue ());
|
||||
}
|
||||
|
||||
/** Calculate the amount particular user could get through an offer.
|
||||
@param amount the maximum flow that is available to the taker.
|
||||
@param offer the offer to flow through.
|
||||
@param taker the person taking the offer.
|
||||
@return the maximum amount that can flow through this offer.
|
||||
*/
|
||||
Amounts
|
||||
Taker::flow (Amounts amount, Offer const& offer, Account const& taker)
|
||||
BasicTaker::BasicTaker (
|
||||
CrossType cross_type, Account const& account, Amounts const& amount,
|
||||
Quality const& quality, std::uint32_t flags, std::uint32_t rate_in,
|
||||
std::uint32_t rate_out)
|
||||
: 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)
|
||||
{
|
||||
// Limit taker's input by available funds less fees
|
||||
Amount const taker_funds (view ().accountFunds (
|
||||
taker, amount.in, fhZERO_IF_FROZEN));
|
||||
assert (remaining_.in > zero);
|
||||
assert (remaining_.out > zero);
|
||||
|
||||
// Get fee rate paid by taker
|
||||
std::uint32_t const taker_charge_rate (rippleTransferRate (view (),
|
||||
taker, offer.account (), amount.in.getIssuer()));
|
||||
assert (m_rate_in != 0);
|
||||
assert (m_rate_out != 0);
|
||||
|
||||
// Skip some math when there's no fee
|
||||
if (taker_charge_rate == QUALITY_ONE)
|
||||
{
|
||||
amount = offer.quality ().ceil_in (amount, taker_funds);
|
||||
}
|
||||
else
|
||||
{
|
||||
Amount const taker_charge (amountFromRate (taker_charge_rate));
|
||||
amount = offer.quality ().ceil_in (amount,
|
||||
divide (taker_funds, taker_charge));
|
||||
}
|
||||
// If we are dealing with a particular flavor, make sure that it's the
|
||||
// flavor we expect:
|
||||
assert (cross_type != CrossType::XrpToIou ||
|
||||
(isXRP (issue_in ()) && !isXRP (issue_out ())));
|
||||
|
||||
// Best flow the owner can get.
|
||||
// Start out assuming entire offer will flow.
|
||||
Amounts owner_amount (amount);
|
||||
assert (cross_type != CrossType::IouToXrp ||
|
||||
(!isXRP (issue_in ()) && isXRP (issue_out ())));
|
||||
|
||||
// Limit owner's output by available funds less fees
|
||||
Amount const owner_funds (view ().accountFunds (
|
||||
offer.account (), owner_amount.out, fhZERO_IF_FROZEN));
|
||||
// And make sure we're not crossing XRP for XRP
|
||||
assert (!isXRP (issue_in ()) || !isXRP (issue_out ()));
|
||||
|
||||
// Get fee rate paid by owner
|
||||
std::uint32_t const owner_charge_rate (rippleTransferRate (view (),
|
||||
offer.account (), taker, amount.out.getIssuer()));
|
||||
|
||||
if (owner_charge_rate == QUALITY_ONE)
|
||||
{
|
||||
// Skip some math when there's no fee
|
||||
owner_amount = offer.quality ().ceil_out (owner_amount, owner_funds);
|
||||
}
|
||||
else
|
||||
{
|
||||
Amount const owner_charge (amountFromRate (owner_charge_rate));
|
||||
owner_amount = offer.quality ().ceil_out (owner_amount,
|
||||
divide (owner_funds, owner_charge));
|
||||
}
|
||||
|
||||
// Calculate the amount that will flow through the offer
|
||||
// This does not include the fees.
|
||||
return (owner_amount.in < amount.in)
|
||||
? owner_amount
|
||||
: amount;
|
||||
// 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_;
|
||||
}
|
||||
|
||||
// Adjust an offer to indicate that we are consuming some (or all) of it.
|
||||
void
|
||||
Taker::consume (Offer const& offer, Amounts const& consumed) const
|
||||
BasicTaker::Rate
|
||||
BasicTaker::effective_rate (
|
||||
std::uint32_t rate, Issue const &issue,
|
||||
Account const& from, Account const& to)
|
||||
{
|
||||
Amounts const& remaining (offer.amount ());
|
||||
assert (rate != 0);
|
||||
|
||||
assert (remaining.in > zero && remaining.out > zero);
|
||||
assert (remaining.in >= consumed.in && remaining.out >= consumed.out);
|
||||
if (rate != QUALITY_ONE)
|
||||
{
|
||||
// We ignore the transfer if the sender is also the recipient since no
|
||||
// actual transfer takes place in that case. We also ignore if either
|
||||
// the sender or the receiver is the issuer.
|
||||
|
||||
offer.entry ()->setFieldAmount (sfTakerPays, remaining.in - consumed.in);
|
||||
offer.entry ()->setFieldAmount (sfTakerGets, remaining.out - consumed.out);
|
||||
if (from != to && from != issue.account && to != issue.account)
|
||||
return Rate (rate);
|
||||
}
|
||||
|
||||
view ().entryModify (offer.entry());
|
||||
|
||||
assert (offer.entry ()->getFieldAmount (sfTakerPays) >= zero);
|
||||
assert (offer.entry ()->getFieldAmount (sfTakerGets) >= zero);
|
||||
}
|
||||
|
||||
// Fill a direct offer.
|
||||
// @param offer the offer we are going to use.
|
||||
// @param amount the amount to flow through the offer.
|
||||
// @returns: tesSUCCESS if successful, or an error code otherwise.
|
||||
TER
|
||||
Taker::fill (Offer const& offer, Amounts const& amount)
|
||||
{
|
||||
consume (offer, amount);
|
||||
|
||||
// Pay the taker, then the owner
|
||||
TER result = view ().accountSend (offer.account(), account(), amount.out);
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
result = view ().accountSend (account(), offer.account(), amount.in);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Fill a bridged offer.
|
||||
// @param leg1 the first leg we are going to use.
|
||||
// @param amount1 the amount to flow through the first leg of the offer.
|
||||
// @param leg2 the second leg we are going to use.
|
||||
// @param amount2 the amount to flow through the second leg of the offer.
|
||||
// @return tesSUCCESS if successful, or an error code otherwise.
|
||||
TER
|
||||
Taker::fill (
|
||||
Offer const& leg1, Amounts const& amount1,
|
||||
Offer const& leg2, Amounts const& amount2)
|
||||
{
|
||||
assert (amount1.out == amount2.in);
|
||||
|
||||
consume (leg1, amount1);
|
||||
consume (leg2, amount2);
|
||||
|
||||
/* It is possible that m_account is the same as leg1.account, leg2.account
|
||||
* or both. This could happen when bridging over one's own offer. In that
|
||||
* case, accountSend won't actually do a send, which is what we want.
|
||||
*/
|
||||
TER result = view ().accountSend (m_account, leg1.account (), amount1.in);
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
result = view ().accountSend (leg1.account (), leg2.account (), amount1.out);
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
result = view ().accountSend (leg2.account (), m_account, amount2.out);
|
||||
|
||||
return result;
|
||||
return Rate (QUALITY_ONE);
|
||||
}
|
||||
|
||||
bool
|
||||
Taker::done () const
|
||||
BasicTaker::done () const
|
||||
{
|
||||
if (m_options.sell && (m_remain.in <= zero))
|
||||
{
|
||||
// Sell semantics: we consumed all the input currency
|
||||
// We are done if we have consumed all the input currency
|
||||
if (remaining_.in <= zero)
|
||||
return true;
|
||||
|
||||
// We are done if using buy semantics and we received the
|
||||
// desired amount of output currency
|
||||
if (!sell_ && (remaining_.out <= zero))
|
||||
return true;
|
||||
|
||||
// We are done if the taker is out of funds
|
||||
if (get_funds (account(), remaining_.in) <= zero)
|
||||
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_)
|
||||
{
|
||||
assert (remaining_.in > zero);
|
||||
|
||||
// We scale the output based on the remaining input:
|
||||
return Amounts (remaining_.in, divRound (
|
||||
remaining_.in, quality_.rate (), remaining_.out, true));
|
||||
}
|
||||
|
||||
if (!m_options.sell && (m_remain.out <= zero))
|
||||
assert (remaining_.out > zero);
|
||||
|
||||
// We scale the input based on the remaining output:
|
||||
return Amounts (mulRound (
|
||||
remaining_.out, quality_.rate (), remaining_.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
|
||||
Amount
|
||||
qual_div (Amount const& amount, Quality const& quality, Amount const& output)
|
||||
{
|
||||
auto result = divide (amount, quality.rate (), output.issue ());
|
||||
return std::min (result, output);
|
||||
}
|
||||
|
||||
static
|
||||
Amount
|
||||
qual_mul (Amount const& amount, Quality const& quality, Amount const& output)
|
||||
{
|
||||
auto result = multiply (amount, quality.rate (), output.issue ());
|
||||
return std::min (result, output);
|
||||
}
|
||||
|
||||
BasicTaker::Flow
|
||||
BasicTaker::flow_xrp_to_iou (
|
||||
Amounts const& order, Quality quality,
|
||||
Amount const& owner_funds, Amount const& taker_funds,
|
||||
Rate const& rate_out)
|
||||
{
|
||||
Flow f;
|
||||
f.order = order;
|
||||
f.issuers.out = rate_out.multiply (f.order.out);
|
||||
|
||||
// Clamp on owner balance
|
||||
if (owner_funds < f.issuers.out)
|
||||
{
|
||||
// Buy semantics: we received the desired amount of output currency
|
||||
return true;
|
||||
f.issuers.out = owner_funds;
|
||||
f.order.out = rate_out.divide (f.issuers.out);
|
||||
f.order.in = qual_mul (f.order.out, quality, f.order.in);
|
||||
}
|
||||
|
||||
// We are finished if the taker is out of funds
|
||||
return view().accountFunds (
|
||||
account(), m_remain.in, fhZERO_IF_FROZEN) <= zero;
|
||||
// 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 = rate_out.multiply (f.order.out);
|
||||
}
|
||||
|
||||
// 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 = rate_out.multiply (f.order.out);
|
||||
}
|
||||
|
||||
// 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 = rate_out.multiply (f.order.out);
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
BasicTaker::Flow
|
||||
BasicTaker::flow_iou_to_xrp (
|
||||
Amounts const& order, Quality quality,
|
||||
Amount const& owner_funds, Amount const& taker_funds,
|
||||
Rate const& rate_in)
|
||||
{
|
||||
Flow f;
|
||||
f.order = order;
|
||||
f.issuers.in = rate_in.multiply (f.order.in);
|
||||
|
||||
// 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 = rate_in.multiply (f.order.in);
|
||||
}
|
||||
|
||||
// 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 = rate_in.multiply (f.order.in);
|
||||
}
|
||||
}
|
||||
|
||||
// Clamp on the taker's input offer
|
||||
if (remaining_.in < f.order.in)
|
||||
{
|
||||
f.order.in = remaining_.in;
|
||||
f.issuers.in = rate_in.multiply (f.order.in);
|
||||
f.order.out = qual_div (f.order.in, quality, f.order.out);
|
||||
}
|
||||
|
||||
// Clamp on the taker's input balance
|
||||
if (taker_funds < f.issuers.in)
|
||||
{
|
||||
f.issuers.in = taker_funds;
|
||||
f.order.in = rate_in.divide (f.issuers.in);
|
||||
f.order.out = qual_div (f.order.in, quality, f.order.out);
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
BasicTaker::Flow
|
||||
BasicTaker::flow_iou_to_iou (
|
||||
Amounts const& order, Quality quality,
|
||||
Amount const& owner_funds, Amount const& taker_funds,
|
||||
Rate const& rate_in, Rate const& rate_out)
|
||||
{
|
||||
Flow f;
|
||||
f.order = order;
|
||||
f.issuers.in = rate_in.multiply (f.order.in);
|
||||
f.issuers.out = rate_out.multiply (f.order.out);
|
||||
|
||||
// Clamp on owner balance
|
||||
if (owner_funds < f.issuers.out)
|
||||
{
|
||||
f.issuers.out = owner_funds;
|
||||
f.order.out = rate_out.divide (f.issuers.out);
|
||||
f.order.in = qual_mul (f.order.out, quality, f.order.in);
|
||||
f.issuers.in = rate_in.multiply (f.order.in);
|
||||
}
|
||||
|
||||
// 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 = rate_out.multiply (f.order.out);
|
||||
f.issuers.in = rate_in.multiply (f.order.in);
|
||||
}
|
||||
|
||||
// Clamp on the taker's input balance and input offer
|
||||
if (remaining_.in < f.order.in)
|
||||
{
|
||||
f.order.in = remaining_.in;
|
||||
f.issuers.in = rate_in.multiply (f.order.in);
|
||||
f.order.out = qual_div (f.order.in, quality, f.order.out);
|
||||
f.issuers.out = rate_out.multiply (f.order.out);
|
||||
}
|
||||
|
||||
if (taker_funds < f.order.in)
|
||||
{
|
||||
f.issuers.in = taker_funds;
|
||||
f.order.in = rate_in.divide (f.issuers.in);
|
||||
f.order.out = qual_div (f.order.in, quality, f.order.out);
|
||||
f.issuers.out = rate_out.multiply (f.order.out);
|
||||
}
|
||||
|
||||
return f;
|
||||
}
|
||||
|
||||
// Calculates the direct flow through the specified offer
|
||||
BasicTaker::Flow
|
||||
BasicTaker::do_cross (Amounts offer, Quality quality, Account const& owner)
|
||||
{
|
||||
assert (!done ());
|
||||
|
||||
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;
|
||||
|
||||
assert (remaining_.in >= zero);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Calculates the bridged flow through the specified offers
|
||||
std::pair<BasicTaker::Flow, BasicTaker::Flow>
|
||||
BasicTaker::do_cross (
|
||||
Amounts offer1, Quality quality1, Account const& owner1,
|
||||
Amounts offer2, Quality quality2, Account const& owner2)
|
||||
{
|
||||
assert (!done ());
|
||||
|
||||
assert (!offer1.in.isNative ());
|
||||
assert (offer1.out.isNative ());
|
||||
assert (offer2.in.isNative ());
|
||||
assert (!offer2.out.isNative ());
|
||||
|
||||
// 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)
|
||||
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)
|
||||
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)
|
||||
xrp_funds = std::max (offer1.out, offer2.in);
|
||||
|
||||
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 = leg2_rate.multiply (flow2.order.out);
|
||||
}
|
||||
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 = leg1_rate.multiply (flow1.order.in);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
|
||||
std::uint32_t
|
||||
Taker::calculateRate (
|
||||
LedgerView& view, Account const& issuer, Account const& account)
|
||||
{
|
||||
return isXRP (issuer) || (account == issuer)
|
||||
? QUALITY_ONE
|
||||
: rippleTransferRate (view, issuer);
|
||||
}
|
||||
|
||||
Taker::Taker (CrossType cross_type, LedgerView& view, Account const& account,
|
||||
Amounts const& offer, std::uint32_t flags)
|
||||
: BasicTaker (cross_type, account, offer, Quality(offer), flags,
|
||||
calculateRate(view, offer.in.getIssuer(), account),
|
||||
calculateRate(view, offer.out.getIssuer(), account))
|
||||
, m_view (view)
|
||||
, xrp_flow_ (0)
|
||||
, direct_crossings_ (0)
|
||||
, bridge_crossings_ (0)
|
||||
{
|
||||
assert (issue_in () == offer.in.issue ());
|
||||
assert (issue_out () == offer.out.issue ());
|
||||
}
|
||||
|
||||
void
|
||||
Taker::consume_offer (Offer const& offer, Amounts const& order)
|
||||
{
|
||||
if (order.in < zero)
|
||||
throw std::logic_error ("flow with negative input.");
|
||||
|
||||
if (order.out < zero)
|
||||
throw std::logic_error ("flow with negative output.");
|
||||
|
||||
return offer.consume (m_view, order);
|
||||
}
|
||||
|
||||
Amount
|
||||
Taker::get_funds (Account const& account, Amount const& funds) const
|
||||
{
|
||||
return m_view.accountFunds (account, funds, fhZERO_IF_FROZEN);
|
||||
}
|
||||
|
||||
TER Taker::transfer_xrp (
|
||||
Account const& from,
|
||||
Account const& to,
|
||||
Amount const& amount)
|
||||
{
|
||||
if (!isXRP (amount))
|
||||
throw std::logic_error ("Using transfer_xrp with IOU");
|
||||
|
||||
if (from == to)
|
||||
return tesSUCCESS;
|
||||
|
||||
return m_view.transfer_xrp (from, to, amount);
|
||||
}
|
||||
|
||||
TER Taker::redeem_iou (
|
||||
Account const& account,
|
||||
Amount const& amount,
|
||||
Issue const& issue)
|
||||
{
|
||||
if (isXRP (amount))
|
||||
throw std::logic_error ("Using redeem_iou with XRP");
|
||||
|
||||
if (account == issue.account)
|
||||
return tesSUCCESS;
|
||||
|
||||
return m_view.redeem_iou (account, amount, issue);
|
||||
}
|
||||
|
||||
TER Taker::issue_iou (
|
||||
Account const& account,
|
||||
Amount const& amount,
|
||||
Issue const& issue)
|
||||
{
|
||||
if (isXRP (amount))
|
||||
throw std::logic_error ("Using issue_iou with XRP");
|
||||
|
||||
if (account == issue.account)
|
||||
return tesSUCCESS;
|
||||
|
||||
return m_view.issue_iou (account, amount, issue);
|
||||
}
|
||||
|
||||
// Performs funds transfers to fill the given offer and adjusts offer.
|
||||
TER
|
||||
Taker::fill (BasicTaker::Flow const& flow, Offer const& offer)
|
||||
{
|
||||
// adjust offer
|
||||
consume_offer (offer, flow.order);
|
||||
|
||||
TER result = tesSUCCESS;
|
||||
|
||||
if (cross_type () != CrossType::XrpToIou)
|
||||
{
|
||||
assert (!isXRP (flow.order.in));
|
||||
|
||||
if(result == tesSUCCESS)
|
||||
result = redeem_iou (account (), flow.issuers.in, flow.issuers.in.issue ());
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
result = issue_iou (offer.owner (), flow.order.in, flow.order.in.issue ());
|
||||
}
|
||||
else
|
||||
{
|
||||
assert (isXRP (flow.order.in));
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
result = transfer_xrp (account (), offer.owner (), flow.order.in);
|
||||
}
|
||||
|
||||
// Now send funds from the account whose offer we're taking
|
||||
if (cross_type () != CrossType::IouToXrp)
|
||||
{
|
||||
assert (!isXRP (flow.order.out));
|
||||
|
||||
if(result == tesSUCCESS)
|
||||
result = redeem_iou (offer.owner (), flow.issuers.out, flow.issuers.out.issue ());
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
result = issue_iou (account (), flow.order.out, flow.order.out.issue ());
|
||||
}
|
||||
else
|
||||
{
|
||||
assert (isXRP (flow.order.out));
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
result = transfer_xrp (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 const& leg1,
|
||||
BasicTaker::Flow const& flow2, Offer const& 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 = redeem_iou (account (), flow1.issuers.in, flow1.issuers.in.issue ());
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
result = issue_iou (leg1.owner (), flow1.order.in, flow1.order.in.issue ());
|
||||
}
|
||||
|
||||
// leg1 to leg2: bridging over XRP
|
||||
if (result == tesSUCCESS)
|
||||
result = transfer_xrp (leg1.owner (), leg2.owner (), flow1.order.out);
|
||||
|
||||
// leg2 to Taker: IOU
|
||||
if (leg2.owner () != account ())
|
||||
{
|
||||
if (result == tesSUCCESS)
|
||||
result = redeem_iou (leg2.owner (), flow2.issuers.out, flow2.issuers.out.issue ());
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
result = issue_iou (account (), flow2.order.out, flow2.order.out.issue ());
|
||||
}
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
{
|
||||
bridge_crossings_++;
|
||||
xrp_flow_ += flow1.order.out;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TER
|
||||
Taker::cross (Offer const& offer)
|
||||
{
|
||||
assert (!done ());
|
||||
// In direct crossings, at least one leg must not be XRP.
|
||||
if (isXRP (offer.amount ().in) && isXRP (offer.amount ().out))
|
||||
return tefINTERNAL;
|
||||
|
||||
/* Before we call flow we must set the limit right; for buy semantics we
|
||||
need to clamp the output. And we always want to clamp the input.
|
||||
*/
|
||||
Amounts limit (offer.amount());
|
||||
auto const amount = do_cross (
|
||||
offer.amount (), offer.quality (), offer.owner ());
|
||||
|
||||
if (! m_options.sell)
|
||||
limit = offer.quality ().ceil_out (limit, m_remain.out);
|
||||
limit = offer.quality().ceil_in (limit, m_remain.in);
|
||||
|
||||
assert (limit.in <= offer.amount().in);
|
||||
assert (limit.out <= offer.amount().out);
|
||||
assert (limit.in <= m_remain.in);
|
||||
|
||||
Amounts const amount (flow (limit, offer, account ()));
|
||||
|
||||
m_remain.out -= amount.out;
|
||||
m_remain.in -= amount.in;
|
||||
|
||||
assert (m_remain.in >= zero);
|
||||
return fill (offer, amount);
|
||||
return fill (amount, offer);
|
||||
}
|
||||
|
||||
TER
|
||||
Taker::cross (Offer const& leg1, Offer const& leg2)
|
||||
{
|
||||
assert (!done ());
|
||||
// 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;
|
||||
|
||||
assert (leg1.amount ().out.isNative ());
|
||||
assert (leg2.amount ().in.isNative ());
|
||||
auto ret = do_cross (
|
||||
leg1.amount (), leg1.quality (), leg1.owner (),
|
||||
leg2.amount (), leg2.quality (), leg2.owner ());
|
||||
|
||||
Amounts amount1 (leg1.amount());
|
||||
Amounts amount2 (leg2.amount());
|
||||
|
||||
if (m_options.sell)
|
||||
amount1 = leg1.quality().ceil_in (amount1, m_remain.in);
|
||||
else
|
||||
amount2 = leg2.quality().ceil_out (amount2, m_remain.out);
|
||||
|
||||
if (amount1.out <= amount2.in)
|
||||
amount2 = leg2.quality().ceil_in (amount2, amount1.out);
|
||||
else
|
||||
amount1 = leg1.quality().ceil_out (amount1, amount2.in);
|
||||
|
||||
assert (amount1.out == amount2.in);
|
||||
|
||||
// As written, flow can't handle a 3-party transfer, but this works for
|
||||
// us because the output of leg1 and the input leg2 are XRP.
|
||||
Amounts flow1 (flow (amount1, leg1, m_account));
|
||||
|
||||
amount2 = leg2.quality().ceil_in (amount2, flow1.out);
|
||||
|
||||
Amounts flow2 (flow (amount2, leg2, m_account));
|
||||
|
||||
m_remain.out -= amount2.out;
|
||||
m_remain.in -= amount1.in;
|
||||
|
||||
return fill (leg1, flow1, leg2, flow2);
|
||||
return fill (ret.first, leg1, ret.second, leg2);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
373
src/ripple/app/book/tests/Taker.test.cpp
Normal file
373
src/ripple/app/book/tests/Taker.test.cpp
Normal file
@@ -0,0 +1,373 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/book/Taker.h>
|
||||
#include <ripple/app/book/Types.h>
|
||||
|
||||
#include <beast/unit_test/suite.h>
|
||||
#include <beast/module/core/text/LexicalCast.h>
|
||||
|
||||
#include <beast/cxx14/type_traits.h>
|
||||
|
||||
|
||||
namespace ripple {
|
||||
namespace core {
|
||||
|
||||
class Taker_test : public beast::unit_test::suite
|
||||
{
|
||||
static bool const Buy = false;
|
||||
static bool const Sell = true;
|
||||
|
||||
class TestTaker
|
||||
: public BasicTaker
|
||||
{
|
||||
Amount funds_;
|
||||
Amount cross_funds;
|
||||
|
||||
public:
|
||||
TestTaker (
|
||||
CrossType cross_type, Amounts const& amount, Quality const& quality,
|
||||
Amount const& funds, std::uint32_t flags, std::uint32_t rate_in,
|
||||
std::uint32_t rate_out)
|
||||
: BasicTaker (cross_type, Account(0x4701), amount, quality, flags, rate_in, rate_out)
|
||||
, funds_ (funds)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
set_funds (Amount const& funds)
|
||||
{
|
||||
cross_funds = funds;
|
||||
}
|
||||
|
||||
Amount
|
||||
get_funds (Account const& owner, Amount const& funds) const
|
||||
{
|
||||
if (owner == account ())
|
||||
return funds_;
|
||||
|
||||
return cross_funds;
|
||||
}
|
||||
|
||||
Amounts
|
||||
cross (Amounts offer, Quality quality)
|
||||
{
|
||||
if (reject (quality))
|
||||
return Amounts (offer.in.zeroed (), offer.out.zeroed ());
|
||||
|
||||
// we need to emulate "unfunded offers" behavior
|
||||
if (get_funds (Account (0x4702), offer.out) == zero)
|
||||
return Amounts (offer.in.zeroed (), offer.out.zeroed ());
|
||||
|
||||
if (done ())
|
||||
return Amounts (offer.in.zeroed (), offer.out.zeroed ());
|
||||
|
||||
auto result = do_cross (offer, quality, Account (0x4702));
|
||||
|
||||
funds_ -= result.order.in;
|
||||
|
||||
return result.order;
|
||||
}
|
||||
|
||||
std::pair<Amounts, Amounts>
|
||||
cross (Amounts offer1, Quality quality1, Amounts offer2, Quality quality2)
|
||||
{
|
||||
/* check if composed quality should be rejected */
|
||||
Quality const quality (core::composed_quality (quality1, quality2));
|
||||
|
||||
if (reject (quality))
|
||||
return std::make_pair(
|
||||
Amounts { offer1.in.zeroed (), offer1.out.zeroed () },
|
||||
Amounts { offer2.in.zeroed (), offer2.out.zeroed () });
|
||||
|
||||
if (done ())
|
||||
return std::make_pair(
|
||||
Amounts { offer1.in.zeroed (), offer1.out.zeroed () },
|
||||
Amounts { offer2.in.zeroed (), offer2.out.zeroed () });
|
||||
|
||||
auto result = do_cross (
|
||||
offer1, quality1, Account (0x4703),
|
||||
offer2, quality2, Account (0x4704));
|
||||
|
||||
return std::make_pair (result.first.order, result.second.order);
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
Issue const& usd () const
|
||||
{
|
||||
static Issue const issue (
|
||||
Currency (0x5553440000000000), Account (0x4985601));
|
||||
return issue;
|
||||
}
|
||||
|
||||
Issue const& eur () const
|
||||
{
|
||||
static Issue const issue (
|
||||
Currency (0x4555520000000000), Account (0x4985602));
|
||||
return issue;
|
||||
}
|
||||
|
||||
Issue const& xrp () const
|
||||
{
|
||||
static Issue const issue (
|
||||
xrpCurrency (), xrpAccount ());
|
||||
return issue;
|
||||
}
|
||||
|
||||
Amount parse_amount (std::string const& amount, Issue const& issue)
|
||||
{
|
||||
Amount result (issue);
|
||||
expect (result.setValue (amount), amount);
|
||||
return result;
|
||||
}
|
||||
|
||||
Amounts parse_amounts (
|
||||
std::string const& amount_in, Issue const& issue_in,
|
||||
std::string const& amount_out, Issue const& issue_out)
|
||||
{
|
||||
Amount const in (parse_amount (amount_in, issue_in));
|
||||
Amount const out (parse_amount (amount_out, issue_out));
|
||||
|
||||
return { in, out };
|
||||
}
|
||||
|
||||
struct cross_attempt_offer
|
||||
{
|
||||
cross_attempt_offer (std::string const& in_,
|
||||
std::string const &out_)
|
||||
: in (in_)
|
||||
, out (out_)
|
||||
{
|
||||
}
|
||||
|
||||
std::string in;
|
||||
std::string out;
|
||||
};
|
||||
|
||||
private:
|
||||
std::string
|
||||
format_amount (STAmount const& amount)
|
||||
{
|
||||
std::string txt = amount.getText ();
|
||||
txt += "/";
|
||||
txt += amount.getHumanCurrency ();
|
||||
return txt;
|
||||
}
|
||||
|
||||
void
|
||||
attempt (
|
||||
bool sell,
|
||||
std::string name,
|
||||
Quality taker_quality,
|
||||
cross_attempt_offer const offer,
|
||||
std::string const funds,
|
||||
Quality cross_quality,
|
||||
cross_attempt_offer const cross,
|
||||
std::string const cross_funds,
|
||||
cross_attempt_offer const flow,
|
||||
Issue const& issue_in,
|
||||
Issue const& issue_out,
|
||||
std::uint32_t rate_in = QUALITY_ONE,
|
||||
std::uint32_t rate_out = QUALITY_ONE)
|
||||
{
|
||||
Amounts taker_offer (parse_amounts (
|
||||
offer.in, issue_in,
|
||||
offer.out, issue_out));
|
||||
|
||||
Amounts cross_offer (parse_amounts (
|
||||
cross.in, issue_in,
|
||||
cross.out, issue_out));
|
||||
|
||||
CrossType cross_type;
|
||||
|
||||
if (isXRP (issue_out))
|
||||
cross_type = core::CrossType::IouToXrp;
|
||||
else if (isXRP (issue_in))
|
||||
cross_type = core::CrossType::XrpToIou;
|
||||
else
|
||||
cross_type = CrossType::IouToIou;
|
||||
|
||||
// FIXME: We are always invoking the IOU-to-IOU taker. We should select
|
||||
// the correct type dynamically.
|
||||
TestTaker taker (cross_type, taker_offer, taker_quality,
|
||||
parse_amount (funds, issue_in), sell ? tfSell : 0,
|
||||
rate_in, rate_out);
|
||||
|
||||
taker.set_funds (parse_amount (cross_funds, issue_out));
|
||||
|
||||
auto result = taker.cross (cross_offer, cross_quality);
|
||||
|
||||
Amounts const expected (parse_amounts (
|
||||
flow.in, issue_in,
|
||||
flow.out, issue_out));
|
||||
|
||||
expect (expected == result, name + (sell ? " (s)" : " (b)"));
|
||||
|
||||
if (expected != result)
|
||||
{
|
||||
log <<
|
||||
"Expected: " << format_amount (expected.in) <<
|
||||
" : " << format_amount (expected.out);
|
||||
log <<
|
||||
" Actual: " << format_amount (result.in) <<
|
||||
" : " << format_amount (result.out);
|
||||
}
|
||||
|
||||
assert (expected == result);
|
||||
}
|
||||
|
||||
Quality get_quality(std::string in, std::string out)
|
||||
{
|
||||
return Quality (parse_amounts (in, xrp(), out, xrp ()));
|
||||
}
|
||||
|
||||
public:
|
||||
Taker_test ()
|
||||
{
|
||||
}
|
||||
|
||||
// Notation for clamp scenario descriptions:
|
||||
//
|
||||
// IN:OUT (with the last in the list being limiting factor)
|
||||
// N = Nothing
|
||||
// T = Taker Offer Balance
|
||||
// A = Taker Account Balance
|
||||
// B = Owner Account Balance
|
||||
//
|
||||
// (s) = sell semantics: taker wants unlimited output
|
||||
// (b) = buy semantics: taker wants a limited amount out
|
||||
|
||||
// NIKB TODO: Augment TestTaker so currencies and rates can be specified
|
||||
// once without need for repetition.
|
||||
void
|
||||
test_xrp_to_iou ()
|
||||
{
|
||||
testcase ("XRP Quantization: input");
|
||||
|
||||
Quality q1 = get_quality ("1", "1");
|
||||
|
||||
// TAKER OWNER
|
||||
// QUAL OFFER FUNDS QUAL OFFER FUNDS EXPECTED
|
||||
// XRP USD
|
||||
attempt (Sell, "N:N", q1, { "2", "2" }, "2", q1, { "2", "2" }, "2", { "2", "2" }, xrp(), usd());
|
||||
attempt (Sell, "N:B", q1, { "2", "2" }, "2", q1, { "2", "2" }, "1.8", { "1", "1.8" }, xrp(), usd());
|
||||
attempt (Buy, "N:T", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
|
||||
attempt (Buy, "N:BT", q1, { "1", "1" }, "2", q1, { "2", "2" }, "1.8", { "1", "1" }, xrp(), usd());
|
||||
attempt (Buy, "N:TB", q1, { "1", "1" }, "2", q1, { "2", "2" }, "0.8", { "0", "0.8" }, xrp(), usd());
|
||||
|
||||
attempt (Sell, "T:N", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
|
||||
attempt (Sell, "T:B", q1, { "1", "1" }, "2", q1, { "2", "2" }, "1.8", { "1", "1.8" }, xrp(), usd());
|
||||
attempt (Buy, "T:T", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
|
||||
attempt (Buy, "T:BT", q1, { "1", "1" }, "2", q1, { "2", "2" }, "1.8", { "1", "1" }, xrp(), usd());
|
||||
attempt (Buy, "T:TB", q1, { "1", "1" }, "2", q1, { "2", "2" }, "0.8", { "0", "0.8" }, xrp(), usd());
|
||||
|
||||
attempt (Sell, "A:N", q1, { "2", "2" }, "1", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
|
||||
attempt (Sell, "A:B", q1, { "2", "2" }, "1", q1, { "2", "2" }, "1.8", { "1", "1.8" }, xrp(), usd());
|
||||
attempt (Buy, "A:T", q1, { "2", "2" }, "1", q1, { "3", "3" }, "3", { "1", "1" }, xrp(), usd());
|
||||
attempt (Buy, "A:BT", q1, { "2", "2" }, "1", q1, { "3", "3" }, "2.4", { "1", "1" }, xrp(), usd());
|
||||
attempt (Buy, "A:TB", q1, { "2", "2" }, "1", q1, { "3", "3" }, "0.8", { "0", "0.8" }, xrp(), usd());
|
||||
|
||||
attempt (Sell, "TA:N", q1, { "2", "2" }, "1", q1, { "2", "2" }, "2", { "1", "1" }, xrp(), usd());
|
||||
attempt (Sell, "TA:B", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
|
||||
attempt (Buy, "TA:T", q1, { "2", "2" }, "1", q1, { "3", "3" }, "3", { "1", "1" }, xrp(), usd());
|
||||
attempt (Buy, "TA:BT", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
|
||||
attempt (Buy, "TA:TB", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
|
||||
|
||||
attempt (Sell, "AT:N", q1, { "2", "2" }, "1", q1, { "3", "3" }, "3", { "1", "1" }, xrp(), usd());
|
||||
attempt (Sell, "AT:B", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
|
||||
attempt (Buy, "AT:T", q1, { "2", "2" }, "1", q1, { "3", "3" }, "3", { "1", "1" }, xrp(), usd());
|
||||
attempt (Buy, "AT:BT", q1, { "2", "2" }, "1", q1, { "3", "3" }, "1.8", { "1", "1.8" }, xrp(), usd());
|
||||
attempt (Buy, "AT:TB", q1, { "2", "2" }, "1", q1, { "3", "3" }, "0.8", { "0", "0.8" }, xrp(), usd());
|
||||
}
|
||||
|
||||
void
|
||||
test_iou_to_xrp ()
|
||||
{
|
||||
testcase ("XRP Quantization: output");
|
||||
|
||||
Quality q1 = get_quality ("1", "1");
|
||||
|
||||
// TAKER OWNER
|
||||
// QUAL OFFER FUNDS QUAL OFFER FUNDS EXPECTED
|
||||
// USD XRP
|
||||
attempt (Sell, "N:N", q1, { "3", "3" }, "3", q1, { "3", "3" }, "3", { "3", "3" }, usd(), xrp());
|
||||
attempt (Sell, "N:B", q1, { "3", "3" }, "3", q1, { "3", "3" }, "2", { "2", "2" }, usd(), xrp());
|
||||
attempt (Buy, "N:T", q1, { "3", "3" }, "2.5", q1, { "5", "5" }, "5", { "2.5", "2" }, usd(), xrp());
|
||||
attempt (Buy, "N:BT", q1, { "3", "3" }, "1.5", q1, { "5", "5" }, "4", { "1.5", "1" }, usd(), xrp());
|
||||
attempt (Buy, "N:TB", q1, { "3", "3" }, "2.2", q1, { "5", "5" }, "1", { "1", "1" }, usd(), xrp());
|
||||
|
||||
attempt (Sell, "T:N", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, usd(), xrp());
|
||||
attempt (Sell, "T:B", q1, { "2", "2" }, "2", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
|
||||
attempt (Buy, "T:T", q1, { "1", "1" }, "2", q1, { "2", "2" }, "2", { "1", "1" }, usd(), xrp());
|
||||
attempt (Buy, "T:BT", q1, { "1", "1" }, "2", q1, { "3", "3" }, "2", { "1", "1" }, usd(), xrp());
|
||||
attempt (Buy, "T:TB", q1, { "2", "2" }, "2", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
|
||||
|
||||
attempt (Sell, "A:N", q1, { "2", "2" }, "1.5", q1, { "2", "2" }, "2", { "1.5", "1" }, usd(), xrp());
|
||||
attempt (Sell, "A:B", q1, { "2", "2" }, "1.8", q1, { "3", "3" }, "2", { "1.8", "1" }, usd(), xrp());
|
||||
attempt (Buy, "A:T", q1, { "2", "2" }, "1.2", q1, { "3", "3" }, "3", { "1.2", "1" }, usd(), xrp());
|
||||
attempt (Buy, "A:BT", q1, { "2", "2" }, "1.5", q1, { "4", "4" }, "3", { "1.5", "1" }, usd(), xrp());
|
||||
attempt (Buy, "A:TB", q1, { "2", "2" }, "1.5", q1, { "4", "4" }, "1", { "1", "1" }, usd(), xrp());
|
||||
|
||||
attempt (Sell, "TA:N", q1, { "2", "2" }, "1.5", q1, { "2", "2" }, "2", { "1.5", "1" }, usd(), xrp());
|
||||
attempt (Sell, "TA:B", q1, { "2", "2" }, "1.5", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
|
||||
attempt (Buy, "TA:T", q1, { "2", "2" }, "1.5", q1, { "3", "3" }, "3", { "1.5", "1" }, usd(), xrp());
|
||||
attempt (Buy, "TA:BT", q1, { "2", "2" }, "1.8", q1, { "4", "4" }, "3", { "1.8", "1" }, usd(), xrp());
|
||||
attempt (Buy, "TA:TB", q1, { "2", "2" }, "1.2", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
|
||||
|
||||
attempt (Sell, "AT:N", q1, { "2", "2" }, "2.5", q1, { "4", "4" }, "4", { "2", "2" }, usd(), xrp());
|
||||
attempt (Sell, "AT:B", q1, { "2", "2" }, "2.5", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
|
||||
attempt (Buy, "AT:T", q1, { "2", "2" }, "2.5", q1, { "3", "3" }, "3", { "2", "2" }, usd(), xrp());
|
||||
attempt (Buy, "AT:BT", q1, { "2", "2" }, "2.5", q1, { "4", "4" }, "3", { "2", "2" }, usd(), xrp());
|
||||
attempt (Buy, "AT:TB", q1, { "2", "2" }, "2.5", q1, { "3", "3" }, "1", { "1", "1" }, usd(), xrp());
|
||||
}
|
||||
|
||||
void
|
||||
test_iou_to_iou ()
|
||||
{
|
||||
testcase ("IOU to IOU");
|
||||
|
||||
Quality q1 = get_quality ("1", "1");
|
||||
|
||||
// Highly exaggerated 50% transfer rate for the input and output:
|
||||
std::uint32_t rate = QUALITY_ONE + (QUALITY_ONE / 2);
|
||||
|
||||
// TAKER OWNER
|
||||
// QUAL OFFER FUNDS QUAL OFFER FUNDS EXPECTED
|
||||
// EUR USD
|
||||
attempt (Sell, "N:N", q1, { "2", "2" }, "10", q1, { "2", "2" }, "10", { "2", "2" }, eur(), usd(), rate, rate);
|
||||
attempt (Sell, "N:B", q1, { "4", "4" }, "10", q1, { "4", "4" }, "4", { "2.666666666666666", "2.666666666666666" }, eur(), usd(), rate, rate);
|
||||
attempt (Buy, "N:T", q1, { "1", "1" }, "10", q1, { "2", "2" }, "10", { "1", "1" }, eur(), usd(), rate, rate);
|
||||
attempt (Buy, "N:BT", q1, { "2", "2" }, "10", q1, { "6", "6" }, "5", { "2", "2" }, eur(), usd(), rate, rate);
|
||||
attempt (Buy, "N:TB", q1, { "2", "2" }, "2", q1, { "6", "6" }, "1", { "0.6666666666666667", "0.6666666666666667" }, eur(), usd(), rate, rate);
|
||||
}
|
||||
|
||||
void
|
||||
run()
|
||||
{
|
||||
test_xrp_to_iou ();
|
||||
test_iou_to_xrp ();
|
||||
test_iou_to_iou ();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Taker,core,ripple);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1587,7 +1587,7 @@ TER LedgerEntrySet::rippleSend (
|
||||
|
||||
if (QUALITY_ONE != transit_rate)
|
||||
{
|
||||
saActual = multiply (saActual, STAmount::saFromRate (transit_rate),
|
||||
saActual = multiply (saActual, amountFromRate (transit_rate),
|
||||
saActual.issue ());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +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_TX_CANCELTICKET_H_INCLUDED
|
||||
#define RIPPLE_TX_CANCELTICKET_H_INCLUDED
|
||||
|
||||
namespace ripple {
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,83 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_APP_CREATEOFFER_H_INCLUDED
|
||||
#define RIPPLE_APP_CREATEOFFER_H_INCLUDED
|
||||
|
||||
#include <ripple/app/book/OfferStream.h>
|
||||
#include <ripple/app/book/Taker.h>
|
||||
#include <ripple/app/book/Types.h>
|
||||
#include <ripple/app/book/Amounts.h>
|
||||
#include <ripple/app/transactors/Transactor.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/json/to_string.h>
|
||||
#include <beast/cxx14/memory.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class CreateOffer
|
||||
: public Transactor
|
||||
{
|
||||
private:
|
||||
// What kind of offer we are placing
|
||||
#if RIPPLE_ENABLE_AUTOBRIDGING
|
||||
bool autobridging_;
|
||||
#endif
|
||||
|
||||
// Determine if we are authorized to hold the asset we want to get
|
||||
TER
|
||||
checkAcceptAsset(IssueRef issue) const;
|
||||
|
||||
/* Fill offer as much as possible by consuming offers already on the books.
|
||||
We adjusts account balances and charges fees on top to taker.
|
||||
|
||||
@param taker_amount.in How much the taker offers
|
||||
@param taker_amount.out How much the taker wants
|
||||
|
||||
@return result.first crossing operation success/failure indicator.
|
||||
result.second amount of offer left unfilled - only meaningful
|
||||
if result.first is tesSUCCESS.
|
||||
*/
|
||||
std::pair<TER, core::Amounts>
|
||||
crossOffersBridged (core::LedgerView& view,
|
||||
core::Amounts const& taker_amount);
|
||||
|
||||
std::pair<TER, core::Amounts>
|
||||
crossOffersDirect (core::LedgerView& view,
|
||||
core::Amounts const& taker_amount);
|
||||
|
||||
std::pair<TER, core::Amounts>
|
||||
crossOffers (core::LedgerView& view,
|
||||
core::Amounts const& taker_amount);
|
||||
|
||||
public:
|
||||
CreateOffer (bool autobridging, STTx const& txn,
|
||||
TransactionEngineParams params, TransactionEngine* engine);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
TER
|
||||
transact_CreateOffer (STTx const& txn,
|
||||
TransactionEngineParams params, TransactionEngine* engine);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,197 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/app/book/OfferStream.h>
|
||||
#include <ripple/app/book/Taker.h>
|
||||
#include <ripple/app/book/Quality.h>
|
||||
#include <ripple/app/transactors/CreateOffer.h>
|
||||
#include <beast/streams/debug_ostream.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
std::pair<TER, core::Amounts>
|
||||
CreateOffer::crossOffersBridged (
|
||||
core::LedgerView& view,
|
||||
core::Amounts const& taker_amount)
|
||||
{
|
||||
assert (!taker_amount.in.isNative () && !taker_amount.out.isNative ());
|
||||
|
||||
if (taker_amount.in.isNative () || taker_amount.out.isNative ())
|
||||
return std::make_pair (tefINTERNAL, core::Amounts ());
|
||||
|
||||
core::Clock::time_point const when (
|
||||
mEngine->getLedger ()->getParentCloseTimeNC ());
|
||||
|
||||
core::Taker::Options const options (mTxn.getFlags());
|
||||
|
||||
core::LedgerView view_cancel (view.duplicate());
|
||||
|
||||
auto& asset_in = taker_amount.in.issue();
|
||||
auto& asset_out = taker_amount.out.issue();
|
||||
|
||||
core::OfferStream offers_direct (view, view_cancel,
|
||||
Book (asset_in, asset_out), when, m_journal);
|
||||
|
||||
core::OfferStream offers_leg1 (view, view_cancel,
|
||||
Book (asset_in, xrpIssue ()), when, m_journal);
|
||||
|
||||
core::OfferStream offers_leg2 (view, view_cancel,
|
||||
Book (xrpIssue (), asset_out), when, m_journal);
|
||||
|
||||
core::Taker taker (view, mTxnAccountID, taker_amount, options);
|
||||
|
||||
if (m_journal.debug) m_journal.debug <<
|
||||
"process_order: " <<
|
||||
(options.sell? "sell" : "buy") << " " <<
|
||||
(options.passive? "passive" : "") << std::endl <<
|
||||
" taker: " << taker.account() << std::endl <<
|
||||
" balances: " <<
|
||||
view.accountFunds (taker.account(), taker_amount.in, fhIGNORE_FREEZE)
|
||||
<< ", " <<
|
||||
view.accountFunds (taker.account(), taker_amount.out, fhIGNORE_FREEZE);
|
||||
|
||||
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_bridged (offers_leg1.step () && offers_leg2.step ());
|
||||
bool have_direct (offers_direct.step_account (taker.account ()));
|
||||
bool place_order (true);
|
||||
|
||||
while (have_direct || have_bridged)
|
||||
{
|
||||
core::Quality quality;
|
||||
bool use_direct;
|
||||
bool leg1_consumed(false);
|
||||
bool leg2_consumed(false);
|
||||
bool direct_consumed(false);
|
||||
|
||||
// Logic:
|
||||
// We calculate the qualities of any direct and bridged offers at the
|
||||
// tip of the order book, and choose the best one of the two.
|
||||
|
||||
if (have_direct)
|
||||
{
|
||||
core::Quality const direct_quality (offers_direct.tip ().quality ());
|
||||
|
||||
if (have_bridged)
|
||||
{
|
||||
core::Quality const bridged_quality (core::composed_quality (
|
||||
offers_leg1.tip ().quality (),
|
||||
offers_leg2.tip ().quality ()));
|
||||
|
||||
if (bridged_quality < direct_quality)
|
||||
{
|
||||
use_direct = true;
|
||||
quality = direct_quality;
|
||||
}
|
||||
else
|
||||
{
|
||||
use_direct = false;
|
||||
quality = bridged_quality;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
use_direct = true;
|
||||
quality = offers_direct.tip ().quality ();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
use_direct = false;
|
||||
quality = core::composed_quality (
|
||||
offers_leg1.tip ().quality (),
|
||||
offers_leg2.tip ().quality ());
|
||||
}
|
||||
|
||||
// We are always looking at the best quality available, so if we reject
|
||||
// that, we know that we are done.
|
||||
if (taker.reject(quality))
|
||||
break;
|
||||
|
||||
if (use_direct)
|
||||
{
|
||||
if (m_journal.debug) m_journal.debug << "Direct:" << std::endl <<
|
||||
" Offer: " << offers_direct.tip () << std::endl <<
|
||||
" " << offers_direct.tip ().amount().in <<
|
||||
" : " << offers_direct.tip ().amount ().out;
|
||||
|
||||
cross_result = taker.cross(offers_direct.tip ());
|
||||
|
||||
if (offers_direct.tip ().fully_consumed ())
|
||||
{
|
||||
direct_consumed = true;
|
||||
have_direct = offers_direct.step_account (taker.account());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_journal.debug) m_journal.debug << "Bridge:" << std::endl <<
|
||||
" Offer1: " << offers_leg1.tip () << std::endl <<
|
||||
" " << offers_leg1.tip ().amount().in <<
|
||||
" : " << offers_leg1.tip ().amount ().out << std::endl <<
|
||||
" Offer2: " << offers_leg2.tip () << std::endl <<
|
||||
" " << offers_leg2.tip ().amount ().in <<
|
||||
" : " << offers_leg2.tip ().amount ().out;
|
||||
|
||||
cross_result = taker.cross(offers_leg1.tip (), offers_leg2.tip ());
|
||||
|
||||
if (offers_leg1.tip ().fully_consumed ())
|
||||
{
|
||||
leg1_consumed = true;
|
||||
have_bridged = offers_leg1.step ();
|
||||
}
|
||||
if (have_bridged && offers_leg2.tip ().fully_consumed ())
|
||||
{
|
||||
leg2_consumed = true;
|
||||
have_bridged = offers_leg2.step ();
|
||||
}
|
||||
}
|
||||
|
||||
if (cross_result != tesSUCCESS)
|
||||
{
|
||||
cross_result = tecFAILED_PROCESSING;
|
||||
break;
|
||||
}
|
||||
|
||||
if (taker.done())
|
||||
{
|
||||
m_journal.debug << "The taker reports he's done during crossing!";
|
||||
place_order = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Postcondition: If we aren't done, then we *must* have consumed at
|
||||
// least one offer fully.
|
||||
assert (direct_consumed || leg1_consumed || leg2_consumed);
|
||||
|
||||
if (!direct_consumed && !leg1_consumed && !leg2_consumed)
|
||||
{
|
||||
cross_result = tefINTERNAL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(cross_result, taker.remaining_offer ());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2012, 2013 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/app/book/OfferStream.h>
|
||||
#include <ripple/app/book/Taker.h>
|
||||
#include <ripple/app/transactors/CreateOffer.h>
|
||||
#include <beast/streams/debug_ostream.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
std::pair<TER, core::Amounts>
|
||||
CreateOffer::crossOffersDirect (
|
||||
core::LedgerView& view,
|
||||
core::Amounts const& taker_amount)
|
||||
{
|
||||
core::Taker::Options const options (mTxn.getFlags());
|
||||
|
||||
core::Clock::time_point const when (
|
||||
mEngine->getLedger ()->getParentCloseTimeNC ());
|
||||
|
||||
core::LedgerView view_cancel (view.duplicate());
|
||||
core::OfferStream offers (
|
||||
view, view_cancel,
|
||||
Book (taker_amount.in.issue(), taker_amount.out.issue()),
|
||||
when, m_journal);
|
||||
core::Taker taker (offers.view(), mTxnAccountID, taker_amount, options);
|
||||
|
||||
TER cross_result (tesSUCCESS);
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Modifying the order or logic of these
|
||||
// operations causes a protocol breaking change.
|
||||
|
||||
// Checks which remove offers are performed early so we
|
||||
// can reduce the size of the order book as much as possible
|
||||
// before terminating the loop.
|
||||
|
||||
if (taker.done())
|
||||
{
|
||||
m_journal.debug << "The taker reports he's done during crossing!";
|
||||
break;
|
||||
}
|
||||
|
||||
if (! offers.step ())
|
||||
{
|
||||
// Place the order since there are no
|
||||
// more offers and the order has a balance.
|
||||
m_journal.debug << "No more offers to consider during crossing!";
|
||||
break;
|
||||
}
|
||||
|
||||
auto const& offer (offers.tip());
|
||||
|
||||
if (taker.reject (offer.quality()))
|
||||
{
|
||||
// Place the order since there are no more offers
|
||||
// at the desired quality, and the order has a balance.
|
||||
break;
|
||||
}
|
||||
|
||||
if (offer.account() == taker.account())
|
||||
{
|
||||
// Skip offer from self. The offer will be considered expired and
|
||||
// will get deleted.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_journal.debug) m_journal.debug <<
|
||||
" Offer: " << offer.entry()->getIndex() << std::endl <<
|
||||
" " << offer.amount().in << " : " << offer.amount().out;
|
||||
|
||||
cross_result = taker.cross (offer);
|
||||
|
||||
if (cross_result != tesSUCCESS)
|
||||
{
|
||||
cross_result = tecFAILED_PROCESSING;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(cross_result, taker.remaining_offer ());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,27 +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_TX_CREATETICKET_H_INCLUDED
|
||||
#define RIPPLE_TX_CREATETICKET_H_INCLUDED
|
||||
|
||||
namespace ripple {
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -155,57 +155,52 @@ TER Transactor::payFee ()
|
||||
|
||||
TER Transactor::checkSig ()
|
||||
{
|
||||
// Consistency: Check signature
|
||||
// Verify the transaction's signing public key is the key authorized for signing.
|
||||
if (mSigningPubKey.getAccountID () == mTxnAccountID)
|
||||
{
|
||||
// Authorized to continue.
|
||||
mSigMaster = true;
|
||||
if (mTxnAccount->isFlag(lsfDisableMaster))
|
||||
return tefMASTER_DISABLED;
|
||||
}
|
||||
else if (mHasAuthKey && mSigningPubKey.getAccountID () == mTxnAccount->getFieldAccount160 (sfRegularKey))
|
||||
{
|
||||
// Authorized to continue.
|
||||
}
|
||||
else if (mHasAuthKey)
|
||||
{
|
||||
m_journal.trace << "applyTransaction: Delay: Not authorized to use account.";
|
||||
return tefBAD_AUTH;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_journal.trace << "applyTransaction: Invalid: Not authorized to use account.";
|
||||
// Consistency: Check signature and verify the transaction's signing public
|
||||
// key is the key authorized for signing.
|
||||
|
||||
auto const signing_account = mSigningPubKey.getAccountID ();
|
||||
|
||||
if (signing_account == mTxnAccountID)
|
||||
{
|
||||
if (mTxnAccount->isFlag(lsfDisableMaster))
|
||||
return tefMASTER_DISABLED;
|
||||
|
||||
mSigMaster = true;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
if (!mHasAuthKey)
|
||||
{
|
||||
m_journal.trace << "Invalid: Not authorized to use account.";
|
||||
return temBAD_AUTH_MASTER;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
if (signing_account == mTxnAccount->getFieldAccount160 (sfRegularKey))
|
||||
return tesSUCCESS;
|
||||
|
||||
m_journal.trace << "Delay: Not authorized to use account.";
|
||||
return tefBAD_AUTH;
|
||||
}
|
||||
|
||||
TER Transactor::checkSeq ()
|
||||
{
|
||||
std::uint32_t t_seq = mTxn.getSequence ();
|
||||
std::uint32_t a_seq = mTxnAccount->getFieldU32 (sfSequence);
|
||||
|
||||
m_journal.trace << "Aseq=" << a_seq << ", Tseq=" << t_seq;
|
||||
std::uint32_t const t_seq = mTxn.getSequence ();
|
||||
std::uint32_t const a_seq = mTxnAccount->getFieldU32 (sfSequence);
|
||||
|
||||
if (t_seq != a_seq)
|
||||
{
|
||||
if (a_seq < t_seq)
|
||||
{
|
||||
m_journal.trace << "apply: transaction has future sequence number";
|
||||
|
||||
m_journal.trace << "Transaction has future sequence number " <<
|
||||
"a_seq=" << a_seq << " t_seq=" << t_seq;
|
||||
return terPRE_SEQ;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mEngine->getLedger ()->hasTransaction (mTxn.getTransactionID ()))
|
||||
return tefALREADY;
|
||||
}
|
||||
|
||||
m_journal.warning << "apply: transaction has past sequence number";
|
||||
if (mEngine->getLedger ()->hasTransaction (mTxn.getTransactionID ()))
|
||||
return tefALREADY;
|
||||
|
||||
m_journal.trace << "Transaction has past sequence number " <<
|
||||
"a_seq=" << a_seq << " t_seq=" << t_seq;
|
||||
return tefPAST_SEQ;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,9 @@ bool isConsistent(IssueType<ByValue> const& ac)
|
||||
template <bool ByValue>
|
||||
std::string to_string (IssueType<ByValue> const& ac)
|
||||
{
|
||||
if (isXRP (ac.account))
|
||||
return to_string (ac.currency);
|
||||
|
||||
return to_string(ac.account) + "/" + to_string(ac.currency);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ public:
|
||||
static const int cMinOffset = -96;
|
||||
static const int cMaxOffset = 80;
|
||||
|
||||
// Maximum native value supported by the code
|
||||
static const std::uint64_t cMinValue = 1000000000000000ull;
|
||||
static const std::uint64_t cMaxValue = 9999999999999999ull;
|
||||
static const std::uint64_t cMaxNative = 9000000000000000000ull;
|
||||
|
||||
@@ -193,6 +193,7 @@ enum TER // aka TransactionEngineResult
|
||||
tecINSUFFICIENT_RESERVE = 141,
|
||||
tecNEED_MASTER_KEY = 142,
|
||||
tecDST_TAG_NEEDED = 143,
|
||||
tecINTERNAL = 144,
|
||||
};
|
||||
|
||||
inline bool isTelLocal(TER x)
|
||||
|
||||
@@ -67,6 +67,7 @@ bool transResultInfo (TER code, std::string& token, std::string& text)
|
||||
{ tecINSUFFICIENT_RESERVE, "tecINSUFFICIENT_RESERVE", "Insufficient reserve to complete requested operation." },
|
||||
{ tecNEED_MASTER_KEY, "tecNEED_MASTER_KEY", "The operation requires the use of the Master Key." },
|
||||
{ tecDST_TAG_NEEDED, "tecDST_TAG_NEEDED", "A destination tag is required." },
|
||||
{ tecINTERNAL, "tecINTERNAL", "An internal error has occurred during processing." },
|
||||
|
||||
{ tefALREADY, "tefALREADY", "The exact transaction was already in this ledger." },
|
||||
{ tefBAD_ADD_AUTH, "tefBAD_ADD_AUTH", "Not authorized to add account." },
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
|
||||
#include <ripple/app/book/tests/OfferStream.test.cpp>
|
||||
#include <ripple/app/book/tests/Quality.test.cpp>
|
||||
#include <ripple/app/book/tests/Taker.test.cpp>
|
||||
#include <ripple/app/ledger/InboundLedger.cpp>
|
||||
#include <ripple/app/paths/RippleState.cpp>
|
||||
#include <ripple/app/peers/UniqueNodeList.cpp>
|
||||
|
||||
@@ -32,7 +32,5 @@
|
||||
#include <ripple/app/transactors/AddWallet.cpp>
|
||||
#include <ripple/app/transactors/SetTrust.cpp>
|
||||
#include <ripple/app/transactors/CreateOffer.cpp>
|
||||
#include <ripple/app/transactors/CreateOfferDirect.cpp>
|
||||
#include <ripple/app/transactors/CreateOfferBridged.cpp>
|
||||
#include <ripple/app/transactors/CreateTicket.cpp>
|
||||
#include <ripple/app/transactors/CancelTicket.cpp>
|
||||
|
||||
Reference in New Issue
Block a user