Support switchover from 0.27 to 0.28 processing semantics based on time:

Changes made to support autobridging and improve the offer-crossing and
pathfinding logic result in transaction-breaking changes which cause
incompatibilities between 0.27 and 0.28 builds of RippleD.

This patch simplifies deployment of 0.28 on the Ripple network by allowing
RippleD to emulate the 0.27 semantics while the last closed ledger closed
before March 30, 2015 at 13:00:00 PDT, after which time the new 0.28
semantics will become active.

The transaction-breaking changes addressed in this commit are:
    3ccbd7c9b2
    b203db27a4
This commit is contained in:
Nik Bougalis
2015-03-05 20:17:35 -08:00
committed by Tom Ritchford
parent eaa1f47f00
commit 8f88d915ba
22 changed files with 2577 additions and 1 deletions

View File

@@ -2317,6 +2317,44 @@
</ClInclude>
<ClInclude Include="..\..\src\ripple\json\Writer.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\legacy\0.27\book\Amount.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\legacy\0.27\book\Amounts.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\legacy\0.27\book\BookTip.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\legacy\0.27\book\impl\BookTip.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\legacy\0.27\book\impl\OfferStream.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\legacy\0.27\book\impl\Quality.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\legacy\0.27\book\impl\Taker.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>
<ClInclude Include="..\..\src\ripple\legacy\0.27\book\Offer.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\legacy\0.27\book\OfferStream.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\legacy\0.27\book\Quality.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\legacy\0.27\book\Taker.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\legacy\0.27\book\Types.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\legacy\0.27\CreateOffer.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>
<ClInclude Include="..\..\src\ripple\legacy\0.27\CreateOffer.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\legacy\0.27\Emulate027.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>
<ClInclude Include="..\..\src\ripple\legacy\0.27\Emulate027.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\net\HTTPClient.h">
</ClInclude>
<ClInclude Include="..\..\src\ripple\net\HTTPRequest.h">
@@ -3410,6 +3448,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\unity\legacy.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\unity\lz4.c">
</ClCompile>
<ClCompile Include="..\..\src\ripple\unity\net.cpp">

View File

@@ -355,6 +355,18 @@
<Filter Include="ripple\json\tests">
<UniqueIdentifier>{BA646284-836B-B151-F2AA-D18535D6F3C1}</UniqueIdentifier>
</Filter>
<Filter Include="ripple\legacy">
<UniqueIdentifier>{312AABAF-FDD8-31C7-C7DB-022D83CEA84F}</UniqueIdentifier>
</Filter>
<Filter Include="ripple\legacy\0.27">
<UniqueIdentifier>{F042ABF9-9B55-1118-07DA-1EECFBF1237B}</UniqueIdentifier>
</Filter>
<Filter Include="ripple\legacy\0.27\book">
<UniqueIdentifier>{73645AAD-9270-EE60-82A6-DFD2DBF65D6C}</UniqueIdentifier>
</Filter>
<Filter Include="ripple\legacy\0.27\book\impl">
<UniqueIdentifier>{381C3650-0CBD-536A-DE2D-301216CB03A6}</UniqueIdentifier>
</Filter>
<Filter Include="ripple\net">
<UniqueIdentifier>{6649BD29-BE86-723F-501A-045E39310112}</UniqueIdentifier>
</Filter>
@@ -3072,6 +3084,54 @@
<ClInclude Include="..\..\src\ripple\json\Writer.h">
<Filter>ripple\json</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\legacy\0.27\book\Amount.h">
<Filter>ripple\legacy\0.27\book</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\legacy\0.27\book\Amounts.h">
<Filter>ripple\legacy\0.27\book</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\legacy\0.27\book\BookTip.h">
<Filter>ripple\legacy\0.27\book</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\legacy\0.27\book\impl\BookTip.cpp">
<Filter>ripple\legacy\0.27\book\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\legacy\0.27\book\impl\OfferStream.cpp">
<Filter>ripple\legacy\0.27\book\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\legacy\0.27\book\impl\Quality.cpp">
<Filter>ripple\legacy\0.27\book\impl</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\legacy\0.27\book\impl\Taker.cpp">
<Filter>ripple\legacy\0.27\book\impl</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\legacy\0.27\book\Offer.h">
<Filter>ripple\legacy\0.27\book</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\legacy\0.27\book\OfferStream.h">
<Filter>ripple\legacy\0.27\book</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\legacy\0.27\book\Quality.h">
<Filter>ripple\legacy\0.27\book</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\legacy\0.27\book\Taker.h">
<Filter>ripple\legacy\0.27\book</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\legacy\0.27\book\Types.h">
<Filter>ripple\legacy\0.27\book</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\legacy\0.27\CreateOffer.cpp">
<Filter>ripple\legacy\0.27</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\legacy\0.27\CreateOffer.h">
<Filter>ripple\legacy\0.27</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\legacy\0.27\Emulate027.cpp">
<Filter>ripple\legacy\0.27</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\legacy\0.27\Emulate027.h">
<Filter>ripple\legacy\0.27</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ripple\net\HTTPClient.h">
<Filter>ripple\net</Filter>
</ClInclude>
@@ -4185,6 +4245,9 @@
<ClCompile Include="..\..\src\ripple\unity\json.cpp">
<Filter>ripple\unity</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\unity\legacy.cpp">
<Filter>ripple\unity</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\unity\lz4.c">
<Filter>ripple\unity</Filter>
</ClCompile>

View File

