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:
Nik Bougalis
2014-10-27 11:23:17 -08:00
parent 385a87db31
commit 3ccbd7c9b2
24 changed files with 1977 additions and 1232 deletions

View File

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

View File

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

View File

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

View File

@@ -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 ();
}
}

View File

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

View File

@@ -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();
}
}
}

View File

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

View File

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

View File

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

View 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);
}
}

View File

@@ -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 ());
}

View File

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

View File

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

View File

@@ -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 ());
}
}

View File

@@ -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 ());
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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." },

View File

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

View File

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