diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj index ed667ed46b..c0449e29ed 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj +++ b/Builds/VisualStudio2013/RippleD.vcxproj @@ -820,6 +820,12 @@ true + + true + true + true + true + true true @@ -2485,7 +2491,11 @@ + + + + diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters index 1104a47d64..f043ee5aca 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters @@ -1515,6 +1515,9 @@ [2] Old Ripple\ripple_app\book\tests + + [2] Old Ripple\ripple_app\book\tests + @@ -3090,6 +3093,18 @@ [2] Old Ripple\ripple_app\book + + [2] Old Ripple\ripple_app\book + + + [2] Old Ripple\ripple_app\book + + + [2] Old Ripple\ripple_app\book + + + [2] Old Ripple\ripple_app\book + diff --git a/src/ripple/types/api/RippleAssets.h b/src/ripple/types/api/RippleAssets.h index 4323719e23..28bb8c11a5 100644 --- a/src/ripple/types/api/RippleAssets.h +++ b/src/ripple/types/api/RippleAssets.h @@ -75,11 +75,10 @@ public: { } - /** Assignment. - This is only valid when ByValue == `true` - */ - template - RippleAssetType& operator= (RippleAssetType const& other) + /** Assignment. */ + template + std::enable_if_t + operator= (RippleAssetType const& other) { currency = other.currency; issuer = other.issuer; diff --git a/src/ripple_app/book/BookTip.h b/src/ripple_app/book/BookTip.h new file mode 100644 index 0000000000..18dac3bb41 --- /dev/null +++ b/src/ripple_app/book/BookTip.h @@ -0,0 +1,144 @@ +//------------------------------------------------------------------------------ +/* + 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_CORE_BOOKTIP_H_INCLUDED +#define RIPPLE_CORE_BOOKTIP_H_INCLUDED + +#include "Quality.h" +#include "Types.h" + +#include "../../beast/beast/utility/noexcept.h" + +#include +#include + +namespace ripple { +namespace core { + +/** Iterates and consumes raw offers in an order book. + Offers are presented from highest quality to lowest quality. This will + return all offers present including missing, invalid, unfunded, etc. +*/ +class BookTip +{ +private: + std::reference_wrapper m_view; + bool m_valid; + uint256 m_book; + uint256 m_end; + uint256 m_dir; + uint256 m_index; + SLE::pointer m_entry; + + LedgerView& + view() const noexcept + { + return m_view; + } + +public: + /** Create the iterator. */ + BookTip (LedgerView& view, BookRef book) + : m_view (view) + , m_valid (false) + , m_book (Ledger::getBookBase ( + book.in.currency, book.in.issuer, + book.out.currency, book.out.issuer)) + , m_end (Ledger::getQualityNext (m_book)) + { + } + + uint256 const& + dir() const noexcept + { + return m_dir; + } + + uint256 const& + index() const noexcept + { + return m_index; + } + + Quality const + quality() const noexcept + { + return Quality (Ledger::getQuality (m_dir)); + } + + SLE::pointer const& + entry() const noexcept + { + return m_entry; + } + + /** Erases the current offer and advance to the next offer. + Complexity: Constant + @return `true` if there is a next offer + */ + bool + step () + { + if (m_valid) + { + if (m_entry) + { + view().offerDelete (m_index); + m_entry = nullptr; + } + } + + for(;;) + { + // See if there's an entry at or worse than current quality. + auto const page ( + view().getNextLedgerIndex (m_book, m_end)); + + if (page.isZero()) + return false; + + unsigned int di (0); + SLE::pointer dir; + if (view().dirFirst (page, dir, di, m_index)) + { + m_dir = dir->getIndex(); + m_entry = view().entryCache (ltOFFER, m_index); + m_valid = true; + + // Next query should start before this directory + m_book = page; + + // The quality immediately before the next quality + --m_book; + + break; + } + // There should never be an empty directory but just in case, + // we handle that case by advancing to the next directory. + m_book = page; + } + + return true; + } +}; + +} +} + +#endif diff --git a/src/ripple_app/book/Offer.h b/src/ripple_app/book/Offer.h new file mode 100644 index 0000000000..62d59978d6 --- /dev/null +++ b/src/ripple_app/book/Offer.h @@ -0,0 +1,104 @@ +//------------------------------------------------------------------------------ +/* + 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_CORE_OFFER_H_INCLUDED +#define RIPPLE_CORE_OFFER_H_INCLUDED + +#include "Amounts.h" +#include "Quality.h" +#include "Types.h" + +#include "../misc/SerializedLedger.h" +#include "../../ripple_data/protocol/FieldNames.h" + +#include + +namespace ripple { +namespace core { + +class Offer +{ +public: + typedef Amount amount_type; + +private: + SLE::pointer m_entry; + Quality m_quality; + +public: + Offer() = default; + + Offer (SLE::pointer const& entry, Quality quality) + : m_entry (entry) + , m_quality (quality) + { + } + + /** Returns the quality of the offer. + Conceptually, the quality is the ratio of output to input currency. + The implementation calculates it as the ratio of input to output + currency (so it sorts ascending). The quality is computed at the time + the offer is placed, and never changes for the lifetime of the offer. + This is an important business rule that maintains accuracy when an + offer is partially filled; Subsequent partial fills will use the + original quality. + */ + Quality const + quality() const noexcept + { + return m_quality; + } + + /** Returns the account id of the offer's owner. */ + Account const + account() const + { + return m_entry->getFieldAccount160 (sfAccount); + } + + /** Returns the in and out amounts. + Some or all of the out amount may be unfunded. + */ + Amounts const + amount() const noexcept + { + return Amounts (m_entry->getFieldAmount (sfTakerPays), + m_entry->getFieldAmount (sfTakerGets)); + } + + /** Returns the ledger entry underlying the offer. */ + // AVOID USING THIS + SLE::pointer + entry() const noexcept + { + return m_entry; + } +}; + +inline +std::ostream& +operator<< (std::ostream& os, Offer const& offer) +{ + return os << offer.entry()->getIndex(); +} + +} +} + +#endif diff --git a/src/ripple_app/book/OfferStream.h b/src/ripple_app/book/OfferStream.h new file mode 100644 index 0000000000..a75f22b3e9 --- /dev/null +++ b/src/ripple_app/book/OfferStream.h @@ -0,0 +1,324 @@ +//------------------------------------------------------------------------------ +/* + 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_CORE_OFFERSTREAM_H_INCLUDED +#define RIPPLE_CORE_OFFERSTREAM_H_INCLUDED + +#include "BookTip.h" +#include "Offer.h" +#include "Quality.h" +#include "Types.h" + +#include "../../beast/beast/utility/noexcept.h" + +#include +#include + +namespace ripple { +namespace core { + +/** Presents and consumes the offers in an order book. + + Two `LedgerView` 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. + + TODO: Remove offers belonging to the taker +*/ +class OfferStream +{ +protected: + beast::Journal m_journal; + std::reference_wrapper m_view; + std::reference_wrapper m_view_cancel; + Book m_book; + Clock::time_point m_when; + BookTip m_tip; + Offer m_offer; + + // 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. + void + erase (LedgerView& view) + { + // VFALCO NOTE + // + // This should be using LedgerView::dirDelete, which will + // correctly remove the directory if its the last entry. + // Unfortunately this is a protocol breaking change. + + auto p (view.entryCache (ltDIR_NODE, m_tip.dir())); + + if (p == nullptr) + { + if (m_journal.error) m_journal.error << + "Missing directory " << m_tip.dir() << + " for offer " << m_tip.index(); + return; + } + + auto v (p->getFieldV256 (sfIndexes)); + auto& x (v.peekValue()); + auto it (std::find (x.begin(), x.end(), m_tip.index())); + + if (it == x.end()) + { + if (m_journal.error) m_journal.error << + "Missing offer " << m_tip.index() << + " for directory " << m_tip.dir(); + return; + } + + x.erase (it); + p->setFieldV256 (sfIndexes, v); + view.entryModify (p); + + if (m_journal.trace) m_journal.trace << + "Missing offer " << m_tip.index() << + " removed from directory " << m_tip.dir(); + } + +public: + OfferStream (LedgerView& view, LedgerView& view_cancel, BookRef book, + Clock::time_point when, beast::Journal journal) + : m_journal (journal) + , m_view (view) + , m_view_cancel (view_cancel) + , m_book (book) + , m_when (when) + , m_tip (view, book) + { + } + + LedgerView& + view() noexcept + { + return m_view; + } + + LedgerView& + view_cancel() noexcept + { + return m_view_cancel; + } + + Book const& + book() const noexcept + { + return m_book; + } + +uint256 const& +dir() const noexcept +{ + return m_tip.dir(); +} + + /** Returns the offer at the tip of the order book. + Offers are always presented in decreasing quality. + Only valid if step() returned `true`. + */ + Offer const& + tip() const + { + return m_offer; + } + + /** Advance to the next valid offer. + This automatically removes: + - Offers with missing ledger entries + - Offers found unfunded + - expired offers + @return `true` if there is a valid offer. + */ + bool + step() + { + // Modifying the order or logic of these + // operations causes a protocol breaking change. + + for(;;) + { + // BookTip::step deletes the current offer from the view before + // advancing to the next (unless the ledger entry is missing). + if (! m_tip.step()) + return false; + + SLE::pointer const& entry (m_tip.entry()); + + // Remove if missing + if (! entry) + { + erase (view()); + erase (view_cancel()); + continue; + } + + // Remove if expired + if (entry->isFieldPresent (sfExpiration) && + entry->getFieldU32 (sfExpiration) <= m_when) + { + view_cancel().offerDelete (entry->getIndex()); + if (m_journal.trace) m_journal.trace << + "Removing expired offer " << entry->getIndex(); + continue; + } + + m_offer = Offer (entry, m_tip.quality()); + + Amounts const amount (m_offer.amount()); + + // Remove if either amount is zero + if (amount.empty()) + { + view_cancel().offerDelete (entry->getIndex()); + if (m_journal.warning) m_journal.warning << + "Removing bad offer " << entry->getIndex(); + m_offer = Offer{}; + continue; + } + + // Calculate owner funds + // VFALCO 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)); + + // Check for unfunded offer + if (! owner_funds.isPositive()) + { + // 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) == owner_funds) + { + view_cancel().offerDelete (entry->getIndex()); + if (m_journal.trace) m_journal.trace << + "Removing unfunded offer " << entry->getIndex(); + } + else + { + if (m_journal.trace) m_journal.trace << + "Removing became unfunded offer " << entry->getIndex(); + } + m_offer = Offer{}; + continue; + } + +#if 0 + // Remove if its our own offer + // + // VFALCO NOTE We might not want this for payments + // + if (m_account == owner) + { + view_cancel().offerDelete (entry->getIndex()); + if (m_journal.trace) m_journal.trace << + "Removing self offer " << entry->getIndex(); + continue; + } +#endif + + break; + } + + return true; + } + + /** Updates the offer to reflect remaining funds. + The caller is responsible for following all the rounding rules. + The offer will be considered fully consumed if either the in + or the out amount is zero. + @return `true` If the offer had no funds remaining. + */ + bool + fill (Amounts const& remaining_funds) + { + // Erase the offer if it is fully consumed (in==0 || out==0) + // This is the same as becoming unfunded + return false; + } +}; + +//------------------------------------------------------------------------------ + +/** + Does everything an OfferStream does, and: + - remove offers that became unfunded (if path is used) +*/ +#if 0 +class PaymentOfferStream : public OfferStream +{ +public: + PaymentOfferStream (LedgerView& view_base, BookRef book, + Clock::time_point when, beast::Journal journal) + : m_journal (journal) + , m_view (view_base.duplicate()) + , m_view_apply (view_base.duplicate()) + , m_book (book) + , m_when (when) + { + } +}; +#endif + +//------------------------------------------------------------------------------ + +/* +TakerOfferStream + Does everything a PaymentOfferStream does, and: + - remove offers owned by the taker (if tx succeeds?) +*/ + +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ + +} +} + +/* +OfferStream + - remove offers with missing ledger entries (always) + - remove expired offers (always) + - remove offers found unfunded (always) + +PaymentOfferStream + Does everything an OfferStream does, and: + - remove offers that became unfunded (if path is used) + +TakerOfferStream + Does everything a PaymentOfferStream does, and: + - remove offers owned by the taker (if tx succeeds?) +*/ + +#endif + diff --git a/src/ripple_app/book/Taker.h b/src/ripple_app/book/Taker.h new file mode 100644 index 0000000000..631b28651d --- /dev/null +++ b/src/ripple_app/book/Taker.h @@ -0,0 +1,351 @@ +//------------------------------------------------------------------------------ +/* + 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_CORE_TAKER_H_INCLUDED +#define RIPPLE_CORE_TAKER_H_INCLUDED + +#include "Amounts.h" +#include "Quality.h" +#include "Types.h" + +#include "../../beast/beast/streams/debug_ostream.h" + +#include + +namespace ripple { +namespace core { + +/** State for the active party during order book or payment operations. */ +class Taker +{ +public: + struct Options + { + Options() = default; + + explicit + Options (std::uint32_t tx_flags) + : sell (isSetBit (tx_flags, tfSell)) + , passive (isSetBit (tx_flags, tfPassive)) + { + } + + bool sell; + bool passive; + }; + +private: + std::reference_wrapper m_view; + Book m_book; + Account m_account; + Options m_options; + Quality m_quality; + Quality m_threshold; + + // The original in and out quantities + Amounts m_amount; + + // Amount of input currency remaining. + Amount m_in; + + // Amount of output currency we have received. + Amount m_out; + + // Returns the balance of the taker's input currency, + Amount + funds() const + { + return view().accountFunds (account(), m_in); + } + +public: + Taker (LedgerView& view, BookRef const& book, + Account const& account, Amounts const& amount, + Options const& options) + : m_view (view) + , m_book (book) + , m_account (account) + , m_options (options) + , m_quality (amount) + , m_threshold (m_quality) + , m_amount (amount) + , m_in (amount.in) + , m_out (amount.out.getCurrency(), amount.out.getIssuer()) + { + // If this is a passive order (tfPassive), this prevents + // offers at the same quality level from being consumed. + if (m_options.passive) + --m_threshold; + } + + LedgerView& + view() const noexcept + { + return m_view; + } + + /** Returns the input and output asset pair identifier. */ + Book const& + book() const noexcept + { + return m_book; + } + + /** Returns the account identifier of the taker. */ + Account const& + account() const noexcept + { + return m_account; + } + + /** Returns `true` if order crossing should not continue. + Order processing is stopped if the taker's order quantities have + been reached, or if the taker has run out of input funds. + */ + bool + done() const noexcept + { + if (m_options.sell) + { + // With the sell option, we are finished when + // we have consumed all the input currency. + if (! m_in.isPositive()) + return true; + } + else if (m_out >= m_amount.out) + { + // With the buy option (!sell) we are finished when we + // have received the desired amount of output currency. + return true; + } + + // We are finished if the taker is out of funds + return ! funds().isPositive(); + } + +Quality +threshold() const noexcept +{ + return m_threshold; +} + + /** Returns `true` if the quality does not meet the taker's requirements. */ + bool + reject (Quality const& quality) const noexcept + { + return quality < m_threshold; + } + + /** Calcualtes the result of applying the taker's funds to the offer. + @return The flow and flag indicating if the order was consumed. + */ + std::pair + fill (Offer const& offer) const + { + // Best flow the owner can get. + // Start out assuming entire offer will flow. + Amounts owner_amount (offer.amount()); + + // Limit owner's output by available funds less fees + + // VFALCO TODO Rename accountFounds to make it clear that + // it can return a clamped value. + Amount const owner_funds (view().accountFunds ( + offer.account(), owner_amount.out)); + + // Get fee rate paid by owner + std::uint32_t const owner_charge_rate (view().rippleTransferRate ( + offer.account(), account(), offer.entry()->getFieldAmount ( + sfTakerGets).getIssuer())); + Amount const owner_charge (Amount::saFromRate (owner_charge_rate)); + + // VFALCO Make Amount::divide skip math if v2 == QUALITY_ONE + 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 + { + owner_amount = offer.quality().ceil_out (owner_amount, + Amount::divide (owner_funds, owner_charge)); + } + + // Best flow the taker can get. + // Start out assuming entire offer will flow. + Amounts taker_amount (offer.amount()); + + // Limit taker's input by available funds less fees + + Amount const taker_funds (view().accountFunds (account(), m_in)); + + // Get fee rate paid by taker + std::uint32_t const taker_charge_rate (view().rippleTransferRate ( + account(), offer.account(), offer.entry()->getFieldAmount ( + sfTakerPays).getIssuer())); + Amount const taker_charge (Amount::saFromRate (taker_charge_rate)); + + // VFALCO Make Amount::divide skip math if v2 == QUALITY_ONE + if (taker_charge_rate == QUALITY_ONE) + { + // Skip some math when there's no fee + taker_amount = offer.quality().ceil_in ( + taker_amount, taker_funds); + } + else + { + taker_amount = offer.quality().ceil_in (taker_amount, + Amount::divide (taker_funds, taker_charge)); + } + + // Limit taker's input by options + if (! m_options.sell) + { + assert (m_out < m_amount.out); + taker_amount = offer.quality().ceil_out ( + taker_amount, m_amount.out - m_out); + assert (! taker_amount.in.isZero()); + } + + // Calculate the amount that will flow through the offer + // This does not include the fees. + Amounts const flow ((owner_amount.in < taker_amount.in) ? + owner_amount : taker_amount); + + bool const consumed (flow.out >= owner_amount.out); + + return std::make_pair (flow, consumed); + } + + /** Process the result of fee and funds calculation on the offer. + + To protect the ledger, conditions which should never occur are + checked. If the invariants are broken, the processing fails. + + If processing succeeds, the funds are distributed to the taker, + owner, and issuers. + + @return `false` if processing failed (due to math errors). + */ + TER + process (Amounts const& flow, Offer const& offer) + { + TER result (tesSUCCESS); + + // VFALCO For the case of !sell, is it possible for the taker + // to get a tiny bit more than he asked for? + assert (m_options.sell || flow.out <= m_amount.out); + + // Calculate remaining portion of offer + Amounts const remain ( + offer.entry()->getFieldAmount (sfTakerPays) - flow.in, + offer.entry()->getFieldAmount (sfTakerGets) - flow.out); + + offer.entry()->setFieldAmount (sfTakerPays, remain.in); + offer.entry()->setFieldAmount (sfTakerGets, remain.out); + view().entryModify (offer.entry()); + + // Pay the taker + result = view().accountSend (offer.account(), account(), flow.out); + + if (result == tesSUCCESS) + { + m_out += flow.out; + + // Pay the owner + result = view().accountSend (account(), offer.account(), flow.in); + + if (result == tesSUCCESS) + { + m_in -= flow.in; + } + } + + return result; + } +}; + + +inline +std::ostream& +operator<< (std::ostream& os, Taker const& taker) +{ + return os << taker.account(); +} + +} +} + +/* + +// This code calculates the fees but then we discovered +// that LedgerEntrySet::accountSend does it for you. + +Amounts fees; + +// Calculate taker fee +if (taker_charge_rate == QUALITY_ONE) +{ + // No fee, skip math + fees.in = Amount (flow.in.getCurrency(), + flow.in.getIssuer()); +} +else +{ + // VFALCO TODO Check the units (versus 4-arg version of mulRound) + Amount const in_plus_fees (Amount::mulRound ( + flow.in, taker_charge, true)); + // Make sure the taker has enough to pay the fee + if (in_plus_fees > taker_funds) + { + // Not enough funds, stiff the issuer + fees.in = taker_funds - flow.in; + } + else + { + fees.in = in_plus_fees - flow.in; + } +} + +// Calculate owner fee +if (owner_charge_rate == QUALITY_ONE) +{ + // No fee, skip math + fees.out = Amount (flow.out.getCurrency(), + flow.out.getIssuer()); +} +else +{ + Amount const out_plus_fees (Amount::mulRound ( + flow.out, owner_charge, true)); + // Make sure the owner has enough to pay the fee + if (out_plus_fees > owner_funds) + { + // Not enough funds, stiff the issuer + fees.out = owner_funds - flow.out; + } + else + { + fees.out = out_plus_fees - flow.out; + } +} +*/ + +#endif diff --git a/src/ripple_app/book/tests/OfferStream.test.cpp b/src/ripple_app/book/tests/OfferStream.test.cpp new file mode 100644 index 0000000000..e4afc59c98 --- /dev/null +++ b/src/ripple_app/book/tests/OfferStream.test.cpp @@ -0,0 +1,46 @@ +//------------------------------------------------------------------------------ +/* + 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 "../OfferStream.h" + +#include "../../../beast/beast/unit_test/suite.h" + +namespace ripple { +namespace core { + +class OfferStream_test : public beast::unit_test::suite +{ +public: + void + test() + { + pass(); + } + + void + run() + { + test(); + } +}; + +BEAST_DEFINE_TESTSUITE_MANUAL(OfferStream,core,ripple); + +} +} diff --git a/src/ripple_app/ledger/DirectoryEntryIterator.h b/src/ripple_app/ledger/DirectoryEntryIterator.h index d8e965ce49..7a1e28ccc5 100644 --- a/src/ripple_app/ledger/DirectoryEntryIterator.h +++ b/src/ripple_app/ledger/DirectoryEntryIterator.h @@ -27,12 +27,15 @@ class DirectoryEntryIterator { public: + DirectoryEntryIterator () + : mEntry(0) + { + } - DirectoryEntryIterator () : mEntry(0) - { ; } - - DirectoryEntryIterator (uint256 const& index) : mRootIndex(index), mEntry(0) - { ; } + DirectoryEntryIterator (uint256 const& index) + : mRootIndex(index), mEntry(0) + { + } /** Construct from a reference to the root directory */ @@ -42,24 +45,19 @@ public: mRootIndex = mDirNode->getIndex(); } - /** Get the SLE this iterator currently references - */ + /** Get the SLE this iterator currently references */ SLE::pointer getEntry (LedgerEntrySet& les, LedgerEntryType type); - /** Make this iterator point to the first offer - */ + /** Make this iterator point to the first offer */ bool firstEntry (LedgerEntrySet&); - /** Make this iterator point to the next offer - */ + /** Make this iterator point to the next offer */ bool nextEntry (LedgerEntrySet&); - /** Add this iterator's position to a JSON object - */ + /** Add this iterator's position to a JSON object */ bool addJson (Json::Value&) const; - /** Set this iterator's position from a JSON object - */ + /** Set this iterator's position from a JSON object */ bool setJson (Json::Value const&, LedgerEntrySet& les); uint256 const& getEntryLedgerIndex () const @@ -72,8 +70,19 @@ public: return mDirNode ? mDirNode->getIndex () : uint256(); } -private: + bool + operator== (DirectoryEntryIterator const& other) const + { + return mEntry == other.mEntry && mDirIndex == other.mDirIndex; + } + bool + operator!= (DirectoryEntryIterator const& other) const + { + return ! (*this == other); + } + +private: uint256 mRootIndex; // ledger index of the root directory uint256 mDirIndex; // ledger index of the current directory unsigned int mEntry; // entry index we are on (0 means first is next) diff --git a/src/ripple_app/ledger/OrderBookIterator.h b/src/ripple_app/ledger/OrderBookIterator.h index 0882466170..73e77a433a 100644 --- a/src/ripple_app/ledger/OrderBookIterator.h +++ b/src/ripple_app/ledger/OrderBookIterator.h @@ -29,7 +29,8 @@ class BookDirIterator public: BookDirIterator () - { ; } + { + } BookDirIterator ( uint160 const& uInCurrency, uint160 const& uInIssuer, @@ -93,26 +94,40 @@ public: bool setJson (Json::Value const&); // Does this iterator currently point to a valid directory + explicit operator bool () const { return mOfferDir && (mOfferDir->getIndex() == mIndex); } -private: + bool + operator== (BookDirIterator const& other) const + { + assert (! mIndex.isZero() && ! other.mIndex.isZero()); + return mIndex == other.mIndex; + } + bool + operator!= (BookDirIterator const& other) const + { + return ! (*this == other); + } + +private: uint256 mBase; // The first index a directory in the book can have uint256 mEnd; // The first index a directory in the book cannot have uint256 mIndex; // The index we are currently on SLE::pointer mOfferDir; // The directory page we are currently on }; +//------------------------------------------------------------------------------ + /** An iterator that walks the offers in a book CAUTION: The LedgerEntrySet must remain valid for the life of the iterator */ class OrderBookIterator { public: - OrderBookIterator ( LedgerEntrySet& set, uint160 const& uInCurrency, @@ -121,7 +136,11 @@ public: uint160 const& uOutIssuer) : mEntrySet (set), mDirectoryIterator (uInCurrency, uInIssuer, uOutCurrency, uOutIssuer) - { ; } + { + } + + OrderBookIterator& + operator= (OrderBookIterator const&) = default; bool addJson (Json::Value&) const; @@ -193,8 +212,23 @@ public: return mOfferIterator; } + bool + operator== (OrderBookIterator const& other) const + { + return + std::addressof(mEntrySet) == std::addressof(other.mEntrySet) && + mDirectoryIterator == other.mDirectoryIterator && + mOfferIterator == mOfferIterator; + } + + bool + operator!= (OrderBookIterator const& other) const + { + return ! (*this == other); + } + private: - LedgerEntrySet& mEntrySet; + std::reference_wrapper mEntrySet; BookDirIterator mDirectoryIterator; DirectoryEntryIterator mOfferIterator; }; diff --git a/src/ripple_app/ripple_app_pt4.cpp b/src/ripple_app/ripple_app_pt4.cpp index 8b0bb9ce05..0e39128d71 100644 --- a/src/ripple_app/ripple_app_pt4.cpp +++ b/src/ripple_app/ripple_app_pt4.cpp @@ -38,4 +38,5 @@ #include "tx/TransactionEngine.cpp" #include "tx/TransactionMeta.cpp" +#include "book/tests/OfferStream.test.cpp" #include "book/tests/Quality.test.cpp" diff --git a/src/ripple_app/transactors/AccountSetTransactor.cpp b/src/ripple_app/transactors/AccountSetTransactor.cpp index 0d28f8c180..f20556285a 100644 --- a/src/ripple_app/transactors/AccountSetTransactor.cpp +++ b/src/ripple_app/transactors/AccountSetTransactor.cpp @@ -55,7 +55,7 @@ TER AccountSetTransactor::doApply () if (bSetRequireAuth && !isSetBit (uFlagsIn, lsfRequireAuth)) { - if (!mEngine->getNodes ().dirIsEmpty (Ledger::getOwnerDirIndex (mTxnAccountID))) + if (!mEngine->view().dirIsEmpty (Ledger::getOwnerDirIndex (mTxnAccountID))) { m_journal.trace << "Retry: Owner directory not empty."; diff --git a/src/ripple_app/transactors/OfferCancelTransactor.cpp b/src/ripple_app/transactors/OfferCancelTransactor.cpp index 454def23d1..a44cc6773b 100644 --- a/src/ripple_app/transactors/OfferCancelTransactor.cpp +++ b/src/ripple_app/transactors/OfferCancelTransactor.cpp @@ -57,7 +57,7 @@ TER OfferCancelTransactor::doApply () m_journal.debug << "OfferCancel: uOfferSequence=" << uOfferSequence; - return mEngine->getNodes ().offerDelete (sleOffer); + return mEngine->view ().offerDelete (sleOffer); } m_journal.warning << diff --git a/src/ripple_app/transactors/OfferCreateTransactor.cpp b/src/ripple_app/transactors/OfferCreateTransactor.cpp index 46eaddd48e..e9fe4c8c10 100644 --- a/src/ripple_app/transactors/OfferCreateTransactor.cpp +++ b/src/ripple_app/transactors/OfferCreateTransactor.cpp @@ -68,7 +68,7 @@ bool OfferCreateTransactor::isValidOffer ( m_journal.trace << "isValidOffer: saOfferPays=" << saOfferPays.getFullText (); - saOfferFunds = mEngine->getNodes ().accountFunds (uOfferOwnerID, saOfferPays); + saOfferFunds = mEngine->view ().accountFunds (uOfferOwnerID, saOfferPays); if (!saOfferFunds.isPositive ()) { @@ -353,7 +353,7 @@ TER OfferCreateTransactor::takeOffers ( "takeOffers: bSell: " << bSell << ": against book: " << uBookBase.ToString (); - LedgerEntrySet& lesActive = mEngine->getNodes (); + LedgerEntrySet& lesActive = mEngine->view (); std::uint64_t const uTakeQuality = STAmount::getRate (saTakerGets, saTakerPays); STAmount saTakerRate = STAmount::setRate (uTakeQuality); uint160 const uTakerPaysAccountID = saTakerPays.getIssuer (); @@ -776,7 +776,7 @@ TER OfferCreateTransactor::doApply () std::uint64_t uOwnerNode; std::uint64_t uBookNode; - LedgerEntrySet& lesActive = mEngine->getNodes (); + LedgerEntrySet& lesActive = mEngine->view (); LedgerEntrySet lesCheckpoint = lesActive; // Checkpoint with just fees paid. lesActive.bumpSeq (); // Begin ledger variance. @@ -869,7 +869,7 @@ TER OfferCreateTransactor::doApply () m_journal.warning << "uCancelSequence=" << uCancelSequence; - terResult = mEngine->getNodes ().offerDelete (sleCancel); + terResult = mEngine->view ().offerDelete (sleCancel); } else { diff --git a/src/ripple_app/transactors/PaymentTransactor.cpp b/src/ripple_app/transactors/PaymentTransactor.cpp index 16a0971efb..b6628c4267 100644 --- a/src/ripple_app/transactors/PaymentTransactor.cpp +++ b/src/ripple_app/transactors/PaymentTransactor.cpp @@ -217,7 +217,7 @@ TER PaymentTransactor::doApply () terResult = openLedger && tooManyPaths ? telBAD_PATH_COUNT // Too many paths for proposed ledger. : RippleCalc::rippleCalc ( - mEngine->getNodes (), + mEngine->view (), saMaxAmountAct, saDstAmountAct, vpsExpanded, @@ -236,7 +236,7 @@ TER PaymentTransactor::doApply () terResult = tecPATH_DRY; if ((tesSUCCESS == terResult) && (saDstAmountAct != saDstAmount)) - mEngine->getNodes().setDeliveredAmount (saDstAmountAct); + mEngine->view().setDeliveredAmount (saDstAmountAct); } catch (std::exception const& e) { diff --git a/src/ripple_app/transactors/TrustSetTransactor.cpp b/src/ripple_app/transactors/TrustSetTransactor.cpp index 3db0beb98f..d6c8429d91 100644 --- a/src/ripple_app/transactors/TrustSetTransactor.cpp +++ b/src/ripple_app/transactors/TrustSetTransactor.cpp @@ -101,7 +101,7 @@ TER TrustSetTransactor::doApply () m_journal.warning << "Clearing redundant line."; - return mEngine->getNodes ().trustDelete ( + return mEngine->view ().trustDelete ( selDelete, mTxnAccountID, uDstAccountID); } else @@ -273,7 +273,7 @@ TER TrustSetTransactor::doApply () { // Set reserve for low account. - mEngine->getNodes ().ownerCountAdjust (uLowAccountID, 1, sleLowAccount); + mEngine->view ().ownerCountAdjust (uLowAccountID, 1, sleLowAccount); uFlagsOut |= lsfLowReserve; if (!bHigh) @@ -284,7 +284,7 @@ TER TrustSetTransactor::doApply () { // Clear reserve for low account. - mEngine->getNodes ().ownerCountAdjust (uLowAccountID, -1, sleLowAccount); + mEngine->view ().ownerCountAdjust (uLowAccountID, -1, sleLowAccount); uFlagsOut &= ~lsfLowReserve; } @@ -292,7 +292,7 @@ TER TrustSetTransactor::doApply () { // Set reserve for high account. - mEngine->getNodes ().ownerCountAdjust (uHighAccountID, 1, sleHighAccount); + mEngine->view ().ownerCountAdjust (uHighAccountID, 1, sleHighAccount); uFlagsOut |= lsfHighReserve; if (bHigh) @@ -303,7 +303,7 @@ TER TrustSetTransactor::doApply () { // Clear reserve for high account. - mEngine->getNodes ().ownerCountAdjust (uHighAccountID, -1, sleHighAccount); + mEngine->view ().ownerCountAdjust (uHighAccountID, -1, sleHighAccount); uFlagsOut &= ~lsfHighReserve; } @@ -314,7 +314,7 @@ TER TrustSetTransactor::doApply () { // Delete. - terResult = mEngine->getNodes ().trustDelete (sleRippleState, uLowAccountID, uHighAccountID); + terResult = mEngine->view ().trustDelete (sleRippleState, uLowAccountID, uHighAccountID); } else if (bReserveIncrease && mPriorBalance.getNValue () < uReserveCreate) // Reserve is not scaled by load. @@ -366,7 +366,7 @@ TER TrustSetTransactor::doApply () Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, uCurrencyID).ToString (); // Create a new ripple line. - terResult = mEngine->getNodes ().trustCreate ( + terResult = mEngine->view ().trustCreate ( bHigh, mTxnAccountID, uDstAccountID, diff --git a/src/ripple_app/tx/TransactionEngine.h b/src/ripple_app/tx/TransactionEngine.h index 825b5af41d..b218414f54 100644 --- a/src/ripple_app/tx/TransactionEngine.h +++ b/src/ripple_app/tx/TransactionEngine.h @@ -70,7 +70,7 @@ public: assert (mLedger); } - LedgerEntrySet& getNodes () + LedgerEntrySet& view () { return mNodes; } @@ -84,19 +84,23 @@ public: mLedger = ledger; } - SLE::pointer entryCreate (LedgerEntryType type, uint256 const & index) + // VFALCO TODO Remove these pointless wrappers + SLE::pointer entryCreate (LedgerEntryType type, uint256 const & index) { return mNodes.entryCreate (type, index); } - SLE::pointer entryCache (LedgerEntryType type, uint256 const & index) + + SLE::pointer entryCache (LedgerEntryType type, uint256 const & index) { return mNodes.entryCache (type, index); } - void entryDelete (SLE::ref sleEntry) + + void entryDelete (SLE::ref sleEntry) { mNodes.entryDelete (sleEntry); } - void entryModify (SLE::ref sleEntry) + + void entryModify (SLE::ref sleEntry) { mNodes.entryModify (sleEntry); } diff --git a/src/ripple_data/protocol/SerializedTypes.h b/src/ripple_data/protocol/SerializedTypes.h index affc088248..a0201dff72 100644 --- a/src/ripple_data/protocol/SerializedTypes.h +++ b/src/ripple_data/protocol/SerializedTypes.h @@ -635,8 +635,10 @@ public: { if (!isZero ()) mIsNegative = !mIsNegative; } + void zero () { + // VFALCO: Why -100? mOffset = mIsNative ? 0 : -100; mValue = 0; mIsNegative = false; @@ -733,6 +735,11 @@ public: return multiply (v1, v2, v1); } + /* addRound, subRound can end up rounding if the amount subtracted is too small + to make a change. Consder (X-d) where d is very small relative to X. + If you ask to round down, then (X-d) should not be X unless d is zero. + If you ask to round up, (X+d) should never be X unless d is zero. (Assuming X and d are positive). + */ // Add, subtract, multiply, or divide rounding result in specified direction static STAmount addRound (const STAmount& v1, const STAmount& v2, bool roundUp); static STAmount subRound (const STAmount& v1, const STAmount& v2, bool roundUp);