mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-21 03:26:01 +00:00
New classes for processing offers in order books:
* BookTip provides consume-and-step offer traversal * OfferStream applies offer business rules and presents offers to callers * Taker class manages state for the active party during order processing * Offer class wraps book offers for presentation
This commit is contained in:
@@ -820,6 +820,12 @@
|
|||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="..\..\src\ripple\validators\ripple_validators.cpp" />
|
<ClCompile Include="..\..\src\ripple\validators\ripple_validators.cpp" />
|
||||||
|
<ClCompile Include="..\..\src\ripple_app\book\tests\OfferStream.test.cpp">
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||||
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="..\..\src\ripple_app\book\tests\Quality.test.cpp">
|
<ClCompile Include="..\..\src\ripple_app\book\tests\Quality.test.cpp">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||||
@@ -2485,7 +2491,11 @@
|
|||||||
<ClInclude Include="..\..\src\ripple\validators\ripple_validators.h" />
|
<ClInclude Include="..\..\src\ripple\validators\ripple_validators.h" />
|
||||||
<ClInclude Include="..\..\src\ripple_app\book\Amount.h" />
|
<ClInclude Include="..\..\src\ripple_app\book\Amount.h" />
|
||||||
<ClInclude Include="..\..\src\ripple_app\book\Amounts.h" />
|
<ClInclude Include="..\..\src\ripple_app\book\Amounts.h" />
|
||||||
|
<ClInclude Include="..\..\src\ripple_app\book\BookTip.h" />
|
||||||
|
<ClInclude Include="..\..\src\ripple_app\book\Offer.h" />
|
||||||
|
<ClInclude Include="..\..\src\ripple_app\book\OfferStream.h" />
|
||||||
<ClInclude Include="..\..\src\ripple_app\book\Quality.h" />
|
<ClInclude Include="..\..\src\ripple_app\book\Quality.h" />
|
||||||
|
<ClInclude Include="..\..\src\ripple_app\book\Taker.h" />
|
||||||
<ClInclude Include="..\..\src\ripple_app\book\Types.h" />
|
<ClInclude Include="..\..\src\ripple_app\book\Types.h" />
|
||||||
<ClInclude Include="..\..\src\ripple_app\consensus\DisputedTx.h" />
|
<ClInclude Include="..\..\src\ripple_app\consensus\DisputedTx.h" />
|
||||||
<ClInclude Include="..\..\src\ripple_app\consensus\LedgerConsensus.h" />
|
<ClInclude Include="..\..\src\ripple_app\consensus\LedgerConsensus.h" />
|
||||||
|
|||||||
@@ -1515,6 +1515,9 @@
|
|||||||
<ClCompile Include="..\..\src\ripple_app\book\tests\Quality.test.cpp">
|
<ClCompile Include="..\..\src\ripple_app\book\tests\Quality.test.cpp">
|
||||||
<Filter>[2] Old Ripple\ripple_app\book\tests</Filter>
|
<Filter>[2] Old Ripple\ripple_app\book\tests</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\src\ripple_app\book\tests\OfferStream.test.cpp">
|
||||||
|
<Filter>[2] Old Ripple\ripple_app\book\tests</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="..\..\src\ripple_basics\containers\RangeSet.h">
|
<ClInclude Include="..\..\src\ripple_basics\containers\RangeSet.h">
|
||||||
@@ -3090,6 +3093,18 @@
|
|||||||
<ClInclude Include="..\..\src\ripple_app\book\Types.h">
|
<ClInclude Include="..\..\src\ripple_app\book\Types.h">
|
||||||
<Filter>[2] Old Ripple\ripple_app\book</Filter>
|
<Filter>[2] Old Ripple\ripple_app\book</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\src\ripple_app\book\OfferStream.h">
|
||||||
|
<Filter>[2] Old Ripple\ripple_app\book</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\src\ripple_app\book\Taker.h">
|
||||||
|
<Filter>[2] Old Ripple\ripple_app\book</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\src\ripple_app\book\BookTip.h">
|
||||||
|
<Filter>[2] Old Ripple\ripple_app\book</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="..\..\src\ripple_app\book\Offer.h">
|
||||||
|
<Filter>[2] Old Ripple\ripple_app\book</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<CustomBuild Include="..\..\src\ripple_data\protocol\ripple.proto">
|
<CustomBuild Include="..\..\src\ripple_data\protocol\ripple.proto">
|
||||||
|
|||||||
@@ -75,11 +75,10 @@ public:
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Assignment.
|
/** Assignment. */
|
||||||
This is only valid when ByValue == `true`
|
template <bool MaybeByValue = ByValue, bool OtherByValue>
|
||||||
*/
|
std::enable_if_t <MaybeByValue, RippleAssetType&>
|
||||||
template <bool OtherByValue>
|
operator= (RippleAssetType <OtherByValue> const& other)
|
||||||
RippleAssetType& operator= (RippleAssetType <OtherByValue> const& other)
|
|
||||||
{
|
{
|
||||||
currency = other.currency;
|
currency = other.currency;
|
||||||
issuer = other.issuer;
|
issuer = other.issuer;
|
||||||
|
|||||||
144
src/ripple_app/book/BookTip.h
Normal file
144
src/ripple_app/book/BookTip.h
Normal file
@@ -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 <ostream>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
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 <LedgerView> 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
|
||||||
104
src/ripple_app/book/Offer.h
Normal file
104
src/ripple_app/book/Offer.h
Normal file
@@ -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 <ostream>
|
||||||
|
|
||||||
|
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
|
||||||
324
src/ripple_app/book/OfferStream.h
Normal file
324
src/ripple_app/book/OfferStream.h
Normal file
@@ -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 <ostream>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
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 <LedgerView> m_view;
|
||||||
|
std::reference_wrapper <LedgerView> 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
|
||||||
|
|
||||||
351
src/ripple_app/book/Taker.h
Normal file
351
src/ripple_app/book/Taker.h
Normal file
@@ -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 <utility>
|
||||||
|
|
||||||
|
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 <LedgerView> 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 <Amounts, bool>
|
||||||
|
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
|
||||||
46
src/ripple_app/book/tests/OfferStream.test.cpp
Normal file
46
src/ripple_app/book/tests/OfferStream.test.cpp
Normal file
@@ -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);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,12 +27,15 @@ class DirectoryEntryIterator
|
|||||||
{
|
{
|
||||||
|
|
||||||
public:
|
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
|
/** Construct from a reference to the root directory
|
||||||
*/
|
*/
|
||||||
@@ -42,24 +45,19 @@ public:
|
|||||||
mRootIndex = mDirNode->getIndex();
|
mRootIndex = mDirNode->getIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the SLE this iterator currently references
|
/** Get the SLE this iterator currently references */
|
||||||
*/
|
|
||||||
SLE::pointer getEntry (LedgerEntrySet& les, LedgerEntryType type);
|
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&);
|
bool firstEntry (LedgerEntrySet&);
|
||||||
|
|
||||||
/** Make this iterator point to the next offer
|
/** Make this iterator point to the next offer */
|
||||||
*/
|
|
||||||
bool nextEntry (LedgerEntrySet&);
|
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;
|
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);
|
bool setJson (Json::Value const&, LedgerEntrySet& les);
|
||||||
|
|
||||||
uint256 const& getEntryLedgerIndex () const
|
uint256 const& getEntryLedgerIndex () const
|
||||||
@@ -72,8 +70,19 @@ public:
|
|||||||
return mDirNode ? mDirNode->getIndex () : uint256();
|
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 mRootIndex; // ledger index of the root directory
|
||||||
uint256 mDirIndex; // ledger index of the current directory
|
uint256 mDirIndex; // ledger index of the current directory
|
||||||
unsigned int mEntry; // entry index we are on (0 means first is next)
|
unsigned int mEntry; // entry index we are on (0 means first is next)
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ class BookDirIterator
|
|||||||
public:
|
public:
|
||||||
|
|
||||||
BookDirIterator ()
|
BookDirIterator ()
|
||||||
{ ; }
|
{
|
||||||
|
}
|
||||||
|
|
||||||
BookDirIterator (
|
BookDirIterator (
|
||||||
uint160 const& uInCurrency, uint160 const& uInIssuer,
|
uint160 const& uInCurrency, uint160 const& uInIssuer,
|
||||||
@@ -93,26 +94,40 @@ public:
|
|||||||
bool setJson (Json::Value const&);
|
bool setJson (Json::Value const&);
|
||||||
|
|
||||||
// Does this iterator currently point to a valid directory
|
// Does this iterator currently point to a valid directory
|
||||||
|
explicit
|
||||||
operator bool () const
|
operator bool () const
|
||||||
{
|
{
|
||||||
return mOfferDir && (mOfferDir->getIndex() == mIndex);
|
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 mBase; // The first index a directory in the book can have
|
||||||
uint256 mEnd; // The first index a directory in the book cannot have
|
uint256 mEnd; // The first index a directory in the book cannot have
|
||||||
uint256 mIndex; // The index we are currently on
|
uint256 mIndex; // The index we are currently on
|
||||||
SLE::pointer mOfferDir; // The directory page we are currently on
|
SLE::pointer mOfferDir; // The directory page we are currently on
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
/** An iterator that walks the offers in a book
|
/** An iterator that walks the offers in a book
|
||||||
CAUTION: The LedgerEntrySet must remain valid for the life of the iterator
|
CAUTION: The LedgerEntrySet must remain valid for the life of the iterator
|
||||||
*/
|
*/
|
||||||
class OrderBookIterator
|
class OrderBookIterator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
OrderBookIterator (
|
OrderBookIterator (
|
||||||
LedgerEntrySet& set,
|
LedgerEntrySet& set,
|
||||||
uint160 const& uInCurrency,
|
uint160 const& uInCurrency,
|
||||||
@@ -121,7 +136,11 @@ public:
|
|||||||
uint160 const& uOutIssuer) :
|
uint160 const& uOutIssuer) :
|
||||||
mEntrySet (set),
|
mEntrySet (set),
|
||||||
mDirectoryIterator (uInCurrency, uInIssuer, uOutCurrency, uOutIssuer)
|
mDirectoryIterator (uInCurrency, uInIssuer, uOutCurrency, uOutIssuer)
|
||||||
{ ; }
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
OrderBookIterator&
|
||||||
|
operator= (OrderBookIterator const&) = default;
|
||||||
|
|
||||||
bool addJson (Json::Value&) const;
|
bool addJson (Json::Value&) const;
|
||||||
|
|
||||||
@@ -193,8 +212,23 @@ public:
|
|||||||
return mOfferIterator;
|
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:
|
private:
|
||||||
LedgerEntrySet& mEntrySet;
|
std::reference_wrapper <LedgerEntrySet> mEntrySet;
|
||||||
BookDirIterator mDirectoryIterator;
|
BookDirIterator mDirectoryIterator;
|
||||||
DirectoryEntryIterator mOfferIterator;
|
DirectoryEntryIterator mOfferIterator;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -38,4 +38,5 @@
|
|||||||
#include "tx/TransactionEngine.cpp"
|
#include "tx/TransactionEngine.cpp"
|
||||||
#include "tx/TransactionMeta.cpp"
|
#include "tx/TransactionMeta.cpp"
|
||||||
|
|
||||||
|
#include "book/tests/OfferStream.test.cpp"
|
||||||
#include "book/tests/Quality.test.cpp"
|
#include "book/tests/Quality.test.cpp"
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ TER AccountSetTransactor::doApply ()
|
|||||||
|
|
||||||
if (bSetRequireAuth && !isSetBit (uFlagsIn, lsfRequireAuth))
|
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.";
|
m_journal.trace << "Retry: Owner directory not empty.";
|
||||||
|
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ TER OfferCancelTransactor::doApply ()
|
|||||||
m_journal.debug <<
|
m_journal.debug <<
|
||||||
"OfferCancel: uOfferSequence=" << uOfferSequence;
|
"OfferCancel: uOfferSequence=" << uOfferSequence;
|
||||||
|
|
||||||
return mEngine->getNodes ().offerDelete (sleOffer);
|
return mEngine->view ().offerDelete (sleOffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_journal.warning <<
|
m_journal.warning <<
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ bool OfferCreateTransactor::isValidOffer (
|
|||||||
m_journal.trace <<
|
m_journal.trace <<
|
||||||
"isValidOffer: saOfferPays=" << saOfferPays.getFullText ();
|
"isValidOffer: saOfferPays=" << saOfferPays.getFullText ();
|
||||||
|
|
||||||
saOfferFunds = mEngine->getNodes ().accountFunds (uOfferOwnerID, saOfferPays);
|
saOfferFunds = mEngine->view ().accountFunds (uOfferOwnerID, saOfferPays);
|
||||||
|
|
||||||
if (!saOfferFunds.isPositive ())
|
if (!saOfferFunds.isPositive ())
|
||||||
{
|
{
|
||||||
@@ -353,7 +353,7 @@ TER OfferCreateTransactor::takeOffers (
|
|||||||
"takeOffers: bSell: " << bSell <<
|
"takeOffers: bSell: " << bSell <<
|
||||||
": against book: " << uBookBase.ToString ();
|
": against book: " << uBookBase.ToString ();
|
||||||
|
|
||||||
LedgerEntrySet& lesActive = mEngine->getNodes ();
|
LedgerEntrySet& lesActive = mEngine->view ();
|
||||||
std::uint64_t const uTakeQuality = STAmount::getRate (saTakerGets, saTakerPays);
|
std::uint64_t const uTakeQuality = STAmount::getRate (saTakerGets, saTakerPays);
|
||||||
STAmount saTakerRate = STAmount::setRate (uTakeQuality);
|
STAmount saTakerRate = STAmount::setRate (uTakeQuality);
|
||||||
uint160 const uTakerPaysAccountID = saTakerPays.getIssuer ();
|
uint160 const uTakerPaysAccountID = saTakerPays.getIssuer ();
|
||||||
@@ -776,7 +776,7 @@ TER OfferCreateTransactor::doApply ()
|
|||||||
std::uint64_t uOwnerNode;
|
std::uint64_t uOwnerNode;
|
||||||
std::uint64_t uBookNode;
|
std::uint64_t uBookNode;
|
||||||
|
|
||||||
LedgerEntrySet& lesActive = mEngine->getNodes ();
|
LedgerEntrySet& lesActive = mEngine->view ();
|
||||||
LedgerEntrySet lesCheckpoint = lesActive; // Checkpoint with just fees paid.
|
LedgerEntrySet lesCheckpoint = lesActive; // Checkpoint with just fees paid.
|
||||||
lesActive.bumpSeq (); // Begin ledger variance.
|
lesActive.bumpSeq (); // Begin ledger variance.
|
||||||
|
|
||||||
@@ -869,7 +869,7 @@ TER OfferCreateTransactor::doApply ()
|
|||||||
m_journal.warning <<
|
m_journal.warning <<
|
||||||
"uCancelSequence=" << uCancelSequence;
|
"uCancelSequence=" << uCancelSequence;
|
||||||
|
|
||||||
terResult = mEngine->getNodes ().offerDelete (sleCancel);
|
terResult = mEngine->view ().offerDelete (sleCancel);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ TER PaymentTransactor::doApply ()
|
|||||||
terResult = openLedger && tooManyPaths
|
terResult = openLedger && tooManyPaths
|
||||||
? telBAD_PATH_COUNT // Too many paths for proposed ledger.
|
? telBAD_PATH_COUNT // Too many paths for proposed ledger.
|
||||||
: RippleCalc::rippleCalc (
|
: RippleCalc::rippleCalc (
|
||||||
mEngine->getNodes (),
|
mEngine->view (),
|
||||||
saMaxAmountAct,
|
saMaxAmountAct,
|
||||||
saDstAmountAct,
|
saDstAmountAct,
|
||||||
vpsExpanded,
|
vpsExpanded,
|
||||||
@@ -236,7 +236,7 @@ TER PaymentTransactor::doApply ()
|
|||||||
terResult = tecPATH_DRY;
|
terResult = tecPATH_DRY;
|
||||||
|
|
||||||
if ((tesSUCCESS == terResult) && (saDstAmountAct != saDstAmount))
|
if ((tesSUCCESS == terResult) && (saDstAmountAct != saDstAmount))
|
||||||
mEngine->getNodes().setDeliveredAmount (saDstAmountAct);
|
mEngine->view().setDeliveredAmount (saDstAmountAct);
|
||||||
}
|
}
|
||||||
catch (std::exception const& e)
|
catch (std::exception const& e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ TER TrustSetTransactor::doApply ()
|
|||||||
m_journal.warning <<
|
m_journal.warning <<
|
||||||
"Clearing redundant line.";
|
"Clearing redundant line.";
|
||||||
|
|
||||||
return mEngine->getNodes ().trustDelete (
|
return mEngine->view ().trustDelete (
|
||||||
selDelete, mTxnAccountID, uDstAccountID);
|
selDelete, mTxnAccountID, uDstAccountID);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -273,7 +273,7 @@ TER TrustSetTransactor::doApply ()
|
|||||||
{
|
{
|
||||||
// Set reserve for low account.
|
// Set reserve for low account.
|
||||||
|
|
||||||
mEngine->getNodes ().ownerCountAdjust (uLowAccountID, 1, sleLowAccount);
|
mEngine->view ().ownerCountAdjust (uLowAccountID, 1, sleLowAccount);
|
||||||
uFlagsOut |= lsfLowReserve;
|
uFlagsOut |= lsfLowReserve;
|
||||||
|
|
||||||
if (!bHigh)
|
if (!bHigh)
|
||||||
@@ -284,7 +284,7 @@ TER TrustSetTransactor::doApply ()
|
|||||||
{
|
{
|
||||||
// Clear reserve for low account.
|
// Clear reserve for low account.
|
||||||
|
|
||||||
mEngine->getNodes ().ownerCountAdjust (uLowAccountID, -1, sleLowAccount);
|
mEngine->view ().ownerCountAdjust (uLowAccountID, -1, sleLowAccount);
|
||||||
uFlagsOut &= ~lsfLowReserve;
|
uFlagsOut &= ~lsfLowReserve;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +292,7 @@ TER TrustSetTransactor::doApply ()
|
|||||||
{
|
{
|
||||||
// Set reserve for high account.
|
// Set reserve for high account.
|
||||||
|
|
||||||
mEngine->getNodes ().ownerCountAdjust (uHighAccountID, 1, sleHighAccount);
|
mEngine->view ().ownerCountAdjust (uHighAccountID, 1, sleHighAccount);
|
||||||
uFlagsOut |= lsfHighReserve;
|
uFlagsOut |= lsfHighReserve;
|
||||||
|
|
||||||
if (bHigh)
|
if (bHigh)
|
||||||
@@ -303,7 +303,7 @@ TER TrustSetTransactor::doApply ()
|
|||||||
{
|
{
|
||||||
// Clear reserve for high account.
|
// Clear reserve for high account.
|
||||||
|
|
||||||
mEngine->getNodes ().ownerCountAdjust (uHighAccountID, -1, sleHighAccount);
|
mEngine->view ().ownerCountAdjust (uHighAccountID, -1, sleHighAccount);
|
||||||
uFlagsOut &= ~lsfHighReserve;
|
uFlagsOut &= ~lsfHighReserve;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,7 +314,7 @@ TER TrustSetTransactor::doApply ()
|
|||||||
{
|
{
|
||||||
// Delete.
|
// Delete.
|
||||||
|
|
||||||
terResult = mEngine->getNodes ().trustDelete (sleRippleState, uLowAccountID, uHighAccountID);
|
terResult = mEngine->view ().trustDelete (sleRippleState, uLowAccountID, uHighAccountID);
|
||||||
}
|
}
|
||||||
else if (bReserveIncrease
|
else if (bReserveIncrease
|
||||||
&& mPriorBalance.getNValue () < uReserveCreate) // Reserve is not scaled by load.
|
&& mPriorBalance.getNValue () < uReserveCreate) // Reserve is not scaled by load.
|
||||||
@@ -366,7 +366,7 @@ TER TrustSetTransactor::doApply ()
|
|||||||
Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, uCurrencyID).ToString ();
|
Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, uCurrencyID).ToString ();
|
||||||
|
|
||||||
// Create a new ripple line.
|
// Create a new ripple line.
|
||||||
terResult = mEngine->getNodes ().trustCreate (
|
terResult = mEngine->view ().trustCreate (
|
||||||
bHigh,
|
bHigh,
|
||||||
mTxnAccountID,
|
mTxnAccountID,
|
||||||
uDstAccountID,
|
uDstAccountID,
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ public:
|
|||||||
assert (mLedger);
|
assert (mLedger);
|
||||||
}
|
}
|
||||||
|
|
||||||
LedgerEntrySet& getNodes ()
|
LedgerEntrySet& view ()
|
||||||
{
|
{
|
||||||
return mNodes;
|
return mNodes;
|
||||||
}
|
}
|
||||||
@@ -84,19 +84,23 @@ public:
|
|||||||
mLedger = ledger;
|
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);
|
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);
|
return mNodes.entryCache (type, index);
|
||||||
}
|
}
|
||||||
void entryDelete (SLE::ref sleEntry)
|
|
||||||
|
void entryDelete (SLE::ref sleEntry)
|
||||||
{
|
{
|
||||||
mNodes.entryDelete (sleEntry);
|
mNodes.entryDelete (sleEntry);
|
||||||
}
|
}
|
||||||
void entryModify (SLE::ref sleEntry)
|
|
||||||
|
void entryModify (SLE::ref sleEntry)
|
||||||
{
|
{
|
||||||
mNodes.entryModify (sleEntry);
|
mNodes.entryModify (sleEntry);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -635,8 +635,10 @@ public:
|
|||||||
{
|
{
|
||||||
if (!isZero ()) mIsNegative = !mIsNegative;
|
if (!isZero ()) mIsNegative = !mIsNegative;
|
||||||
}
|
}
|
||||||
|
|
||||||
void zero ()
|
void zero ()
|
||||||
{
|
{
|
||||||
|
// VFALCO: Why -100?
|
||||||
mOffset = mIsNative ? 0 : -100;
|
mOffset = mIsNative ? 0 : -100;
|
||||||
mValue = 0;
|
mValue = 0;
|
||||||
mIsNegative = false;
|
mIsNegative = false;
|
||||||
@@ -733,6 +735,11 @@ public:
|
|||||||
return multiply (v1, v2, v1);
|
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
|
// Add, subtract, multiply, or divide rounding result in specified direction
|
||||||
static STAmount addRound (const STAmount& v1, const STAmount& v2, bool roundUp);
|
static STAmount addRound (const STAmount& v1, const STAmount& v2, bool roundUp);
|
||||||
static STAmount subRound (const STAmount& v1, const STAmount& v2, bool roundUp);
|
static STAmount subRound (const STAmount& v1, const STAmount& v2, bool roundUp);
|
||||||
|
|||||||
Reference in New Issue
Block a user