Add IOU/XRP Amount support to Offers

This commit is contained in:
seelabs
2015-10-03 15:53:24 -04:00
parent 3d9589f010
commit 6d2f7e46dd
6 changed files with 300 additions and 77 deletions

View File

@@ -493,7 +493,7 @@ CreateOffer::direct_cross (
while (have_offer) while (have_offer)
{ {
bool direct_consumed = false; bool direct_consumed = false;
auto const& offer (offers.tip()); auto& offer (offers.tip());
// We are done with crossing as soon as we cross the quality boundary // We are done with crossing as soon as we cross the quality boundary
if (taker.reject (offer.quality())) if (taker.reject (offer.quality()))

View File

@@ -30,27 +30,35 @@
namespace ripple { namespace ripple {
class Offer template <class TIn, class TOut>
class TOfferBase
{
protected:
Issue issIn_;
Issue issOut_;
};
template<>
class TOfferBase<STAmount, STAmount>
{
};
template<class TIn=STAmount, class TOut=STAmount>
class TOffer
: public TOfferBase<TIn, TOut>
{ {
private: private:
SLE::pointer m_entry; SLE::pointer m_entry;
Quality m_quality; Quality m_quality;
AccountID m_account; AccountID m_account;
mutable Amounts m_amounts; TAmounts<TIn, TOut> m_amounts;
void setFieldAmounts ();
public: public:
Offer() = default; TOffer() = default;
Offer (SLE::pointer const& entry, Quality quality) TOffer (SLE::pointer const& entry, Quality quality);
: m_entry (entry)
, m_quality (quality)
, m_account (m_entry->getAccountID (sfAccount))
, m_amounts (
m_entry->getFieldAmount (sfTakerPays),
m_entry->getFieldAmount (sfTakerGets))
{
}
/** Returns the quality of the offer. /** Returns the quality of the offer.
Conceptually, the quality is the ratio of output to input currency. Conceptually, the quality is the ratio of output to input currency.
@@ -77,7 +85,7 @@ public:
/** Returns the in and out amounts. /** Returns the in and out amounts.
Some or all of the out amount may be unfunded. Some or all of the out amount may be unfunded.
*/ */
Amounts const& TAmounts<TIn, TOut> const&
amount () const amount () const
{ {
return m_amounts; return m_amounts;
@@ -97,7 +105,7 @@ public:
/** Adjusts the offer to indicate that we consumed some (or all) of it. */ /** Adjusts the offer to indicate that we consumed some (or all) of it. */
void void
consume (ApplyView& view, consume (ApplyView& view,
Amounts const& consumed) const TAmounts<TIn, TOut> const& consumed)
{ {
if (consumed.in > m_amounts.in) if (consumed.in > m_amounts.in)
Throw<std::logic_error> ("can't consume more than is available."); Throw<std::logic_error> ("can't consume more than is available.");
@@ -105,12 +113,8 @@ public:
if (consumed.out > m_amounts.out) if (consumed.out > m_amounts.out)
Throw<std::logic_error> ("can't produce more than is available."); Throw<std::logic_error> ("can't produce more than is available.");
m_amounts.in -= consumed.in; m_amounts -= consumed;
m_amounts.out -= consumed.out; setFieldAmounts ();
m_entry->setFieldAmount (sfTakerPays, m_amounts.in);
m_entry->setFieldAmount (sfTakerGets, m_amounts.out);
view.update (m_entry); view.update (m_entry);
} }
@@ -118,11 +122,112 @@ public:
{ {
return to_string (m_entry->getIndex()); return to_string (m_entry->getIndex());
} }
Issue issueIn () const;
Issue issueOut () const;
}; };
using Offer = TOffer <>;
template<class TIn, class TOut>
TOffer<TIn, TOut>::TOffer (SLE::pointer const& entry, Quality quality)
: m_entry (entry)
, m_quality (quality)
, m_account (m_entry->getAccountID (sfAccount))
{
auto const tp = m_entry->getFieldAmount (sfTakerPays);
auto const tg = m_entry->getFieldAmount (sfTakerGets);
m_amounts.in = toAmount<TIn> (tp);
m_amounts.out = toAmount<TOut> (tg);
this->issIn_ = tp.issue ();
this->issOut_ = tg.issue ();
}
template<>
inline
TOffer<STAmount, STAmount>::TOffer (SLE::pointer const& entry, Quality quality)
: m_entry (entry)
, m_quality (quality)
, m_account (m_entry->getAccountID (sfAccount))
, m_amounts (
m_entry->getFieldAmount (sfTakerPays),
m_entry->getFieldAmount (sfTakerGets))
{
}
template<class TIn, class TOut>
void TOffer<TIn, TOut>::setFieldAmounts ()
{
#ifdef _MSC_VER
assert(0);
#else
static_assert(sizeof(TOut) == -1, "Must be specialized");
#endif
}
template<>
inline
void TOffer<STAmount, STAmount>::setFieldAmounts ()
{
m_entry->setFieldAmount (sfTakerPays, m_amounts.in);
m_entry->setFieldAmount (sfTakerGets, m_amounts.out);
}
template<>
inline
void TOffer<IOUAmount, IOUAmount>::setFieldAmounts ()
{
m_entry->setFieldAmount (sfTakerPays, toSTAmount(m_amounts.in, issIn_));
m_entry->setFieldAmount (sfTakerGets, toSTAmount(m_amounts.out, issOut_));
}
template<>
inline
void TOffer<IOUAmount, XRPAmount>::setFieldAmounts ()
{
m_entry->setFieldAmount (sfTakerPays, toSTAmount(m_amounts.in, issIn_));
m_entry->setFieldAmount (sfTakerGets, toSTAmount(m_amounts.out));
}
template<>
inline
void TOffer<XRPAmount, IOUAmount>::setFieldAmounts ()
{
m_entry->setFieldAmount (sfTakerPays, toSTAmount(m_amounts.in));
m_entry->setFieldAmount (sfTakerGets, toSTAmount(m_amounts.out, issOut_));
}
template<class TIn, class TOut>
Issue TOffer<TIn, TOut>::issueIn () const
{
return this->issIn_;
}
template<>
inline
Issue TOffer<STAmount, STAmount>::issueIn () const
{
return m_amounts.in.issue ();
}
template<class TIn, class TOut>
Issue TOffer<TIn, TOut>::issueOut () const
{
return this->issOut_;
}
template<>
inline
Issue TOffer<STAmount, STAmount>::issueOut () const
{
return m_amounts.out.issue ();
}
template<class TIn, class TOut>
inline inline
std::ostream& std::ostream&
operator<< (std::ostream& os, Offer const& offer) operator<< (std::ostream& os, TOffer<TIn, TOut> const& offer)
{ {
return os << offer.id (); return os << offer.id ();
} }

View File

@@ -23,7 +23,8 @@
namespace ripple { namespace ripple {
OfferStream::OfferStream (ApplyView& view, ApplyView& cancelView, template<class TIn, class TOut>
TOfferStreamBase<TIn, TOut>::TOfferStreamBase (ApplyView& view, ApplyView& cancelView,
Book const& book, NetClock::time_point when, Book const& book, NetClock::time_point when,
StepCounter& counter, beast::Journal journal) StepCounter& counter, beast::Journal journal)
: j_ (journal) : j_ (journal)
@@ -38,8 +39,9 @@ OfferStream::OfferStream (ApplyView& view, ApplyView& cancelView,
// Handle the case where a directory item with no corresponding ledger entry // Handle the case where a directory item with no corresponding ledger entry
// is found. This shouldn't happen but if it does we clean it up. // is found. This shouldn't happen but if it does we clean it up.
template<class TIn, class TOut>
void void
OfferStream::erase (ApplyView& view) TOfferStreamBase<TIn, TOut>::erase (ApplyView& view)
{ {
// NIKB NOTE This should be using ApplyView::dirDelete, which would // NIKB NOTE This should be using ApplyView::dirDelete, which would
// correctly remove the directory if its the last entry. // correctly remove the directory if its the last entry.
@@ -75,8 +77,48 @@ OfferStream::erase (ApplyView& view)
" removed from directory " << tip_.dir(); " removed from directory " << tip_.dir();
} }
static
STAmount accountFundsHelper (ReadView const& view,
AccountID const& id,
STAmount const& saDefault,
Issue const& issue,
FreezeHandling freezeHandling,
beast::Journal j)
{
return accountFunds (view, id, saDefault, freezeHandling, j);
}
static
IOUAmount accountFundsHelper (ReadView const& view,
AccountID const& id,
IOUAmount const& amtDefault,
Issue const& issue,
FreezeHandling freezeHandling,
beast::Journal j)
{
if (issue.account == id)
// self funded
return amtDefault;
return toAmount<IOUAmount> (
accountHolds (view, id, issue.currency, issue.account, freezeHandling, j));
}
static
XRPAmount accountFundsHelper (ReadView const& view,
AccountID const& id,
XRPAmount const& amtDefault,
Issue const& issue,
FreezeHandling freezeHandling,
beast::Journal j)
{
return toAmount<XRPAmount> (
accountHolds (view, id, issue.currency, issue.account, freezeHandling, j));
}
template<class TIn, class TOut>
bool bool
OfferStream::step (Logs& l) TOfferStreamBase<TIn, TOut>::step (Logs& l)
{ {
// Modifying the order or logic of these // Modifying the order or logic of these
// operations causes a protocol breaking change. // operations causes a protocol breaking change.
@@ -84,6 +126,7 @@ OfferStream::step (Logs& l)
auto viewJ = l.journal ("View"); auto viewJ = l.journal ("View");
for(;;) for(;;)
{ {
ownerFunds_ = boost::none;
// BookTip::step deletes the current offer from the view before // BookTip::step deletes the current offer from the view before
// advancing to the next (unless the ledger entry is missing). // advancing to the next (unless the ledger entry is missing).
if (! tip_.step(l)) if (! tip_.step(l))
@@ -111,43 +154,41 @@ OfferStream::step (Logs& l)
{ {
JLOG(j_.trace) << JLOG(j_.trace) <<
"Removing expired offer " << entry->getIndex(); "Removing expired offer " << entry->getIndex();
offerDelete (cancelView_, permRmOffer (entry, viewJ);
cancelView_.peek(keylet::offer(entry->key())), viewJ);
continue; continue;
} }
offer_ = Offer (entry, tip_.quality()); offer_ = TOffer<TIn, TOut> (entry, tip_.quality());
Amounts const amount (offer_.amount()); auto const amount (offer_.amount());
// Remove if either amount is zero // Remove if either amount is zero
if (amount.empty()) if (amount.empty())
{ {
JLOG(j_.warning) << JLOG(j_.warning) <<
"Removing bad offer " << entry->getIndex(); "Removing bad offer " << entry->getIndex();
offerDelete (cancelView_, permRmOffer (entry, viewJ);
cancelView_.peek(keylet::offer(entry->key())), viewJ); offer_ = TOffer<TIn, TOut>{};
offer_ = Offer{};
continue; continue;
} }
// Calculate owner funds // Calculate owner funds
auto const owner_funds = accountFunds(view_, ownerFunds_ = accountFundsHelper (view_, offer_.owner (), amount.out,
offer_.owner(), amount.out, fhZERO_IF_FROZEN, viewJ); offer_.issueOut (), fhZERO_IF_FROZEN, viewJ);
// Check for unfunded offer // Check for unfunded offer
if (owner_funds <= zero) if (*ownerFunds_ <= zero)
{ {
// If the owner's balance in the pristine view is the same, // If the owner's balance in the pristine view is the same,
// we haven't modified the balance and therefore the // we haven't modified the balance and therefore the
// offer is "found unfunded" versus "became unfunded" // offer is "found unfunded" versus "became unfunded"
auto const original_funds = accountFunds(cancelView_, auto const original_funds =
offer_.owner(), amount.out, fhZERO_IF_FROZEN, viewJ); accountFundsHelper (cancelView_, offer_.owner (), amount.out,
offer_.issueOut (), fhZERO_IF_FROZEN, viewJ);
if (original_funds == owner_funds) if (original_funds == *ownerFunds_)
{ {
offerDelete (cancelView_, cancelView_.peek( permRmOffer (entry, viewJ);
keylet::offer(entry->key())), viewJ);
JLOG(j_.trace) << JLOG(j_.trace) <<
"Removing unfunded offer " << entry->getIndex(); "Removing unfunded offer " << entry->getIndex();
} }
@@ -156,7 +197,7 @@ OfferStream::step (Logs& l)
JLOG(j_.trace) << JLOG(j_.trace) <<
"Removing became unfunded offer " << entry->getIndex(); "Removing became unfunded offer " << entry->getIndex();
} }
offer_ = Offer{}; offer_ = TOffer<TIn, TOut>{};
continue; continue;
} }
@@ -166,4 +207,26 @@ OfferStream::step (Logs& l)
return true; return true;
} }
void
OfferStream::permRmOffer (std::shared_ptr<SLE> const& sle, beast::Journal j)
{
offerDelete (cancelView_,
cancelView_.peek(keylet::offer(sle->key())), j);
}
template<class TIn, class TOut>
void FlowOfferStream<TIn, TOut>::permRmOffer (std::shared_ptr<SLE> const& sle, beast::Journal)
{
toRemove_.push_back (sle->key());
}
template class FlowOfferStream<STAmount, STAmount>;
template class FlowOfferStream<IOUAmount, IOUAmount>;
template class FlowOfferStream<XRPAmount, IOUAmount>;
template class FlowOfferStream<IOUAmount, XRPAmount>;
template class TOfferStreamBase<STAmount, STAmount>;
template class TOfferStreamBase<IOUAmount, IOUAmount>;
template class TOfferStreamBase<XRPAmount, IOUAmount>;
template class TOfferStreamBase<IOUAmount, XRPAmount>;
} }

View File

@@ -29,24 +29,8 @@
namespace ripple { namespace ripple {
/** Presents and consumes the offers in an order book. template<class TIn=STAmount, class TOut=STAmount>
class TOfferStreamBase
Two `ApplyView` objects accumulate changes to the ledger. `view`
is applied when the calling transaction succeeds. If the calling
transaction fails, then `view_cancel` is applied.
Certain invalid offers are automatically removed:
- Offers with missing ledger entries
- Offers that expired
- Offers found unfunded:
An offer is found unfunded when the corresponding balance is zero
and the caller has not modified the balance. This is accomplished
by also looking up the balance in the cancel view.
When an offer is removed, it is removed from both views. This grooms the
order book regardless of whether or not the transaction is successful.
*/
class OfferStream
{ {
public: public:
class StepCounter class StepCounter
@@ -77,21 +61,25 @@ public:
} }
}; };
private: protected:
beast::Journal j_; beast::Journal j_;
ApplyView& view_; ApplyView& view_;
ApplyView& cancelView_; ApplyView& cancelView_;
Book book_; Book book_;
NetClock::time_point const expire_; NetClock::time_point const expire_;
BookTip tip_; BookTip tip_;
Offer offer_; TOffer<TIn, TOut> offer_;
boost::optional<TOut> ownerFunds_;
StepCounter& counter_; StepCounter& counter_;
void void
erase (ApplyView& view); erase (ApplyView& view);
virtual
void
permRmOffer (std::shared_ptr<SLE> const& sle, beast::Journal j) = 0;
public: public:
OfferStream (ApplyView& view, ApplyView& cancelView, TOfferStreamBase (ApplyView& view, ApplyView& cancelView,
Book const& book, NetClock::time_point when, Book const& book, NetClock::time_point when,
StepCounter& counter, beast::Journal journal); StepCounter& counter, beast::Journal journal);
@@ -99,10 +87,10 @@ public:
Offers are always presented in decreasing quality. Offers are always presented in decreasing quality.
Only valid if step() returned `true`. Only valid if step() returned `true`.
*/ */
Offer const& TOffer<TIn, TOut>&
tip () const tip () const
{ {
return offer_; return const_cast<TOfferStreamBase*>(this)->offer_;
} }
/** Advance to the next valid offer. /** Advance to the next valid offer.
@@ -114,8 +102,75 @@ public:
*/ */
bool bool
step (Logs& l); step (Logs& l);
TOut ownerFunds () const
{
return *ownerFunds_;
}
}; };
/** Presents and consumes the offers in an order book.
Two `ApplyView` objects accumulate changes to the ledger. `view`
is applied when the calling transaction succeeds. If the calling
transaction fails, then `view_cancel` is applied.
Certain invalid offers are automatically removed:
- Offers with missing ledger entries
- Offers that expired
- Offers found unfunded:
An offer is found unfunded when the corresponding balance is zero
and the caller has not modified the balance. This is accomplished
by also looking up the balance in the cancel view.
When an offer is removed, it is removed from both views. This grooms the
order book regardless of whether or not the transaction is successful.
*/
class OfferStream : public TOfferStreamBase<>
{
protected:
virtual
void
permRmOffer (std::shared_ptr<SLE> const& sle, beast::Journal j) override;
public:
using TOfferStreamBase<>::TOfferStreamBase;
};
/** Presents and consumes the offers in an order book.
The `view_' ` `ApplyView` accumulates changes to the ledger.
The `cancelView_` is used to determine if an offer is found
unfunded or became unfunded.
The `toRemove` collection identifies offers that should be
removed even if the strand associated with this OfferStream
is not applied.
Certain invalid offers are added to the `toRemove` collection:
- Offers with missing ledger entries
- Offers that expired
- Offers found unfunded:
An offer is found unfunded when the corresponding balance is zero
and the caller has not modified the balance. This is accomplished
by also looking up the balance in the cancel view.
*/
template <class TIn, class TOut>
class FlowOfferStream : public TOfferStreamBase<TIn, TOut>
{
private:
std::vector<uint256> toRemove_;
protected:
virtual
void
permRmOffer (std::shared_ptr<SLE> const& sle, beast::Journal j) override;
public:
using TOfferStreamBase<TIn, TOut>::TOfferStreamBase;
std::vector<uint256> const& toRemove () const
{
return toRemove_;
};
};
} }
#endif #endif

View File

@@ -562,7 +562,7 @@ Taker::Taker (CrossType cross_type, ApplyView& view,
} }
void void
Taker::consume_offer (Offer const& offer, Amounts const& order) Taker::consume_offer (Offer& offer, Amounts const& order)
{ {
if (order.in < zero) if (order.in < zero)
Throw<std::logic_error> ("flow with negative input."); Throw<std::logic_error> ("flow with negative input.");
@@ -655,7 +655,7 @@ TER Taker::issueIOU (
// Performs funds transfers to fill the given offer and adjusts offer. // Performs funds transfers to fill the given offer and adjusts offer.
TER TER
Taker::fill (BasicTaker::Flow const& flow, Offer const& offer) Taker::fill (BasicTaker::Flow const& flow, Offer& offer)
{ {
// adjust offer // adjust offer
consume_offer (offer, flow.order); consume_offer (offer, flow.order);
@@ -708,8 +708,8 @@ Taker::fill (BasicTaker::Flow const& flow, Offer const& offer)
// Performs bridged funds transfers to fill the given offers and adjusts offers. // Performs bridged funds transfers to fill the given offers and adjusts offers.
TER TER
Taker::fill ( Taker::fill (
BasicTaker::Flow const& flow1, Offer const& leg1, BasicTaker::Flow const& flow1, Offer& leg1,
BasicTaker::Flow const& flow2, Offer const& leg2) BasicTaker::Flow const& flow2, Offer& leg2)
{ {
// Adjust offers accordingly // Adjust offers accordingly
consume_offer (leg1, flow1.order); consume_offer (leg1, flow1.order);
@@ -751,7 +751,7 @@ Taker::fill (
} }
TER TER
Taker::cross (Offer const& offer) Taker::cross (Offer& offer)
{ {
// In direct crossings, at least one leg must not be XRP. // In direct crossings, at least one leg must not be XRP.
if (isXRP (offer.amount ().in) && isXRP (offer.amount ().out)) if (isXRP (offer.amount ().in) && isXRP (offer.amount ().out))
@@ -764,7 +764,7 @@ Taker::cross (Offer const& offer)
} }
TER TER
Taker::cross (Offer const& leg1, Offer const& leg2) Taker::cross (Offer& leg1, Offer& leg2)
{ {
// In bridged crossings, XRP must can't be the input to the first leg // In bridged crossings, XRP must can't be the input to the first leg
// or the output of the second leg. // or the output of the second leg.

View File

@@ -254,7 +254,7 @@ public:
~Taker () = default; ~Taker () = default;
void void
consume_offer (Offer const& offer, Amounts const& order); consume_offer (Offer& offer, Amounts const& order);
STAmount STAmount
get_funds (AccountID const& account, STAmount const& funds) const; get_funds (AccountID const& account, STAmount const& funds) const;
@@ -283,10 +283,10 @@ public:
*/ */
/** @{ */ /** @{ */
TER TER
cross (Offer const& offer); cross (Offer& offer);
TER TER
cross (Offer const& leg1, Offer const& leg2); cross (Offer& leg1, Offer& leg2);
/** @} */ /** @} */
private: private:
@@ -297,12 +297,12 @@ private:
AccountID const& account); AccountID const& account);
TER TER
fill (BasicTaker::Flow const& flow, Offer const& offer); fill (BasicTaker::Flow const& flow, Offer& offer);
TER TER
fill ( fill (
BasicTaker::Flow const& flow1, Offer const& leg1, BasicTaker::Flow const& flow1, Offer& leg1,
BasicTaker::Flow const& flow2, Offer const& leg2); BasicTaker::Flow const& flow2, Offer& leg2);
TER TER
transferXRP (AccountID const& from, AccountID const& to, STAmount const& amount); transferXRP (AccountID const& from, AccountID const& to, STAmount const& amount);