@@ -603,6 +603,8 @@ for tu_style in ['classic', 'unity']:
*list_sources('src/ripple/crypto', '.cpp'))
object_builder.add_source_files(
*list_sources('src/ripple/json', '.cpp'))
object_builder.add_source_files(
*list_sources('src/ripple/legacy', '.cpp'))
object_builder.add_source_files(
*list_sources('src/ripple/net', '.cpp'))
object_builder.add_source_files(
@@ -641,6 +643,7 @@ for tu_style in ['classic', 'unity']:
'src/ripple/unity/json.cpp',
'src/ripple/unity/protocol.cpp',
'src/ripple/unity/shamap.cpp',
'src/ripple/unity/legacy.cpp',
)
object_builder.add_source_files(

View File

@@ -21,6 +21,7 @@
#include <ripple/app/book/Quality.h>
#include <ripple/app/paths/cursor/RippleLiquidity.h>
#include <ripple/basics/Log.h>
#include <ripple/legacy/0.27/Emulate027.h>
namespace ripple {
namespace path {
@@ -473,7 +474,14 @@ TER PathCursor::forwardLiquidityForAccount () const
node().saFwdDeliver.clear (node().saRevDeliver);
if (previousNode().saFwdDeliver && node().saRevDeliver)
bool do_liquidity;
if (ripple::legacy::emulate027 (rippleCalc_.mActiveLedger.getLedger ()))
do_liquidity = previousNode().saFwdDeliver && node().saRevIssue;
else
do_liquidity = previousNode().saFwdDeliver && node().saRevDeliver;
if (do_liquidity)
{
// Rate : 1.0 : transfer_rate
rippleLiquidity (

View File

@@ -27,6 +27,8 @@
#include <ripple/basics/Log.h>
#include <ripple/json/to_string.h>
#include <ripple/legacy/0.27/CreateOffer.h>
#include <beast/cxx14/memory.h>
#include <stdexcept>
@@ -827,6 +829,14 @@ transact_CreateOffer (
TransactionEngineParams params,
TransactionEngine* engine)
{
// Attempt to implement legacy offer creation semantics. If successful,
// then return the result. Otherwise, attempt to process using the
// new semantics.
auto ret = ripple::legacy::transact_CreateOffer (txn, params, engine);
if (ret.first)
return ret.second;
core::CrossType cross_type = core::CrossType::IouToIou;
bool const pays_xrp = txn.getFieldAmount (sfTakerPays).isNative ();

View File

@@ -0,0 +1,625 @@
//------------------------------------------------------------------------------
/*
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 <BeastConfig.h>
#include <ripple/legacy/0.27/book/OfferStream.h>
#include <ripple/legacy/0.27/book/Taker.h>
#include <ripple/legacy/0.27/book/Types.h>
#include <ripple/legacy/0.27/book/Amounts.h>
#include <ripple/legacy/0.27/CreateOffer.h>
#include <ripple/legacy/0.27/Emulate027.h>
#include <ripple/basics/Log.h>
#include <ripple/core/Config.h>
#include <ripple/json/to_string.h>
#include <beast/cxx14/memory.h>
namespace ripple {
namespace legacy {
TER
CreateOffer::checkAcceptAsset(IssueRef issue) const
{
/* Only valid for custom currencies */
assert (!isXRP (issue.currency));
SLE::pointer const issuerAccount = mEngine->entryCache (
ltACCOUNT_ROOT, getAccountRootIndex (issue.account));
if (!issuerAccount)
{
if (m_journal.warning) m_journal.warning <<
"delay: can't receive IOUs from non-existent issuer: " <<
to_string (issue.account);
return (mParams & tapRETRY)
? terNO_ACCOUNT
: tecNO_ISSUER;
}
if (issuerAccount->getFieldU32 (sfFlags) & lsfRequireAuth)
{
SLE::pointer const trustLine (mEngine->entryCache (
ltRIPPLE_STATE, getRippleStateIndex (
mTxnAccountID, issue.account, issue.currency)));
if (!trustLine)
{
return (mParams & tapRETRY)
? terNO_LINE
: tecNO_LINE;
}
// Entries have a canonical representation, determined by a
// lexicographical "greater than" comparison employing strict weak
// ordering. Determine which entry we need to access.
bool const canonical_gt (mTxnAccountID > issue.account);
bool const is_authorized (trustLine->getFieldU32 (sfFlags) &
(canonical_gt ? lsfLowAuth : lsfHighAuth));
if (!is_authorized)
{
if (m_journal.debug) m_journal.debug <<
"delay: can't receive IOUs from issuer without auth.";
return (mParams & tapRETRY)
? terNO_AUTH
: tecNO_AUTH;
}
}
return tesSUCCESS;
}
std::pair<TER, legacy::core::Amounts>
CreateOffer::crossOffers (legacy::core::LedgerView& view,
legacy::core::Amounts const& taker_amount)
{
legacy::core::Taker::Options const options (mTxn.getFlags());
legacy::core::Clock::time_point const when (
mEngine->getLedger ()->getParentCloseTimeNC ());
legacy::core::LedgerView view_cancel (view.duplicate());
legacy::core::OfferStream offers (
view, view_cancel,
Book (taker_amount.in.issue(), taker_amount.out.issue()),
when, m_journal);
legacy::core::Taker taker (offers.view(), mTxnAccountID, taker_amount, options);
TER cross_result (tesSUCCESS);
while (true)
{
// Modifying the order or logic of these
// operations causes a protocol breaking change.
// Checks which remove offers are performed early so we
// can reduce the size of the order book as much as possible
// before terminating the loop.
if (taker.done())
{
m_journal.debug << "The taker reports he's done during crossing!";
break;
}
if (! offers.step ())
{
// Place the order since there are no
// more offers and the order has a balance.
m_journal.debug << "No more offers to consider during crossing!";
break;
}
auto const& offer (offers.tip());
if (taker.reject (offer.quality()))
{
// Place the order since there are no more offers
// at the desired quality, and the order has a balance.
break;
}
if (offer.account() == taker.account())
{
// Skip offer from self. The offer will be considered expired and
// will get deleted.
continue;
}
if (m_journal.debug) m_journal.debug <<
" Offer: " << offer.entry()->getIndex() << std::endl <<
" " << offer.amount().in << " : " << offer.amount().out;
cross_result = taker.cross (offer);
if (cross_result != tesSUCCESS)
{
cross_result = tecFAILED_PROCESSING;
break;
}
}
return std::make_pair(cross_result, taker.remaining_offer ());
}
CreateOffer::CreateOffer (STTx const& txn,
TransactionEngineParams params, TransactionEngine* engine)
: Transactor (txn, params, engine, deprecatedLogs().journal("CreateOffer"))
{
}
TER
CreateOffer::doApply()
{
if (m_journal.debug) m_journal.debug <<
"OfferCreate> " << mTxn.getJson (0);
std::uint32_t const uTxFlags = mTxn.getFlags ();
bool const bPassive (uTxFlags & tfPassive);
bool const bImmediateOrCancel (uTxFlags & tfImmediateOrCancel);
bool const bFillOrKill (uTxFlags & tfFillOrKill);
bool const bSell (uTxFlags & tfSell);
STAmount saTakerPays = mTxn.getFieldAmount (sfTakerPays);
STAmount saTakerGets = mTxn.getFieldAmount (sfTakerGets);
if (!isLegalNet (saTakerPays) || !isLegalNet (saTakerGets))
return temBAD_AMOUNT;
auto const& uPaysIssuerID = saTakerPays.getIssuer ();
auto const& uPaysCurrency = saTakerPays.getCurrency ();
auto const& uGetsIssuerID = saTakerGets.getIssuer ();
auto const& uGetsCurrency = saTakerGets.getCurrency ();
bool const bHaveExpiration (mTxn.isFieldPresent (sfExpiration));
bool const bHaveCancel (mTxn.isFieldPresent (sfOfferSequence));
std::uint32_t const uExpiration = mTxn.getFieldU32 (sfExpiration);
std::uint32_t const uCancelSequence = mTxn.getFieldU32 (sfOfferSequence);
// FIXME understand why we use SequenceNext instead of current transaction
// sequence to determine the transaction. Why is the offer seuqnce
// number insufficient?
std::uint32_t const uAccountSequenceNext = mTxnAccount->getFieldU32 (sfSequence);
std::uint32_t const uSequence = mTxn.getSequence ();
const uint256 uLedgerIndex = getOfferIndex (mTxnAccountID, uSequence);
if (m_journal.debug)
{
m_journal.debug <<
"Creating offer node: " << to_string (uLedgerIndex) <<
" uSequence=" << uSequence;
if (bImmediateOrCancel)
m_journal.debug << "Transaction: IoC set.";
if (bFillOrKill)
m_journal.debug << "Transaction: FoK set.";
}
// This is the original rate of this offer, and is the rate at which it will
// be placed, even if crossing offers change the amounts.
std::uint64_t const uRate = getRate (saTakerGets, saTakerPays);
TER terResult (tesSUCCESS);
// This is the ledger view that we work against. Transactions are applied
// as we go on processing transactions.
legacy::core::LedgerView& view (mEngine->view ());
// This is a checkpoint with just the fees paid. If something goes wrong
// with this transaction, we roll back to this ledger.
legacy::core::LedgerView view_checkpoint (view);
view.bumpSeq (); // Begin ledger variance.
SLE::pointer sleCreator = mEngine->entryCache (
ltACCOUNT_ROOT, getAccountRootIndex (mTxnAccountID));
if (uTxFlags & tfOfferCreateMask)
{
if (m_journal.debug) m_journal.debug <<
"Malformed transaction: Invalid flags set.";
terResult = temINVALID_FLAG;
}
else if (bImmediateOrCancel && bFillOrKill)
{
if (m_journal.debug) m_journal.debug <<
"Malformed transaction: both IoC and FoK set.";
terResult = temINVALID_FLAG;
}
else if (bHaveExpiration && !uExpiration)
{
m_journal.warning <<
"Malformed offer: bad expiration";
terResult = temBAD_EXPIRATION;
}
else if (saTakerPays.isNative () && saTakerGets.isNative ())
{
m_journal.warning <<
"Malformed offer: XRP for XRP";
terResult = temBAD_OFFER;
}
else if (saTakerPays <= zero || saTakerGets <= zero)
{
m_journal.warning <<
"Malformed offer: bad amount";
terResult = temBAD_OFFER;
}
else if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID)
{
m_journal.warning <<
"Malformed offer: redundant offer";
terResult = temREDUNDANT;
}
// We don't allow a non-native currency to use the currency code XRP.
else if (badCurrency() == uPaysCurrency || badCurrency() == uGetsCurrency)
{
m_journal.warning <<
"Malformed offer: Bad currency.";
terResult = temBAD_CURRENCY;
}
else if (saTakerPays.isNative () != !uPaysIssuerID ||
saTakerGets.isNative () != !uGetsIssuerID)
{
m_journal.warning <<
"Malformed offer: bad issuer";
terResult = temBAD_ISSUER;
}
else if (view.isGlobalFrozen (uPaysIssuerID) || view.isGlobalFrozen (uGetsIssuerID))
{
m_journal.warning <<
"Offer involves frozen asset";
terResult = tecFROZEN;
}
else if (view.accountFunds (
mTxnAccountID, saTakerGets, fhZERO_IF_FROZEN) <= zero)
{
m_journal.warning <<
"delay: Offers must be at least partially funded.";
terResult = tecUNFUNDED_OFFER;
}
// This can probably be simplified to make sure that you cancel sequences
// before the transaction sequence number.
else if (bHaveCancel && (!uCancelSequence || uAccountSequenceNext - 1 <= uCancelSequence))
{
if (m_journal.debug) m_journal.debug <<
"uAccountSequenceNext=" << uAccountSequenceNext <<
" uOfferSequence=" << uCancelSequence;
terResult = temBAD_SEQUENCE;
}
if (terResult != tesSUCCESS)
{
if (m_journal.debug) m_journal.debug <<
"final terResult=" << transToken (terResult);
return terResult;
}
// Process a cancellation request that's passed along with an offer.
if ((terResult == tesSUCCESS) && bHaveCancel)
{
uint256 const uCancelIndex (
getOfferIndex (mTxnAccountID, uCancelSequence));
SLE::pointer sleCancel = mEngine->entryCache (ltOFFER, uCancelIndex);
// It's not an error to not find the offer to cancel: it might have
// been consumed or removed as we are processing.
if (sleCancel)
{
m_journal.warning <<
"Cancelling order with sequence " << uCancelSequence;
terResult = view.offerDelete (sleCancel);
}
}
// Expiration is defined in terms of the close time of the parent ledger,
// because we definitively know the time that it closed but we do not
// know the closing time of the ledger that is under construction.
if (bHaveExpiration &&
(mEngine->getLedger ()->getParentCloseTimeNC () >= uExpiration))
{
return tesSUCCESS;
}
// Make sure that we are authorized to hold what the taker will pay us.
if (terResult == tesSUCCESS && !saTakerPays.isNative ())
terResult = checkAcceptAsset (Issue (uPaysCurrency, uPaysIssuerID));
bool crossed = false;
bool const bOpenLedger (mParams & tapOPEN_LEDGER);
if (terResult == tesSUCCESS)
{
// We reverse gets and pays because during offer crossing we are taking.
legacy::core::Amounts const taker_amount (saTakerGets, saTakerPays);
// The amount of the offer that we will need to place, after we finish
// offer crossing processing. It may be equal to the original amount,
// empty (fully crossed), or something in-between.
legacy::core::Amounts place_offer;
std::tie(terResult, place_offer) = crossOffers (view, taker_amount);
if (terResult == tecFAILED_PROCESSING && bOpenLedger)
terResult = telFAILED_PROCESSING;
if (terResult == tesSUCCESS)
{
// We now need to reduce the offer by the cross flow. We reverse
// in and out here, since during crossing we were takers.
assert (saTakerPays.getCurrency () == place_offer.out.getCurrency ());
assert (saTakerPays.getIssuer () == place_offer.out.getIssuer ());
assert (saTakerGets.getCurrency () == place_offer.in.getCurrency ());
assert (saTakerGets.getIssuer () == place_offer.in.getIssuer ());
if (taker_amount != place_offer)
crossed = true;
if (m_journal.debug)
{
m_journal.debug << "Offer Crossing: " << transToken (terResult);
if (terResult == tesSUCCESS)
{
m_journal.debug <<
" takerPays: " << saTakerPays.getFullText () <<
" -> " << place_offer.out.getFullText ();
m_journal.debug <<
" takerGets: " << saTakerGets.getFullText () <<
" -> " << place_offer.in.getFullText ();
}
}
saTakerPays = place_offer.out;
saTakerGets = place_offer.in;
}
}
if (terResult != tesSUCCESS)
{
m_journal.debug <<
"final terResult=" << transToken (terResult);
return terResult;
}
if (m_journal.debug)
{
m_journal.debug <<
"takeOffers: saTakerPays=" <<saTakerPays.getFullText ();
m_journal.debug <<
"takeOffers: saTakerGets=" << saTakerGets.getFullText ();
m_journal.debug <<
"takeOffers: mTxnAccountID=" <<
to_string (mTxnAccountID);
m_journal.debug <<
"takeOffers: FUNDS=" << view.accountFunds (
mTxnAccountID, saTakerGets, fhZERO_IF_FROZEN).getFullText ();
}
if (saTakerPays < zero || saTakerGets < zero)
{
// Earlier, we verified that the amounts, as specified in the offer,
// were not negative. That they are now suggests that something went
// very wrong with offer crossing.
m_journal.fatal << (crossed ? "Partially consumed" : "Full") <<
" offer has negative component:" <<
" pays=" << saTakerPays.getFullText () <<
" gets=" << saTakerGets.getFullText ();
assert (saTakerPays >= zero);
assert (saTakerGets >= zero);
return tefINTERNAL;
}
if (bFillOrKill && (saTakerPays != zero || saTakerGets != zero))
{
// Fill or kill and have leftovers.
view.swapWith (view_checkpoint); // Restore with just fees paid.
return tesSUCCESS;
}
// What the reserve would be if this offer was placed.
auto const accountReserve (mEngine->getLedger ()->getReserve (
sleCreator->getFieldU32 (sfOwnerCount) + 1));
if (saTakerPays == zero || // Wants nothing more.
saTakerGets == zero || // Offering nothing more.
bImmediateOrCancel) // Do not persist.
{
// Complete as is.
}
else if (mPriorBalance.getNValue () < accountReserve)
{
// If we are here, the signing account had an insufficient reserve
// *prior* to our processing. We use the prior balance to simplify
// client writing and make the user experience better.
if (bOpenLedger) // Ledger is not final, can vote no.
{
// Hope for more reserve to come in or more offers to consume. If we
// specified a local error this transaction will not be retried, so
// specify a tec to distribute the transaction and allow it to be
// retried. In particular, it may have been successful to a
// degree (partially filled) and if it hasn't, it might succeed.
terResult = tecINSUF_RESERVE_OFFER;
}
else if (!crossed)
{
// Ledger is final, insufficent reserve to create offer, processed
// nothing.
terResult = tecINSUF_RESERVE_OFFER;
}
else
{
// Ledger is final, insufficent reserve to create offer, processed
// something.
// Consider the offer unfunded. Treat as tesSUCCESS.
}
}
else
{
assert (saTakerPays > zero);
assert (saTakerGets > zero);
// We need to place the remainder of the offer into its order book.
if (m_journal.debug) m_journal.debug <<
"offer not fully consumed:" <<
" saTakerPays=" << saTakerPays.getFullText () <<
" saTakerGets=" << saTakerGets.getFullText ();
std::uint64_t uOwnerNode;
std::uint64_t uBookNode;
uint256 uDirectory;
// Add offer to owner's directory.
terResult = view.dirAdd (uOwnerNode,
getOwnerDirIndex (mTxnAccountID), uLedgerIndex,
std::bind (
&Ledger::ownerDirDescriber, std::placeholders::_1,
std::placeholders::_2, mTxnAccountID));
if (tesSUCCESS == terResult)
{
// Update owner count.
view.incrementOwnerCount (sleCreator);
uint256 const uBookBase (getBookBase (
{{uPaysCurrency, uPaysIssuerID},
{uGetsCurrency, uGetsIssuerID}}));
if (m_journal.debug) m_journal.debug <<
"adding to book: " << to_string (uBookBase) <<
" : " << saTakerPays.getHumanCurrency () <<
"/" << to_string (saTakerPays.getIssuer ()) <<
" -> " << saTakerGets.getHumanCurrency () <<
"/" << to_string (saTakerGets.getIssuer ());
// We use the original rate to place the offer.
uDirectory = getQualityIndex (uBookBase, uRate);
// Add offer to order book.
terResult = view.dirAdd (uBookNode, uDirectory, uLedgerIndex,
std::bind (
&Ledger::qualityDirDescriber, std::placeholders::_1,
std::placeholders::_2, saTakerPays.getCurrency (),
uPaysIssuerID, saTakerGets.getCurrency (),
uGetsIssuerID, uRate));
}
if (tesSUCCESS == terResult)
{
if (m_journal.debug)
{
m_journal.debug <<
"sfAccount=" <<
to_string (mTxnAccountID);
m_journal.debug <<
"uPaysIssuerID=" <<
to_string (uPaysIssuerID);
m_journal.debug <<
"uGetsIssuerID=" <<
to_string (uGetsIssuerID);
m_journal.debug <<
"saTakerPays.isNative()=" <<
saTakerPays.isNative ();
m_journal.debug <<
"saTakerGets.isNative()=" <<
saTakerGets.isNative ();
m_journal.debug <<
"uPaysCurrency=" <<
saTakerPays.getHumanCurrency ();
m_journal.debug <<
"uGetsCurrency=" <<
saTakerGets.getHumanCurrency ();
}
SLE::pointer sleOffer (mEngine->entryCreate (ltOFFER, uLedgerIndex));
sleOffer->setFieldAccount (sfAccount, mTxnAccountID);
sleOffer->setFieldU32 (sfSequence, uSequence);
sleOffer->setFieldH256 (sfBookDirectory, uDirectory);
sleOffer->setFieldAmount (sfTakerPays, saTakerPays);
sleOffer->setFieldAmount (sfTakerGets, saTakerGets);
sleOffer->setFieldU64 (sfOwnerNode, uOwnerNode);
sleOffer->setFieldU64 (sfBookNode, uBookNode);
if (uExpiration)
sleOffer->setFieldU32 (sfExpiration, uExpiration);
if (bPassive)
sleOffer->setFlag (lsfPassive);
if (bSell)
sleOffer->setFlag (lsfSell);
if (m_journal.debug) m_journal.debug <<
"final terResult=" << transToken (terResult) <<
" sleOffer=" << sleOffer->getJson (0);
}
}
if (terResult != tesSUCCESS)
{
m_journal.debug <<
"final terResult=" << transToken (terResult);
}
return terResult;
}
//------------------------------------------------------------------------------
std::pair <bool, TER>
transact_CreateOffer (STTx const& txn,
TransactionEngineParams params, TransactionEngine* engine)
{
// If we are emulating 0.27, we process the transaction using the 0.27
// semantics and no autobridging.
if (emulate027 (engine->getLedger ()))
return std::make_pair (true, CreateOffer (txn, params, engine).apply ());
// Otherwise, we use 0.28 semantics with autobridging.
return std::make_pair (false, tesSUCCESS);
}
}
}

View File

@@ -0,0 +1,75 @@
//------------------------------------------------------------------------------
/*
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_APP_CREATEOFFER_H_INCLUDED
#define RIPPLE_APP_CREATEOFFER_H_INCLUDED
#include <ripple/app/book/OfferStream.h>
#include <ripple/app/book/Taker.h>
#include <ripple/app/book/Types.h>
#include <ripple/app/book/Amounts.h>
#include <ripple/app/transactors/Transactor.h>
#include <ripple/basics/Log.h>
#include <ripple/json/to_string.h>
#include <beast/cxx14/memory.h>
#include <utility>
namespace ripple {
namespace legacy {
class CreateOffer
: public Transactor
{
private:
// Determine if we are authorized to hold the asset we want to get
TER
checkAcceptAsset(IssueRef issue) const;
/* Fill offer as much as possible by consuming offers already on the books.
We adjusts account balances and charges fees on top to taker.
@param taker_amount.in How much the taker offers
@param taker_amount.out How much the taker wants
@return result.first crossing operation success/failure indicator.
result.second amount of offer left unfilled - only meaningful
if result.first is tesSUCCESS.
*/
std::pair<TER, core::Amounts>
crossOffers (core::LedgerView& view,
core::Amounts const& taker_amount);
public:
CreateOffer (STTx const& txn,
TransactionEngineParams params, TransactionEngine* engine);
TER
doApply() override;
};
std::pair <bool, TER>
transact_CreateOffer (STTx const& txn,
TransactionEngineParams params, TransactionEngine* engine);
}
}
#endif

