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