From 6d2f7e46dd7ddacf3bf1b80ef602e06ee255b656 Mon Sep 17 00:00:00 2001 From: seelabs Date: Sat, 3 Oct 2015 15:53:24 -0400 Subject: [PATCH] Add IOU/XRP Amount support to Offers --- src/ripple/app/tx/impl/CreateOffer.cpp | 2 +- src/ripple/app/tx/impl/Offer.h | 149 +++++++++++++++++++++---- src/ripple/app/tx/impl/OfferStream.cpp | 101 +++++++++++++---- src/ripple/app/tx/impl/OfferStream.h | 101 +++++++++++++---- src/ripple/app/tx/impl/Taker.cpp | 12 +- src/ripple/app/tx/impl/Taker.h | 12 +- 6 files changed, 300 insertions(+), 77 deletions(-) diff --git a/src/ripple/app/tx/impl/CreateOffer.cpp b/src/ripple/app/tx/impl/CreateOffer.cpp index f9d81f676..6a9c02fff 100644 --- a/src/ripple/app/tx/impl/CreateOffer.cpp +++ b/src/ripple/app/tx/impl/CreateOffer.cpp @@ -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())) diff --git a/src/ripple/app/tx/impl/Offer.h b/src/ripple/app/tx/impl/Offer.h index 215e1a864..656ed87e6 100644 --- a/src/ripple/app/tx/impl/Offer.h +++ b/src/ripple/app/tx/impl/Offer.h @@ -30,27 +30,35 @@ namespace ripple { -class Offer +template +class TOfferBase +{ +protected: + Issue issIn_; + Issue issOut_; +}; + +template<> +class TOfferBase +{ +}; + + +template +class TOffer + : public TOfferBase { private: SLE::pointer m_entry; Quality m_quality; AccountID m_account; - mutable Amounts m_amounts; - + TAmounts 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 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 const& consumed) { if (consumed.in > m_amounts.in) Throw ("can't consume more than is available."); @@ -105,12 +113,8 @@ public: if (consumed.out > m_amounts.out) Throw ("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 +TOffer::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 (tp); + m_amounts.out = toAmount (tg); + this->issIn_ = tp.issue (); + this->issOut_ = tg.issue (); +} + +template<> +inline +TOffer::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 +void TOffer::setFieldAmounts () +{ +#ifdef _MSC_VER + assert(0); +#else + static_assert(sizeof(TOut) == -1, "Must be specialized"); +#endif +} + +template<> +inline +void TOffer::setFieldAmounts () +{ + m_entry->setFieldAmount (sfTakerPays, m_amounts.in); + m_entry->setFieldAmount (sfTakerGets, m_amounts.out); +} + +template<> +inline +void TOffer::setFieldAmounts () +{ + m_entry->setFieldAmount (sfTakerPays, toSTAmount(m_amounts.in, issIn_)); + m_entry->setFieldAmount (sfTakerGets, toSTAmount(m_amounts.out, issOut_)); +} + +template<> +inline +void TOffer::setFieldAmounts () +{ + m_entry->setFieldAmount (sfTakerPays, toSTAmount(m_amounts.in, issIn_)); + m_entry->setFieldAmount (sfTakerGets, toSTAmount(m_amounts.out)); +} + +template<> +inline +void TOffer::setFieldAmounts () +{ + m_entry->setFieldAmount (sfTakerPays, toSTAmount(m_amounts.in)); + m_entry->setFieldAmount (sfTakerGets, toSTAmount(m_amounts.out, issOut_)); +} + +template +Issue TOffer::issueIn () const +{ + return this->issIn_; +} + +template<> +inline +Issue TOffer::issueIn () const +{ + return m_amounts.in.issue (); +} + +template +Issue TOffer::issueOut () const +{ + return this->issOut_; +} + +template<> +inline +Issue TOffer::issueOut () const +{ + return m_amounts.out.issue (); +} + +template inline std::ostream& -operator<< (std::ostream& os, Offer const& offer) +operator<< (std::ostream& os, TOffer const& offer) { return os << offer.id (); } diff --git a/src/ripple/app/tx/impl/OfferStream.cpp b/src/ripple/app/tx/impl/OfferStream.cpp index 91e4e2a99..b1d713058 100644 --- a/src/ripple/app/tx/impl/OfferStream.cpp +++ b/src/ripple/app/tx/impl/OfferStream.cpp @@ -23,7 +23,8 @@ namespace ripple { -OfferStream::OfferStream (ApplyView& view, ApplyView& cancelView, +template +TOfferStreamBase::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 void -OfferStream::erase (ApplyView& view) +TOfferStreamBase::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 ( + 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 ( + accountHolds (view, id, issue.currency, issue.account, freezeHandling, j)); +} + +template bool -OfferStream::step (Logs& l) +TOfferStreamBase::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 (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{}; 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{}; continue; } @@ -166,4 +207,26 @@ OfferStream::step (Logs& l) return true; } +void +OfferStream::permRmOffer (std::shared_ptr const& sle, beast::Journal j) +{ + offerDelete (cancelView_, + cancelView_.peek(keylet::offer(sle->key())), j); +} + +template +void FlowOfferStream::permRmOffer (std::shared_ptr const& sle, beast::Journal) +{ + toRemove_.push_back (sle->key()); +} + +template class FlowOfferStream; +template class FlowOfferStream; +template class FlowOfferStream; +template class FlowOfferStream; + +template class TOfferStreamBase; +template class TOfferStreamBase; +template class TOfferStreamBase; +template class TOfferStreamBase; } diff --git a/src/ripple/app/tx/impl/OfferStream.h b/src/ripple/app/tx/impl/OfferStream.h index dbfe058eb..5912686f6 100644 --- a/src/ripple/app/tx/impl/OfferStream.h +++ b/src/ripple/app/tx/impl/OfferStream.h @@ -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 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 offer_; + boost::optional ownerFunds_; StepCounter& counter_; void erase (ApplyView& view); + virtual + void + permRmOffer (std::shared_ptr 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& tip () const { - return offer_; + return const_cast(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 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 FlowOfferStream : public TOfferStreamBase +{ +private: + std::vector toRemove_; +protected: + virtual + void + permRmOffer (std::shared_ptr const& sle, beast::Journal j) override; + +public: + using TOfferStreamBase::TOfferStreamBase; + + std::vector const& toRemove () const + { + return toRemove_; + }; +}; } #endif diff --git a/src/ripple/app/tx/impl/Taker.cpp b/src/ripple/app/tx/impl/Taker.cpp index cb989554d..7a3ef6de0 100644 --- a/src/ripple/app/tx/impl/Taker.cpp +++ b/src/ripple/app/tx/impl/Taker.cpp @@ -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 ("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. diff --git a/src/ripple/app/tx/impl/Taker.h b/src/ripple/app/tx/impl/Taker.h index e7dde3fc5..03080ea5c 100644 --- a/src/ripple/app/tx/impl/Taker.h +++ b/src/ripple/app/tx/impl/Taker.h @@ -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);