View File

@@ -0,0 +1,48 @@
//------------------------------------------------------------------------------
/*
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 <BeastConfig.h>
#include <ripple/core/Config.h>
#include <ripple/legacy/0.27/Emulate027.h>
namespace ripple {
namespace legacy {
bool
emulate027 (Ledger::ref ledger)
{
// In standalone mode, the server always uses 0.28 semantics to allow
// unit tests to succeed.
if (getConfig ().RUN_STANDALONE)
return false;
// The server also uses 0.28 semantics for all ledgers whose parent
// closed after 2015-03-30 13:00:00 PDT.
static std::uint32_t const legacy_cutoff = 481060800;
if (ledger->getParentCloseTimeNC () > legacy_cutoff)
return false;
return true;
}
}
}

View File

@@ -0,0 +1,32 @@
//------------------------------------------------------------------------------
/*
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 <BeastConfig.h>
#include <ripple/app/ledger/Ledger.h>
namespace ripple {
namespace legacy {
bool
emulate027 (Ledger::ref ledger);
}
}

View File

@@ -0,0 +1,216 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2014 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_LEGACY_CORE_AMOUNT_H_INCLUDED
#define RIPPLE_LEGACY_CORE_AMOUNT_H_INCLUDED
#include <ripple/protocol/STObject.h>
#include <beast/utility/noexcept.h>
#include <beast/cxx14/type_traits.h> // <type_traits>
namespace ripple {
namespace legacy {
namespace core {
/** Custom floating point asset amount.
The "representation" may be integral or non-integral. For integral
representations, the exponent is always zero and the value held in the
mantissa is an exact quantity.
*/
class AmountType
{
private:
std::uint64_t m_mantissa;
int m_exponent;
bool m_negative;
bool m_integral;
AmountType (std::uint64_t mantissa,
int exponent, bool negative, bool integral)
: m_mantissa (mantissa)
, m_exponent (exponent)
, m_negative (negative)
, m_integral (integral)
{
}
public:
/** Default construction.
The value is uninitialized.
*/
AmountType() noexcept
{
}
/** Construct from an integer.
The representation is set to integral.
*/
/** @{ */
template <class Integer>
AmountType (Integer value,
std::enable_if_t <std::is_signed <Integer>::value>* = 0) noexcept
: m_mantissa (value)
, m_exponent (0)
, m_negative (value < 0)
, m_integral (true)
{
static_assert (std::is_integral<Integer>::value,
"Cannot construct from non-integral type.");
}
template <class Integer>
AmountType (Integer value,
std::enable_if_t <! std::is_signed <Integer>::value>* = 0) noexcept
: m_mantissa (value)
, m_exponent (0)
, m_negative (false)
{
static_assert (std::is_integral<Integer>::value,
"Cannot construct from non-integral type.");
}
/** @} */
/** Assign the value zero.
The representation is preserved.
*/
AmountType&
operator= (Zero) noexcept
{
m_mantissa = 0;
// VFALCO Why -100?
// "We have to use something in range."
// "This makes zero the smallest value."
m_exponent = m_integral ? 0 : -100;
m_exponent = 0;
m_negative = false;
return *this;
}
/** Returns the value in canonical format. */
AmountType
normal() const noexcept
{
if (m_integral)
{
AmountType result;
if (m_mantissa == 0)
{
result.m_exponent = 0;
result.m_negative = false;
}
return result;
}
return AmountType();
}
//
// Comparison
//
int
signum() const noexcept
{
if (m_mantissa == 0)
return 0;
return m_negative ? -1 : 1;
}
bool
operator== (AmountType const& other) const noexcept
{
return
m_negative == other.m_negative &&
m_mantissa == other.m_mantissa &&
m_exponent == other.m_exponent;
}
bool
operator!= (AmountType const& other) const noexcept
{
return ! (*this == other);
}
bool
operator< (AmountType const& other) const noexcept
{
return false;
}
bool
operator>= (AmountType const& other) const noexcept
{
return ! (*this < other);
}
bool
operator> (AmountType const& other) const noexcept
{
return other < *this;
}
bool
operator<= (AmountType const& other) const noexcept
{
return ! (other < *this);
}
//
// Arithmetic
//
AmountType
operator-() const noexcept
{
return AmountType (m_mantissa, m_exponent, ! m_negative, m_integral);
}
//
// Output
//
std::ostream&
operator<< (std::ostream& os)
{
int const sig (signum());
if (sig == 0)
return os << "0";
if (sig < 0)
os << "-";
if (m_integral)
return os << m_mantissa;
if (m_exponent != 0 && (m_exponent < -25 || m_exponent > -5))
return os << m_mantissa << "e" << m_exponent;
return os;
}
};
//------------------------------------------------------------------------------
// TODO(tom): remove this typedef and have exactly one name for STAmount.
typedef STAmount Amount;
} // core
} // legacy
} // ripple
#endif

