From 8f88d915baa4411fd2c3e9255be926dfa1158bc0 Mon Sep 17 00:00:00 2001 From: Nik Bougalis Date: Thu, 5 Mar 2015 20:17:35 -0800 Subject: [PATCH] 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: 3ccbd7c9b2e44775b5534f38e55bc52d356e06f6 b203db27a46c115ec8f6960b2abeaaa6f0bfba0e --- Builds/VisualStudio2013/RippleD.vcxproj | 42 ++ .../VisualStudio2013/RippleD.vcxproj.filters | 63 ++ SConstruct | 3 + .../cursor/ForwardLiquidityForAccount.cpp | 10 +- src/ripple/app/transactors/CreateOffer.cpp | 10 + src/ripple/legacy/0.27/CreateOffer.cpp | 625 ++++++++++++++++++ src/ripple/legacy/0.27/CreateOffer.h | 75 +++ src/ripple/legacy/0.27/Emulate027.cpp | 48 ++ src/ripple/legacy/0.27/Emulate027.h | 32 + src/ripple/legacy/0.27/book/Amount.h | 216 ++++++ src/ripple/legacy/0.27/book/Amounts.h | 70 ++ src/ripple/legacy/0.27/book/BookTip.h | 96 +++ src/ripple/legacy/0.27/book/Offer.h | 120 ++++ src/ripple/legacy/0.27/book/OfferStream.h | 126 ++++ src/ripple/legacy/0.27/book/Quality.h | 146 ++++ src/ripple/legacy/0.27/book/Taker.h | 148 +++++ src/ripple/legacy/0.27/book/Types.h | 51 ++ src/ripple/legacy/0.27/book/impl/BookTip.cpp | 85 +++ .../legacy/0.27/book/impl/OfferStream.cpp | 172 +++++ src/ripple/legacy/0.27/book/impl/Quality.cpp | 126 ++++ src/ripple/legacy/0.27/book/impl/Taker.cpp | 286 ++++++++ src/ripple/unity/legacy.cpp | 28 + 22 files changed, 2577 insertions(+), 1 deletion(-) create mode 100644 src/ripple/legacy/0.27/CreateOffer.cpp create mode 100644 src/ripple/legacy/0.27/CreateOffer.h create mode 100644 src/ripple/legacy/0.27/Emulate027.cpp create mode 100644 src/ripple/legacy/0.27/Emulate027.h create mode 100644 src/ripple/legacy/0.27/book/Amount.h create mode 100644 src/ripple/legacy/0.27/book/Amounts.h create mode 100644 src/ripple/legacy/0.27/book/BookTip.h create mode 100644 src/ripple/legacy/0.27/book/Offer.h create mode 100644 src/ripple/legacy/0.27/book/OfferStream.h create mode 100644 src/ripple/legacy/0.27/book/Quality.h create mode 100644 src/ripple/legacy/0.27/book/Taker.h create mode 100644 src/ripple/legacy/0.27/book/Types.h create mode 100644 src/ripple/legacy/0.27/book/impl/BookTip.cpp create mode 100644 src/ripple/legacy/0.27/book/impl/OfferStream.cpp create mode 100644 src/ripple/legacy/0.27/book/impl/Quality.cpp create mode 100644 src/ripple/legacy/0.27/book/impl/Taker.cpp create mode 100644 src/ripple/unity/legacy.cpp diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj index 1e3644e3dd..3c031edeb5 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj +++ b/Builds/VisualStudio2013/RippleD.vcxproj @@ -2317,6 +2317,44 @@ + + + + + + + + True + + + True + + + True + + + True + + + + + + + + + + + + + True + + + + + True + + + @@ -3410,6 +3448,10 @@ True True + + True + True + diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters index 84ddda7dd1..3c4f855491 100644 --- a/Builds/VisualStudio2013/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters @@ -355,6 +355,18 @@ {BA646284-836B-B151-F2AA-D18535D6F3C1} + + {312AABAF-FDD8-31C7-C7DB-022D83CEA84F} + + + {F042ABF9-9B55-1118-07DA-1EECFBF1237B} + + + {73645AAD-9270-EE60-82A6-DFD2DBF65D6C} + + + {381C3650-0CBD-536A-DE2D-301216CB03A6} + {6649BD29-BE86-723F-501A-045E39310112} @@ -3072,6 +3084,54 @@ ripple\json + + ripple\legacy\0.27\book + + + ripple\legacy\0.27\book + + + ripple\legacy\0.27\book + + + ripple\legacy\0.27\book\impl + + + ripple\legacy\0.27\book\impl + + + ripple\legacy\0.27\book\impl + + + ripple\legacy\0.27\book\impl + + + ripple\legacy\0.27\book + + + ripple\legacy\0.27\book + + + ripple\legacy\0.27\book + + + ripple\legacy\0.27\book + + + ripple\legacy\0.27\book + + + ripple\legacy\0.27 + + + ripple\legacy\0.27 + + + ripple\legacy\0.27 + + + ripple\legacy\0.27 + ripple\net @@ -4185,6 +4245,9 @@ ripple\unity + + ripple\unity + ripple\unity diff --git a/SConstruct b/SConstruct index ce3586ac05..c327bb65ef 100644 --- a/SConstruct +++ b/SConstruct @@ -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( diff --git a/src/ripple/app/paths/cursor/ForwardLiquidityForAccount.cpp b/src/ripple/app/paths/cursor/ForwardLiquidityForAccount.cpp index 274cba130f..9e2c4c67a9 100644 --- a/src/ripple/app/paths/cursor/ForwardLiquidityForAccount.cpp +++ b/src/ripple/app/paths/cursor/ForwardLiquidityForAccount.cpp @@ -21,6 +21,7 @@ #include #include #include +#include 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 ( diff --git a/src/ripple/app/transactors/CreateOffer.cpp b/src/ripple/app/transactors/CreateOffer.cpp index 5d44556940..e1fcdc8856 100644 --- a/src/ripple/app/transactors/CreateOffer.cpp +++ b/src/ripple/app/transactors/CreateOffer.cpp @@ -27,6 +27,8 @@ #include #include +#include + #include #include @@ -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 (); diff --git a/src/ripple/legacy/0.27/CreateOffer.cpp b/src/ripple/legacy/0.27/CreateOffer.cpp new file mode 100644 index 0000000000..e9ea2d68d9 --- /dev/null +++ b/src/ripple/legacy/0.27/CreateOffer.cpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 +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=" < " << 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 +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); +} + +} + +} diff --git a/src/ripple/legacy/0.27/CreateOffer.h b/src/ripple/legacy/0.27/CreateOffer.h new file mode 100644 index 0000000000..0c7c9607e5 --- /dev/null +++ b/src/ripple/legacy/0.27/CreateOffer.h @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +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 + crossOffers (core::LedgerView& view, + core::Amounts const& taker_amount); + +public: + CreateOffer (STTx const& txn, + TransactionEngineParams params, TransactionEngine* engine); + + TER + doApply() override; +}; + +std::pair +transact_CreateOffer (STTx const& txn, + TransactionEngineParams params, TransactionEngine* engine); + +} + +} + +#endif diff --git a/src/ripple/legacy/0.27/Emulate027.cpp b/src/ripple/legacy/0.27/Emulate027.cpp new file mode 100644 index 0000000000..6c9524e00c --- /dev/null +++ b/src/ripple/legacy/0.27/Emulate027.cpp @@ -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 +#include +#include + +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; +} + +} + +} diff --git a/src/ripple/legacy/0.27/Emulate027.h b/src/ripple/legacy/0.27/Emulate027.h new file mode 100644 index 0000000000..2dba120850 --- /dev/null +++ b/src/ripple/legacy/0.27/Emulate027.h @@ -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 +#include + +namespace ripple { + +namespace legacy { + +bool +emulate027 (Ledger::ref ledger); + +} + +} diff --git a/src/ripple/legacy/0.27/book/Amount.h b/src/ripple/legacy/0.27/book/Amount.h new file mode 100644 index 0000000000..35962b0f67 --- /dev/null +++ b/src/ripple/legacy/0.27/book/Amount.h @@ -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 + +#include +#include // + +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 + AmountType (Integer value, + std::enable_if_t ::value>* = 0) noexcept + : m_mantissa (value) + , m_exponent (0) + , m_negative (value < 0) + , m_integral (true) + { + static_assert (std::is_integral::value, + "Cannot construct from non-integral type."); + } + + template + AmountType (Integer value, + std::enable_if_t ::value>* = 0) noexcept + : m_mantissa (value) + , m_exponent (0) + , m_negative (false) + { + static_assert (std::is_integral::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 diff --git a/src/ripple/legacy/0.27/book/Amounts.h b/src/ripple/legacy/0.27/book/Amounts.h new file mode 100644 index 0000000000..cb363fe7a5 --- /dev/null +++ b/src/ripple/legacy/0.27/book/Amounts.h @@ -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 + +#include + +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 diff --git a/src/ripple/legacy/0.27/book/BookTip.h b/src/ripple/legacy/0.27/book/BookTip.h new file mode 100644 index 0000000000..71d75c80bc --- /dev/null +++ b/src/ripple/legacy/0.27/book/BookTip.h @@ -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 +#include +#include +#include + +#include + +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 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 diff --git a/src/ripple/legacy/0.27/book/Offer.h b/src/ripple/legacy/0.27/book/Offer.h new file mode 100644 index 0000000000..d76cd9b6d0 --- /dev/null +++ b/src/ripple/legacy/0.27/book/Offer.h @@ -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 +#include +#include + +#include +#include + +#include + +#include + +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 diff --git a/src/ripple/legacy/0.27/book/OfferStream.h b/src/ripple/legacy/0.27/book/OfferStream.h new file mode 100644 index 0000000000..e29c5380ec --- /dev/null +++ b/src/ripple/legacy/0.27/book/OfferStream.h @@ -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 +#include +#include +#include + +#include + +#include + +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 m_view; + std::reference_wrapper 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 + diff --git a/src/ripple/legacy/0.27/book/Quality.h b/src/ripple/legacy/0.27/book/Quality.h new file mode 100644 index 0000000000..08d345116f --- /dev/null +++ b/src/ripple/legacy/0.27/book/Quality.h @@ -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 +#include + +#include +#include + +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 diff --git a/src/ripple/legacy/0.27/book/Taker.h b/src/ripple/legacy/0.27/book/Taker.h new file mode 100644 index 0000000000..bf83d55073 --- /dev/null +++ b/src/ripple/legacy/0.27/book/Taker.h @@ -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 +#include +#include +#include +#include +#include +#include + +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 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 diff --git a/src/ripple/legacy/0.27/book/Types.h b/src/ripple/legacy/0.27/book/Types.h new file mode 100644 index 0000000000..d9ebe28c77 --- /dev/null +++ b/src/ripple/legacy/0.27/book/Types.h @@ -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 +#include + +#include +#include + +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 +{ +public: + typedef std::uint32_t time_point; + typedef std::chrono::seconds duration; +}; + +} // core +} // legacy +} // ripple + +#endif diff --git a/src/ripple/legacy/0.27/book/impl/BookTip.cpp b/src/ripple/legacy/0.27/book/impl/BookTip.cpp new file mode 100644 index 0000000000..87d405758c --- /dev/null +++ b/src/ripple/legacy/0.27/book/impl/BookTip.cpp @@ -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 +#include + +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; +} + +} +} +} diff --git a/src/ripple/legacy/0.27/book/impl/OfferStream.cpp b/src/ripple/legacy/0.27/book/impl/OfferStream.cpp new file mode 100644 index 0000000000..f000015efb --- /dev/null +++ b/src/ripple/legacy/0.27/book/impl/OfferStream.cpp @@ -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 +#include + +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; +} + +} +} +} diff --git a/src/ripple/legacy/0.27/book/impl/Quality.cpp b/src/ripple/legacy/0.27/book/impl/Quality.cpp new file mode 100644 index 0000000000..c4ca287171 --- /dev/null +++ b/src/ripple/legacy/0.27/book/impl/Quality.cpp @@ -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 +#include +#include +#include + +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::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); +} + +} +} +} diff --git a/src/ripple/legacy/0.27/book/impl/Taker.cpp b/src/ripple/legacy/0.27/book/impl/Taker.cpp new file mode 100644 index 0000000000..500bb10221 --- /dev/null +++ b/src/ripple/legacy/0.27/book/impl/Taker.cpp @@ -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 +#include + +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); +} + +} +} +} diff --git a/src/ripple/unity/legacy.cpp b/src/ripple/unity/legacy.cpp new file mode 100644 index 0000000000..7debd523c3 --- /dev/null +++ b/src/ripple/unity/legacy.cpp @@ -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 + +#include +#include +#include +#include +#include +#include +