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)
{
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
if (taker.reject (offer.quality()))

View File

@@ -30,27 +30,35 @@
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:
SLE::pointer m_entry;
Quality m_quality;
AccountID m_account;
mutable Amounts m_amounts;
TAmounts<TIn, TOut> m_amounts;
void setFieldAmounts ();
public:
Offer() = default;
TOffer() = default;
Offer (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))
{
}
TOffer (SLE::pointer const& entry, Quality quality);
/** Returns the quality of the offer.
Conceptually, the quality is the ratio of output to input currency.
@@ -77,7 +85,7 @@ public:
/** Returns the in and out amounts.
Some or all of the out amount may be unfunded.
*/
Amounts const&
TAmounts<TIn, TOut> const&
amount () const
{
return m_amounts;
@@ -97,7 +105,7 @@ public:
/** Adjusts the offer to indicate that we consumed some (or all) of it. */
void
consume (ApplyView& view,
Amounts const& consumed) const
TAmounts<TIn, TOut> const& consumed)
{
if (consumed.in > m_amounts.in)
Throw<std::logic_error> ("can't consume more than is available.");
@@ -105,12 +113,8 @@ public:
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);
m_amounts -= consumed;
setFieldAmounts ();
view.update (m_entry);
}
@@ -118,11 +122,112 @@ public:
{
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
std::ostream&
operator<< (std::ostream& os, Offer const& offer)
operator<< (std::ostream& os, TOffer<TIn, TOut> const& offer)
{
return os << offer.id ();
}

View File

@@ -23,7 +23,8 @@
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,
StepCounter& counter, beast::Journal 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
// is found. This shouldn't happen but if it does we clean it up.
template<class TIn, class TOut>
void
OfferStream::erase (ApplyView& view)
TOfferStreamBase<TIn, TOut>::erase (ApplyView& view)
{
// NIKB NOTE This should be using ApplyView::dirDelete, which would
// correctly remove the directory if its the last entry.
@@ -75,8 +77,48 @@ OfferStream::erase (ApplyView& view)
" 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
OfferStream::step (Logs& l)
TOfferStreamBase<TIn, TOut>::step (Logs& l)
{
// Modifying the order or logic of these
// operations causes a protocol breaking change.
@@ -84,6 +126,7 @@ OfferStream::step (Logs& l)
auto viewJ = l.journal ("View");
for(;;)
{
ownerFunds_ = boost::none;
// BookTip::step deletes the current offer from the view before
// advancing to the next (unless the ledger entry is missing).
if (! tip_.step(l))
@@ -111,43 +154,41 @@ OfferStream::step (Logs& l)
{
JLOG(j_.trace) <<
"Removing expired offer " << entry->getIndex();
offerDelete (cancelView_,
cancelView_.peek(keylet::offer(entry->key())), viewJ);
permRmOffer (entry, viewJ);
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
if (amount.empty())
{
JLOG(j_.warning) <<
"Removing bad offer " << entry->getIndex();
offerDelete (cancelView_,
cancelView_.peek(keylet::offer(entry->key())), viewJ);
offer_ = Offer{};
permRmOffer (entry, viewJ);
offer_ = TOffer<TIn, TOut>{};
continue;
}
// Calculate owner funds
auto const owner_funds = accountFunds(view_,
offer_.owner(), amount.out, fhZERO_IF_FROZEN, viewJ);
ownerFunds_ = accountFundsHelper (view_, offer_.owner (), amount.out,
offer_.issueOut (), fhZERO_IF_FROZEN, viewJ);
// Check for unfunded offer
if (owner_funds <= zero)
if (*ownerFunds_ <= zero)
{
// 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"
auto const original_funds = accountFunds(cancelView_,
offer_.owner(), amount.out, fhZERO_IF_FROZEN, viewJ);
auto const original_funds =
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(
keylet::offer(entry->key())), viewJ);
permRmOffer (entry, viewJ);
JLOG(j_.trace) <<
"Removing unfunded offer " << entry->getIndex();
}
@@ -156,7 +197,7 @@ OfferStream::step (Logs& l)
JLOG(j_.trace) <<
"Removing became unfunded offer " << entry->getIndex();
}
offer_ = Offer{};
offer_ = TOffer<TIn, TOut>{};
continue;
}
@@ -166,4 +207,26 @@ OfferStream::step (Logs& l)
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 {
/** 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
template<class TIn=STAmount, class TOut=STAmount>
class TOfferStreamBase
{
public:
class StepCounter
@@ -77,21 +61,25 @@ public:
}
};
private:
protected:
beast::Journal j_;
ApplyView& view_;
ApplyView& cancelView_;
Book book_;
NetClock::time_point const expire_;
BookTip tip_;
Offer offer_;
TOffer<TIn, TOut> offer_;
boost::optional<TOut> ownerFunds_;
StepCounter& counter_;
void
erase (ApplyView& view);
virtual
void
permRmOffer (std::shared_ptr<SLE> const& sle, beast::Journal j) = 0;
public:
OfferStream (ApplyView& view, ApplyView& cancelView,
TOfferStreamBase (ApplyView& view, ApplyView& cancelView,
Book const& book, NetClock::time_point when,
StepCounter& counter, beast::Journal journal);
@@ -99,10 +87,10 @@ public:
Offers are always presented in decreasing quality.
Only valid if step() returned `true`.
*/
Offer const&
TOffer<TIn, TOut>&
tip () const
{
return offer_;
return const_cast<TOfferStreamBase*>(this)->offer_;
}
/** Advance to the next valid offer.
@@ -114,8 +102,75 @@ public:
*/
bool
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

View File

@@ -562,7 +562,7 @@ Taker::Taker (CrossType cross_type, ApplyView& view,
}
void
Taker::consume_offer (Offer const& offer, Amounts const& order)
Taker::consume_offer (Offer& offer, Amounts const& order)
{
if (order.in < zero)
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.
TER
Taker::fill (BasicTaker::Flow const& flow, Offer const& offer)
Taker::fill (BasicTaker::Flow const& flow, Offer& offer)
{
// adjust offer
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.
TER
Taker::fill (
BasicTaker::Flow const& flow1, Offer const& leg1,
BasicTaker::Flow const& flow2, Offer const& leg2)
BasicTaker::Flow const& flow1, Offer& leg1,
BasicTaker::Flow const& flow2, Offer& leg2)
{
// Adjust offers accordingly
consume_offer (leg1, flow1.order);
@@ -751,7 +751,7 @@ Taker::fill (
}
TER
Taker::cross (Offer const& offer)
Taker::cross (Offer& offer)
{
// In direct crossings, at least one leg must not be XRP.
if (isXRP (offer.amount ().in) && isXRP (offer.amount ().out))
@@ -764,7 +764,7 @@ Taker::cross (Offer const& offer)
}
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
// or the output of the second leg.

View File

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