View File

@@ -0,0 +1,70 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2014 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_LEGACY_CORE_AMOUNTS_H_INCLUDED
#define RIPPLE_LEGACY_CORE_AMOUNTS_H_INCLUDED
#include <ripple/legacy/0.27/book/Amount.h>
#include <beast/utility/noexcept.h>
namespace ripple {
namespace legacy {
namespace core {
struct Amounts
{
Amounts() = default;
Amounts (Amount const& in_, Amount const& out_)
: in (in_)
, out (out_)
{
}
/** Returns `true` if either quantity is not positive. */
bool
empty() const noexcept
{
return in <= zero || out <= zero;
}
Amount in;
Amount out;
};
inline
bool
operator== (Amounts const& lhs, Amounts const& rhs) noexcept
{
return lhs.in == rhs.in && lhs.out == rhs.out;
}
inline
bool
operator!= (Amounts const& lhs, Amounts const& rhs) noexcept
{
return ! (lhs == rhs);
}
}
}
}
#endif

View File

@@ -0,0 +1,96 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2014 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_LEGACY_CORE_BOOKTIP_H_INCLUDED
#define RIPPLE_LEGACY_CORE_BOOKTIP_H_INCLUDED
#include <ripple/legacy/0.27/book/Quality.h>
#include <ripple/legacy/0.27/book/Types.h>
#include <ripple/protocol/Indexes.h>
#include <beast/utility/noexcept.h>
#include <functional>
namespace ripple {
namespace legacy {
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;
Quality m_quality;
LedgerView&
view() const noexcept
{
return m_view;
}
public:
/** Create the iterator. */
BookTip (LedgerView& view, BookRef book);
uint256 const&
dir() const noexcept
{
return m_dir;
}
uint256 const&
index() const noexcept
{
return m_index;
}
Quality const&
quality() const noexcept
{
return m_quality;
}
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 ();
};
}
}
}
#endif

View File

@@ -0,0 +1,120 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2014 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_LEGACY_CORE_OFFER_H_INCLUDED
#define RIPPLE_LEGACY_CORE_OFFER_H_INCLUDED
#include <ripple/legacy/0.27/book/Amounts.h>
#include <ripple/legacy/0.27/book/Quality.h>
#include <ripple/legacy/0.27/book/Types.h>
#include <ripple/protocol/STLedgerEntry.h>
#include <ripple/protocol/SField.h>
#include <beast/utility/noexcept.h>
#include <ostream>
namespace ripple {
namespace legacy {
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
{
return Amounts (
m_entry->getFieldAmount (sfTakerPays),
m_entry->getFieldAmount (sfTakerGets));
}
/** Returns `true` if no more funds can flow through this offer. */
bool
fully_consumed() const
{
if (m_entry->getFieldAmount (sfTakerPays) <= zero)
return true;
if (m_entry->getFieldAmount (sfTakerGets) <= zero)
return true;
return false;
}
/** 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

View File

@@ -0,0 +1,126 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2014 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_LEGACY_CORE_OFFERSTREAM_H_INCLUDED
#define RIPPLE_LEGACY_CORE_OFFERSTREAM_H_INCLUDED
#include <ripple/legacy/0.27/book/BookTip.h>
#include <ripple/legacy/0.27/book/Offer.h>
#include <ripple/legacy/0.27/book/Quality.h>
#include <ripple/legacy/0.27/book/Types.h>
#include <beast/utility/noexcept.h>
#include <functional>
namespace ripple {
namespace legacy {
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.
*/
class OfferStream
{
private:
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;
void
erase (LedgerView& view);
public:
OfferStream (LedgerView& view, LedgerView& view_cancel, BookRef book,
Clock::time_point when, beast::Journal journal);
LedgerView&
view () noexcept
{
return m_view;
}
LedgerView&
view_cancel () noexcept
{
return m_view_cancel;
}
Book const&
book () const noexcept
{
return m_book;
}
/** 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 ();
/** Advance to the next valid offer that is not from the specified account.
This automatically removes:
- Offers with missing ledger entries
- Offers found unfunded
- Offers from the same account
- Expired offers
@return `true` if there is a valid offer.
*/
bool
step_account (Account const& account);
};
}
}
}
#endif

View File

@@ -0,0 +1,146 @@
//------------------------------------------------------------------------------
/*
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_LEGACY_CORE_QUALITY_H_INCLUDED
#define RIPPLE_LEGACY_CORE_QUALITY_H_INCLUDED
#include <ripple/legacy/0.27/book/Amount.h>
#include <ripple/legacy/0.27/book/Amounts.h>
#include <cstdint>
#include <ostream>
namespace ripple {
namespace legacy {
namespace core {
// Ripple specific constant used for parsing qualities and other things
#define QUALITY_ONE 1000000000 // 10e9
/** Represents the logical ratio of output currency to input currency.
Internally this is stored using a custom floating point representation,
as the inverse of the ratio, so that quality will be descending in
a sequence of actual values that represent qualities.
*/
class Quality
{
public:
// Type of the internal representation. Higher qualities
// have lower unsigned integer representations.
typedef std::uint64_t value_type;
private:
value_type m_value;
public:
Quality() = default;
/** Create a quality from the integer encoding of an Amount */
explicit
Quality (std::uint64_t value);
/** Create a quality from the ratio of two amounts. */
explicit
Quality (Amounts const& amount);
/** Advances to the next higher quality level. */
/** @{ */
Quality&
operator++();
Quality
operator++ (int);
/** @} */
/** Advances to the next lower quality level. */
/** @{ */
Quality&
operator--();
Quality
operator-- (int);
/** @} */
/** Returns the quality as Amount. */
Amount
rate () const
{
return amountFromQuality (m_value);
}
/** Returns the scaled amount with in capped.
Math is avoided if the result is exact. The output is clamped
to prevent money creation.
*/
Amounts
ceil_in (Amounts const& amount, Amount const& limit) const;
/** Returns the scaled amount with out capped.
Math is avoided if the result is exact. The input is clamped
to prevent money creation.
*/
Amounts
ceil_out (Amounts const& amount, Amount const& limit) const;
/** Returns `true` if lhs is lower quality than `rhs`.
Lower quality means the taker receives a worse deal.
Higher quality is better for the taker.
*/
friend
bool
operator< (Quality const& lhs, Quality const& rhs) noexcept
{
return lhs.m_value > rhs.m_value;
}
friend
bool
operator== (Quality const& lhs, Quality const& rhs) noexcept
{
return lhs.m_value == rhs.m_value;
}
friend
bool
operator!= (Quality const& lhs, Quality const& rhs) noexcept
{
return ! (lhs == rhs);
}
friend
std::ostream&
operator<< (std::ostream& os, Quality const& quality)
{
os << quality.m_value;
return os;
}
};
/** Calculate the quality of a two-hop path given the two hops.
@param lhs The first leg of the path: input to intermediate.
@param rhs The second leg of the path: intermediate to output.
*/
Quality
composed_quality (Quality const& lhs, Quality const& rhs);
}
}
}
#endif

View File

@@ -0,0 +1,148 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2014 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_LEGACY_CORE_TAKER_H_INCLUDED
#define RIPPLE_LEGACY_CORE_TAKER_H_INCLUDED
#include <ripple/legacy/0.27/book/Amounts.h>
#include <ripple/legacy/0.27/book/Quality.h>
#include <ripple/legacy/0.27/book/Offer.h>
#include <ripple/legacy/0.27/book/Types.h>
#include <ripple/protocol/TxFlags.h>
#include <beast/utility/noexcept.h>
#include <functional>
namespace ripple {
namespace legacy {
namespace core {
/** State for the active party during order book or payment operations. */
class Taker
{
public:
struct Options
{
Options() = delete;
explicit
Options (std::uint32_t tx_flags)
: sell (tx_flags & tfSell)
, passive (tx_flags & tfPassive)
, fill_or_kill (tx_flags & tfFillOrKill)
, immediate_or_cancel (tx_flags & tfImmediateOrCancel)
{
}
bool const sell;
bool const passive;
bool const fill_or_kill;
bool const immediate_or_cancel;
};
private:
std::reference_wrapper <LedgerView> m_view;
Account m_account;
Options m_options;
Quality m_quality;
Quality m_threshold;
// The original in and out quantities.
Amounts const m_amount;
// The amounts still left over for us to try and take.
Amounts m_remain;
Amounts
flow (Amounts amount, Offer const& offer, Account const& taker);
TER
fill (Offer const& offer, Amounts const& amount);
TER
fill (Offer const& leg1, Amounts const& amount1,
Offer const& leg2, Amounts const& amount2);
void
consume (Offer const& offer, Amounts const& consumed) const;
public:
Taker (LedgerView& view, Account const& account,
Amounts const& amount, Options const& options);
LedgerView&
view () const noexcept
{
return m_view;
}
/** Returns the amount remaining on the offer.
This is the amount at which the offer should be placed. It may either
be for the full amount when there were no crossing offers, or for zero
when the offer fully crossed, or any amount in between.
It is always at the original offer quality (m_quality)
*/
Amounts
remaining_offer () const;
/** Returns the account identifier of the taker. */
Account const&
account () const noexcept
{
return m_account;
}
/** Returns `true` if the quality does not meet the taker's requirements. */
bool
reject (Quality const& quality) const noexcept
{
return quality < m_threshold;
}
/** 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;
/** Perform direct crossing through given offer.
@return tesSUCCESS on success, error code otherwise.
*/
TER
cross (Offer const& offer);
/** Perform bridged crossing through given offers.
@return tesSUCCESS on success, error code otherwise.
*/
TER
cross (Offer const& leg1, Offer const& leg2);
};
inline
std::ostream&
operator<< (std::ostream& os, Taker const& taker)
{
return os << taker.account();
}
}
}
}
#endif

View File

@@ -0,0 +1,51 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2014 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_LEGACY_CORE_TYPES_H_INCLUDED
#define RIPPLE_LEGACY_CORE_TYPES_H_INCLUDED
#include <ripple/app/ledger/LedgerEntrySet.h>
#include <ripple/protocol/Book.h>
#include <chrono>
#include <cstdint>
namespace ripple {
namespace legacy {
namespace core {
/** A mutable view that overlays an immutable ledger to track changes. */
typedef LedgerEntrySet LedgerView;
/** A clock representing network time.
This measures seconds since the Ripple epoch as seen
by the ledger close clock.
*/
class Clock // : public abstract_clock <std::chrono::seconds>
{
public:
typedef std::uint32_t time_point;
typedef std::chrono::seconds duration;
};
} // core
} // legacy
} // ripple
#endif

View File

@@ -0,0 +1,85 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2014 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 <BeastConfig.h>
#include <ripple/legacy/0.27/book/BookTip.h>
namespace ripple {
namespace legacy {
namespace core {
BookTip::BookTip (LedgerView& view, BookRef book)
: m_view (view)
, m_valid (false)
, m_book (getBookBase (book))
, m_end (getQualityNext (m_book))
, m_quality ()
{
}
bool
BookTip::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 first_page (view().getNextLedgerIndex (m_book, m_end));
if (first_page.isZero())
return false;
unsigned int di (0);
SLE::pointer dir;
if (view().dirFirst (first_page, dir, di, m_index))
{
m_dir = dir->getIndex();
m_entry = view().entryCache (ltOFFER, m_index);
m_quality = Quality (getQuality (first_page));
m_valid = true;
// Next query should start before this directory
m_book = first_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 = first_page;
}
return true;
}
}
}
}

View File

@@ -0,0 +1,172 @@
//------------------------------------------------------------------------------
/*
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 <BeastConfig.h>
#include <ripple/legacy/0.27/book/OfferStream.h>
namespace ripple {
namespace legacy {
namespace core {
OfferStream::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)
{
}
// 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
OfferStream::erase (LedgerView& view)
{
// NIKB NOTE This should be using LedgerView::dirDelete, which would
// 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 it (std::find (v.begin(), v.end(), m_tip.index()));
if (it == v.end())
{
if (m_journal.error) m_journal.error <<
"Missing offer " << m_tip.index() <<
" for directory " << m_tip.dir();
return;
}
v.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();
}
bool
OfferStream::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
// NIKB 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, fhZERO_IF_FROZEN));
// Check for unfunded offer
if (owner_funds <= zero)
{
// If the owner's balance in the pristine view is the same,
// we haven't modified the balance and therefore the
// offer is "found unfunded" versus "became unfunded"
if (view_cancel().accountFunds (m_offer.account(),
m_offer.amount().out, fhZERO_IF_FROZEN) == 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;
}
break;
}
return true;
}
bool
OfferStream::step_account (Account const& account)
{
while (step ())
{
if (tip ().account () != account)
return true;
}
return false;
}
}
}
}

View File

@@ -0,0 +1,126 @@
//------------------------------------------------------------------------------
/*
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 <BeastConfig.h>
#include <ripple/legacy/0.27/book/Quality.h>
#include <cassert>
#include <limits>
namespace ripple {
namespace legacy {
namespace core {
Quality::Quality (std::uint64_t value)
: m_value (value)
{
}
Quality::Quality (Amounts const& amount)
: m_value (getRate (amount.out, amount.in))
{
}
Quality&
Quality::operator++()
{
assert (m_value > 0);
--m_value;
return *this;
}
Quality
Quality::operator++ (int)
{
Quality prev (*this);
++*this;
return prev;
}
Quality&
Quality::operator--()
{
assert (m_value < std::numeric_limits<value_type>::max());
++m_value;
return *this;
}
Quality
Quality::operator-- (int)
{
Quality prev (*this);
--*this;
return prev;
}
Amounts
Quality::ceil_in (Amounts const& amount, Amount const& limit) const
{
if (amount.in > limit)
{
Amounts result (limit, divRound (
limit, rate(), amount.out, true));
// Clamp out
if (result.out > amount.out)
result.out = amount.out;
assert (result.in == limit);
return result;
}
assert (amount.in <= limit);
return amount;
}
Amounts
Quality::ceil_out (Amounts const& amount, Amount const& limit) const
{
if (amount.out > limit)
{
Amounts result (mulRound (
limit, rate(), amount.in, true), limit);
// Clamp in
if (result.in > amount.in)
result.in = amount.in;
assert (result.out == limit);
return result;
}
assert (amount.out <= limit);
return amount;
}
Quality
composed_quality (Quality const& lhs, Quality const& rhs)
{
Amount const lhs_rate (lhs.rate ());
assert (lhs_rate != zero);
Amount const rhs_rate (rhs.rate ());
assert (rhs_rate != zero);
Amount const rate (mulRound (lhs_rate, rhs_rate, true));
std::uint64_t const stored_exponent (rate.exponent () + 100);
std::uint64_t const stored_mantissa (rate.mantissa());
assert ((stored_exponent > 0) && (stored_exponent <= 255));
return Quality ((stored_exponent << (64 - 8)) | stored_mantissa);
}
}
}
}

View File

@@ -0,0 +1,286 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2014 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 <BeastConfig.h>
#include <ripple/legacy/0.27/book/Taker.h>
namespace ripple {
namespace legacy {
namespace core {
Taker::Taker (LedgerView& view, Account const& account,
Amounts const& amount, Options const& options)
: m_view (view)
, m_account (account)
, m_options (options)
, m_quality (amount)
, m_threshold (m_quality)
, m_amount (amount)
, m_remain (amount)
{
assert (m_remain.in > zero);
assert (m_remain.out > zero);
// If this is a passive order (tfPassive), this prevents
// offers at the same quality level from being consumed.
if (m_options.passive)
++m_threshold;
}
Amounts
Taker::remaining_offer () const
{
// If the taker is done, then there's no offer to place.
if (done ())
return Amounts (m_amount.in.zeroed(), m_amount.out.zeroed());
// Avoid math altogether if we didn't cross.
if (m_amount == m_remain)
return m_amount;
if (m_options.sell)
{
assert (m_remain.in > zero);
// We scale the output based on the remaining input:
return Amounts (m_remain.in, divRound (
m_remain.in, m_quality.rate (), m_remain.out, true));
}
assert (m_remain.out > zero);
// We scale the input based on the remaining output:
return Amounts (mulRound (
m_remain.out, m_quality.rate (), m_remain.in, true), m_remain.out);
}
/** Calculate the amount particular user could get through an offer.
@param amount the maximum flow that is available to the taker.
@param offer the offer to flow through.
@param taker the person taking the offer.
@return the maximum amount that can flow through this offer.
*/
Amounts
Taker::flow (Amounts amount, Offer const& offer, Account const& taker)
{
// Limit taker's input by available funds less fees
Amount const taker_funds (view ().accountFunds (
taker, amount.in, fhZERO_IF_FROZEN));
// Get fee rate paid by taker
std::uint32_t const taker_charge_rate (rippleTransferRate (view (),
taker, offer.account (), amount.in.getIssuer()));
// Skip some math when there's no fee
if (taker_charge_rate == QUALITY_ONE)
{
amount = offer.quality ().ceil_in (amount, taker_funds);
}
else
{
Amount const taker_charge (amountFromRate (taker_charge_rate));
amount = offer.quality ().ceil_in (amount,
divide (taker_funds, taker_charge));
}
// Best flow the owner can get.
// Start out assuming entire offer will flow.
Amounts owner_amount (amount);
// Limit owner's output by available funds less fees
Amount const owner_funds (view ().accountFunds (
offer.account (), owner_amount.out, fhZERO_IF_FROZEN));
// Get fee rate paid by owner
std::uint32_t const owner_charge_rate (rippleTransferRate (view (),
offer.account (), taker, amount.out.getIssuer()));
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
{
Amount const owner_charge (amountFromRate (owner_charge_rate));
owner_amount = offer.quality ().ceil_out (owner_amount,
divide (owner_funds, owner_charge));
}
// Calculate the amount that will flow through the offer
// This does not include the fees.
return (owner_amount.in < amount.in)
? owner_amount
: amount;
}
// Adjust an offer to indicate that we are consuming some (or all) of it.
void
Taker::consume (Offer const& offer, Amounts const& consumed) const
{
Amounts const& remaining (offer.amount ());
assert (remaining.in > zero && remaining.out > zero);
assert (remaining.in >= consumed.in && remaining.out >= consumed.out);
offer.entry ()->setFieldAmount (sfTakerPays, remaining.in - consumed.in);
offer.entry ()->setFieldAmount (sfTakerGets, remaining.out - consumed.out);
view ().entryModify (offer.entry());
assert (offer.entry ()->getFieldAmount (sfTakerPays) >= zero);
assert (offer.entry ()->getFieldAmount (sfTakerGets) >= zero);
}
// Fill a direct offer.
// @param offer the offer we are going to use.
// @param amount the amount to flow through the offer.
// @returns: tesSUCCESS if successful, or an error code otherwise.
TER
Taker::fill (Offer const& offer, Amounts const& amount)
{
consume (offer, amount);
// Pay the taker, then the owner
TER result = view ().accountSend (offer.account(), account(), amount.out);
if (result == tesSUCCESS)
result = view ().accountSend (account(), offer.account(), amount.in);
return result;
}
// Fill a bridged offer.
// @param leg1 the first leg we are going to use.
// @param amount1 the amount to flow through the first leg of the offer.
// @param leg2 the second leg we are going to use.
// @param amount2 the amount to flow through the second leg of the offer.
// @return tesSUCCESS if successful, or an error code otherwise.
TER
Taker::fill (
Offer const& leg1, Amounts const& amount1,
Offer const& leg2, Amounts const& amount2)
{
assert (amount1.out == amount2.in);
consume (leg1, amount1);
consume (leg2, amount2);
/* It is possible that m_account is the same as leg1.account, leg2.account
* or both. This could happen when bridging over one's own offer. In that
* case, accountSend won't actually do a send, which is what we want.
*/
TER result = view ().accountSend (m_account, leg1.account (), amount1.in);
if (result == tesSUCCESS)
result = view ().accountSend (leg1.account (), leg2.account (), amount1.out);
if (result == tesSUCCESS)
result = view ().accountSend (leg2.account (), m_account, amount2.out);
return result;
}
bool
Taker::done () const
{
if (m_options.sell && (m_remain.in <= zero))
{
// Sell semantics: we consumed all the input currency
return true;
}
if (!m_options.sell && (m_remain.out <= zero))
{
// Buy semantics: we received the desired amount of output currency
return true;
}
// We are finished if the taker is out of funds
return view().accountFunds (
account(), m_remain.in, fhZERO_IF_FROZEN) <= zero;
}
TER
Taker::cross (Offer const& offer)
{
assert (!done ());
/* Before we call flow we must set the limit right; for buy semantics we
need to clamp the output. And we always want to clamp the input.
*/
Amounts limit (offer.amount());
if (! m_options.sell)
limit = offer.quality ().ceil_out (limit, m_remain.out);
limit = offer.quality().ceil_in (limit, m_remain.in);
assert (limit.in <= offer.amount().in);
assert (limit.out <= offer.amount().out);
assert (limit.in <= m_remain.in);
Amounts const amount (flow (limit, offer, account ()));
m_remain.out -= amount.out;
m_remain.in -= amount.in;
assert (m_remain.in >= zero);
return fill (offer, amount);
}
TER
Taker::cross (Offer const& leg1, Offer const& leg2)
{
assert (!done ());
assert (leg1.amount ().out.isNative ());
assert (leg2.amount ().in.isNative ());
Amounts amount1 (leg1.amount());
Amounts amount2 (leg2.amount());
if (m_options.sell)
amount1 = leg1.quality().ceil_in (amount1, m_remain.in);
else
amount2 = leg2.quality().ceil_out (amount2, m_remain.out);
if (amount1.out <= amount2.in)
amount2 = leg2.quality().ceil_in (amount2, amount1.out);
else
amount1 = leg1.quality().ceil_out (amount1, amount2.in);
assert (amount1.out == amount2.in);
// As written, flow can't handle a 3-party transfer, but this works for
// us because the output of leg1 and the input leg2 are XRP.
Amounts flow1 (flow (amount1, leg1, m_account));
amount2 = leg2.quality().ceil_in (amount2, flow1.out);
Amounts flow2 (flow (amount2, leg2, m_account));
m_remain.out -= amount2.out;
m_remain.in -= amount1.in;
return fill (leg1, flow1, leg2, flow2);
}
}
}
}

View File

@@ -0,0 +1,28 @@
//------------------------------------------------------------------------------
/*
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 <BeastConfig.h>
#include <ripple/legacy/0.27/book/impl/BookTip.cpp>
#include <ripple/legacy/0.27/book/impl/OfferStream.cpp>
#include <ripple/legacy/0.27/book/impl/Quality.cpp>
#include <ripple/legacy/0.27/book/impl/Taker.cpp>
#include <ripple/legacy/0.27/CreateOffer.cpp>
#include <ripple/legacy/0.27/Emulate027.cpp>