Files
xahaud/src/ripple/app/tx/impl/CreateOffer.cpp
Vinnie Falco d468deee12 Refactor Ledger and LedgerEntrySet:
Member functions and free functions on Ledger and LedgerEntrySet are
rewritten in terms of new abstract interfaces `BasicView` and `View`,
representing the set of non-decomposable primitives necessary to read
and write state map items in a ledger, and to overlay a discardable
view onto a Ledger that can calculate metadata during transaction
processing. const-correctness is enforced through the parameter and
return types.

The MetaView now supports multi-level stacking: A MetaView can be
stacked on top of either a Ledger or another MetaView, up to any
number of levels.

The getSLEi member function is removed. The CachedView wrapper
replaces it, wrapping a View such that any function called with a
CachedView will go through the SLECache.

* Add BasicView, View, CachedView
* Rename LedgerEntrySet to MetaView
* Factor out free functions
* Consolidate free functions in ViewAPI
* Remove unused class members and free functions
2015-06-22 18:39:33 -07:00

875 lines
31 KiB
C++

//------------------------------------------------------------------------------
/*
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/tx/impl/OfferStream.h>
#include <ripple/app/tx/impl/Taker.h>
#include <ripple/app/tx/impl/Transactor.h>
#include <ripple/ledger/ViewAPI.h>
#include <ripple/protocol/Quality.h>
#include <ripple/basics/Log.h>
#include <ripple/json/to_string.h>
#include <beast/cxx14/memory.h>
#include <beast/utility/Journal.h>
#include <beast/utility/WrappedSink.h>
#include <stdexcept>
namespace ripple {
class CreateOffer
: public Transactor
{
private:
// What kind of offer we are placing
CrossType cross_type_;
/** Determine if we are authorized to hold the asset we want to get */
TER
checkAcceptAsset(IssueRef issue) const
{
// Only valid for custom currencies
assert (!isXRP (issue.currency));
auto const issuerAccount = mEngine->view().read(
keylet::account(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)
{
auto const trustLine = mEngine->view().read(
keylet::line(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;
}
static
bool
dry_offer (View& view, Offer const& offer)
{
if (offer.fully_consumed ())
return true;
auto const amount = accountFunds(view, offer.owner(),
offer.amount().out, fhZERO_IF_FROZEN, getConfig());
return (amount <= zero);
}
static
std::pair<bool, Quality>
select_path (
bool have_direct, OfferStream const& direct,
bool have_bridge, OfferStream const& leg1, OfferStream const& leg2)
{
// If we don't have any viable path, why are we here?!
assert (have_direct || have_bridge);
// If there's no bridged path, the direct is the best by default.
if (!have_bridge)
return std::make_pair (true, direct.tip ().quality ());
Quality const bridged_quality (composed_quality (
leg1.tip ().quality (), leg2.tip ().quality ()));
if (have_direct)
{
// We compare the quality of the composed quality of the bridged
// offers and compare it against the direct offer to pick the best.
Quality const direct_quality (direct.tip ().quality ());
if (bridged_quality < direct_quality)
return std::make_pair (true, direct_quality);
}
// Either there was no direct offer, or it didn't have a better quality
// than the bridge.
return std::make_pair (false, bridged_quality);
}
std::pair<TER, Amounts>
bridged_cross (
Taker& taker,
View& view,
View& view_cancel,
Clock::time_point const when)
{
auto const& taker_amount = taker.original_offer ();
assert (!isXRP (taker_amount.in) && !isXRP (taker_amount.out));
if (isXRP (taker_amount.in) || isXRP (taker_amount.out))
throw std::logic_error ("Bridging with XRP and an endpoint.");
OfferStream offers_direct (view, view_cancel,
Book (taker.issue_in (), taker.issue_out ()), when, m_journal);
OfferStream offers_leg1 (view, view_cancel,
Book (taker.issue_in (), xrpIssue ()), when, m_journal);
OfferStream offers_leg2 (view, view_cancel,
Book (xrpIssue (), taker.issue_out ()), when, m_journal);
TER cross_result = tesSUCCESS;
// Note the subtle distinction here: self-offers encountered in the
// bridge are taken, but self-offers encountered in the direct book
// are not.
bool have_bridge = offers_leg1.step () && offers_leg2.step ();
bool have_direct = step_account (offers_direct, taker);
int count = 0;
// Modifying the order or logic of the operations in the loop will cause
// a protocol breaking change.
while (have_direct || have_bridge)
{
bool leg1_consumed = false;
bool leg2_consumed = false;
bool direct_consumed = false;
Quality quality;
bool use_direct;
std::tie (use_direct, quality) = select_path (
have_direct, offers_direct,
have_bridge, offers_leg1, offers_leg2);
// We are always looking at the best quality; we are done with
// crossing as soon as we cross the quality boundary.
if (taker.reject(quality))
break;
count++;
if (use_direct)
{
if (m_journal.debug)
{
m_journal.debug << count << " Direct:";
m_journal.debug << " offer: " << offers_direct.tip ();
m_journal.debug << " in: " << offers_direct.tip ().amount().in;
m_journal.debug << " out: " << offers_direct.tip ().amount ().out;
m_journal.debug << " owner: " << offers_direct.tip ().owner ();
m_journal.debug << " funds: " << accountFunds(view,
offers_direct.tip ().owner (),
offers_direct.tip ().amount ().out,
fhIGNORE_FREEZE,
getConfig());
}
cross_result = taker.cross(offers_direct.tip ());
m_journal.debug << "Direct Result: " << transToken (cross_result);
if (dry_offer (view, offers_direct.tip ()))
{
direct_consumed = true;
have_direct = step_account (offers_direct, taker);
}
}
else
{
if (m_journal.debug)
{
auto const owner1_funds_before = accountFunds(view,
offers_leg1.tip ().owner (),
offers_leg1.tip ().amount ().out,
fhIGNORE_FREEZE,
getConfig());
auto const owner2_funds_before = accountFunds(view,
offers_leg2.tip ().owner (),
offers_leg2.tip ().amount ().out,
fhIGNORE_FREEZE,
getConfig());
m_journal.debug << count << " Bridge:";
m_journal.debug << " offer1: " << offers_leg1.tip ();
m_journal.debug << " in: " << offers_leg1.tip ().amount().in;
m_journal.debug << " out: " << offers_leg1.tip ().amount ().out;
m_journal.debug << " owner: " << offers_leg1.tip ().owner ();
m_journal.debug << " funds: " << owner1_funds_before;
m_journal.debug << " offer2: " << offers_leg2.tip ();
m_journal.debug << " in: " << offers_leg2.tip ().amount ().in;
m_journal.debug << " out: " << offers_leg2.tip ().amount ().out;
m_journal.debug << " owner: " << offers_leg2.tip ().owner ();
m_journal.debug << " funds: " << owner2_funds_before;
}
cross_result = taker.cross(offers_leg1.tip (), offers_leg2.tip ());
m_journal.debug << "Bridge Result: " << transToken (cross_result);
if (dry_offer (view, offers_leg1.tip ()))
{
leg1_consumed = true;
have_bridge = (have_bridge && offers_leg1.step ());
}
if (dry_offer (view, offers_leg2.tip ()))
{
leg2_consumed = true;
have_bridge = (have_bridge && offers_leg2.step ());
}
}
if (cross_result != tesSUCCESS)
{
cross_result = tecFAILED_PROCESSING;
break;
}
if (taker.done())
{
m_journal.debug << "The taker reports he's done during crossing!";
break;
}
// Postcondition: If we aren't done, then we *must* have consumed at
// least one offer fully.
assert (direct_consumed || leg1_consumed || leg2_consumed);
if (!direct_consumed && !leg1_consumed && !leg2_consumed)
throw std::logic_error ("bridged crossing: nothing was fully consumed.");
}
return std::make_pair(cross_result, taker.remaining_offer ());
}
std::pair<TER, Amounts>
direct_cross (
Taker& taker,
View& view,
View& view_cancel,
Clock::time_point const when)
{
OfferStream offers (
view, view_cancel,
Book (taker.issue_in (), taker.issue_out ()),
when, m_journal);
TER cross_result (tesSUCCESS);
int count = 0;
bool have_offer = step_account (offers, taker);
// Modifying the order or logic of the operations in the loop will cause
// a protocol breaking change.
while (have_offer)
{
bool direct_consumed = false;
auto const& offer (offers.tip());
// We are done with crossing as soon as we cross the quality boundary
if (taker.reject (offer.quality()))
break;
count++;
if (m_journal.debug)
{
m_journal.debug << count << " Direct:";
m_journal.debug << " offer: " << offer;
m_journal.debug << " in: " << offer.amount ().in;
m_journal.debug << " out: " << offer.amount ().out;
m_journal.debug << " owner: " << offer.owner ();
m_journal.debug << " funds: " << accountFunds(view,
offer.owner (), offer.amount ().out, fhIGNORE_FREEZE, getConfig());
}
cross_result = taker.cross (offer);
m_journal.debug << "Direct Result: " << transToken (cross_result);
if (dry_offer (view, offer))
{
direct_consumed = true;
have_offer = step_account (offers, taker);
}
if (cross_result != tesSUCCESS)
{
cross_result = tecFAILED_PROCESSING;
break;
}
if (taker.done())
{
m_journal.debug << "The taker reports he's done during crossing!";
break;
}
// Postcondition: If we aren't done, then we *must* have consumed the
// offer on the books fully!
assert (direct_consumed);
if (!direct_consumed)
throw std::logic_error ("direct crossing: nothing was fully consumed.");
}
return std::make_pair(cross_result, taker.remaining_offer ());
}
// Step through the stream for as long as possible, skipping any offers
// that are from the taker or which cross the taker's threshold.
// Return false if the is no offer in the book, true otherwise.
static
bool
step_account (OfferStream& stream, Taker const& taker)
{
while (stream.step ())
{
auto const& offer = stream.tip ();
// This offer at the tip crosses the taker's threshold. We're done.
if (taker.reject (offer.quality ()))
return true;
// This offer at the tip is not from the taker. We're done.
if (offer.owner () != taker.account ())
return true;
}
// We ran out of offers. Can't advance.
return false;
}
// Fill offer as much as possible by consuming offers already on the books,
// and adjusting account balances accordingly.
//
// Charges fees on top to taker.
std::pair<TER, Amounts>
cross (
View& view,
View& cancel_view,
Amounts const& taker_amount)
{
Clock::time_point const when (
mEngine->getLedger ()->getParentCloseTimeNC ());
beast::WrappedSink takerSink (m_journal, "Taker ");
Taker taker (cross_type_, view, mTxnAccountID, taker_amount,
mTxn.getFlags(), beast::Journal (takerSink));
try
{
if (cross_type_ == CrossType::IouToIou)
return bridged_cross (taker, view, cancel_view, when);
return direct_cross (taker, view, cancel_view, when);
}
catch (std::exception const& e)
{
m_journal.error << "Exception during offer crossing: " << e.what ();
return std::make_pair (tecINTERNAL, taker.remaining_offer ());
}
catch (...)
{
m_journal.error << "Exception during offer crossing.";
return std::make_pair (tecINTERNAL, taker.remaining_offer ());
}
}
static
std::string
format_amount (STAmount const& amount)
{
std::string txt = amount.getText ();
txt += "/";
txt += to_string (amount.issue().currency);
return txt;
}
public:
CreateOffer (
CrossType cross_type,
STTx const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
: Transactor (
txn,
params,
engine,
deprecatedLogs().journal("CreateOffer"))
, cross_type_ (cross_type)
{
}
/** Returns the reserve the account would have if an offer was added. */
STAmount
getAccountReserve (SLE::pointer account)
{
return STAmount (mEngine->getLedger ()->getReserve (
account->getFieldU32 (sfOwnerCount) + 1));
}
TER
preCheck () override
{
std::uint32_t const uTxFlags = mTxn.getFlags ();
if (uTxFlags & tfOfferCreateMask)
{
if (m_journal.debug) m_journal.debug <<
"Malformed transaction: Invalid flags set.";
return temINVALID_FLAG;
}
bool const bImmediateOrCancel (uTxFlags & tfImmediateOrCancel);
bool const bFillOrKill (uTxFlags & tfFillOrKill);
if (bImmediateOrCancel && bFillOrKill)
{
if (m_journal.debug) m_journal.debug <<
"Malformed transaction: both IoC and FoK set.";
return temINVALID_FLAG;
}
bool const bHaveExpiration (mTxn.isFieldPresent (sfExpiration));
if (bHaveExpiration && (mTxn.getFieldU32 (sfExpiration) == 0))
{
if (m_journal.debug) m_journal.warning <<
"Malformed offer: bad expiration";
return temBAD_EXPIRATION;
}
bool const bHaveCancel (mTxn.isFieldPresent (sfOfferSequence));
if (bHaveCancel && (mTxn.getFieldU32 (sfOfferSequence) == 0))
{
if (m_journal.debug) m_journal.debug <<
"Malformed offer: bad cancel sequence";
return temBAD_SEQUENCE;
}
STAmount saTakerPays = mTxn.getFieldAmount (sfTakerPays);
STAmount saTakerGets = mTxn.getFieldAmount (sfTakerGets);
if (!isLegalNet (saTakerPays) || !isLegalNet (saTakerGets))
return temBAD_AMOUNT;
if (saTakerPays.native () && saTakerGets.native ())
{
if (m_journal.debug) m_journal.warning <<
"Malformed offer: XRP for XRP";
return temBAD_OFFER;
}
if (saTakerPays <= zero || saTakerGets <= zero)
{
if (m_journal.debug) m_journal.warning <<
"Malformed offer: bad amount";
return temBAD_OFFER;
}
auto const& uPaysIssuerID = saTakerPays.getIssuer ();
auto const& uPaysCurrency = saTakerPays.getCurrency ();
auto const& uGetsIssuerID = saTakerGets.getIssuer ();
auto const& uGetsCurrency = saTakerGets.getCurrency ();
if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID)
{
if (m_journal.debug) m_journal.debug <<
"Malformed offer: redundant offer";
return temREDUNDANT;
}
// We don't allow a non-native currency to use the currency code XRP.
if (badCurrency() == uPaysCurrency || badCurrency() == uGetsCurrency)
{
if (m_journal.debug) m_journal.warning <<
"Malformed offer: Bad currency.";
return temBAD_CURRENCY;
}
if (saTakerPays.native () != !uPaysIssuerID ||
saTakerGets.native () != !uGetsIssuerID)
{
if (m_journal.warning) m_journal.warning <<
"Malformed offer: bad issuer";
return temBAD_ISSUER;
}
return Transactor::preCheck ();
}
std::pair<TER, bool>
applyGuts (View& view, View& view_cancel)
{
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, true };
auto const& uPaysIssuerID = saTakerPays.getIssuer ();
auto const& uPaysCurrency = saTakerPays.getCurrency ();
auto const& uGetsIssuerID = saTakerGets.getIssuer ();
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 sequence
// number insufficient?
std::uint32_t const uAccountSequenceNext = mTxnAccount->getFieldU32 (sfSequence);
std::uint32_t const uSequence = mTxn.getSequence ();
// This is the original rate of the offer, and is the rate at which
// it will be placed, even if crossing offers change the amounts that
// end up on the books.
std::uint64_t const uRate = getRate (saTakerGets, saTakerPays);
TER result = tesSUCCESS;
// This is the ledger view that we work against. Transactions are applied
// as we go on processing transactions.
auto const sleCreator = view.peek (
keylet::account(mTxnAccountID));
if (isGlobalFrozen (view, uPaysIssuerID) || isGlobalFrozen (view, uGetsIssuerID))
{
if (m_journal.warning) m_journal.warning <<
"Offer involves frozen asset";
result = tecFROZEN;
}
else if (accountFunds(view, mTxnAccountID, saTakerGets,
fhZERO_IF_FROZEN, getConfig()) <= zero)
{
if (m_journal.debug) m_journal.debug <<
"delay: Offers must be at least partially funded.";
result = tecUNFUNDED_OFFER;
}
// This can probably be simplified to make sure that you cancel sequences
// before the transaction sequence number.
else if (bHaveCancel && (uAccountSequenceNext - 1 <= uCancelSequence))
{
if (m_journal.debug) m_journal.debug <<
"uAccountSequenceNext=" << uAccountSequenceNext <<
" uOfferSequence=" << uCancelSequence;
result = temBAD_SEQUENCE;
}
if (result != tesSUCCESS)
{
m_journal.debug << "final result: " << transToken (result);
return { result, true };
}
// Process a cancellation request that's passed along with an offer.
if (bHaveCancel)
{
auto const sleCancel = view.peek(
keylet::offer(mTxnAccountID, uCancelSequence));
// It's not an error to not find the offer to cancel: it might have
// been consumed or removed. If it is found, however, it's an error
// to fail to delete it.
if (sleCancel)
{
m_journal.debug << "Create cancels order " << uCancelSequence;
result = offerDelete (view, 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, true };
}
// Make sure that we are authorized to hold what the taker will pay us.
if (result == tesSUCCESS && !saTakerPays.native ())
result = checkAcceptAsset (Issue (uPaysCurrency, uPaysIssuerID));
bool const bOpenLedger (mParams & tapOPEN_LEDGER);
bool crossed = false;
if (result == tesSUCCESS)
{
// We reverse pays and gets because during crossing we are taking.
Amounts const taker_amount (saTakerGets, saTakerPays);
// The amount of the offer that is unfilled after crossing has been
// performed. It may be equal to the original amount (didn't cross),
// empty (fully crossed), or something in-between.
Amounts place_offer;
m_journal.debug << "Attempting cross: " <<
to_string (taker_amount.in.issue ()) << " -> " <<
to_string (taker_amount.out.issue ());
if (m_journal.trace)
{
m_journal.debug << " mode: " <<
(bPassive ? "passive " : "") <<
(bSell ? "sell" : "buy");
m_journal.trace <<" in: " << format_amount (taker_amount.in);
m_journal.trace << " out: " << format_amount (taker_amount.out);
}
std::tie(result, place_offer) = cross (view, view_cancel, taker_amount);
assert (result != tefINTERNAL);
if (m_journal.trace)
{
m_journal.trace << "Cross result: " << transToken (result);
m_journal.trace << " in: " << format_amount (place_offer.in);
m_journal.trace << " out: " << format_amount (place_offer.out);
}
if (result == tecFAILED_PROCESSING && bOpenLedger)
result = telFAILED_PROCESSING;
if (result != tesSUCCESS)
{
m_journal.debug << "final result: " << transToken (result);
return { result, true };
}
assert (saTakerGets.issue () == place_offer.in.issue ());
assert (saTakerPays.issue () == place_offer.out.issue ());
if (taker_amount != place_offer)
crossed = true;
// The offer that we need to place after offer crossing should
// never be negative. If it is, something went very very wrong.
if (place_offer.in < zero || place_offer.out < zero)
{
m_journal.fatal << "Cross left offer negative!" <<
" in: " << format_amount (place_offer.in) <<
" out: " << format_amount (place_offer.out);
return { tefINTERNAL, true };
}
if (place_offer.in == zero || place_offer.out == zero)
{
m_journal.debug << "Offer fully crossed!";
return { result, true };
}
// We now need to adjust the offer to reflect the amount left after
// crossing. We reverse in and out here, since during crossing we
// were the taker.
saTakerPays = place_offer.out;
saTakerGets = place_offer.in;
}
assert (saTakerPays > zero && saTakerGets > zero);
if (result != tesSUCCESS)
{
m_journal.debug << "final result: " << transToken (result);
return { result, true };
}
if (m_journal.trace)
{
m_journal.trace << "Place" << (crossed ? " remaining " : " ") << "offer:";
m_journal.trace << " Pays: " << saTakerPays.getFullText ();
m_journal.trace << " Gets: " << saTakerGets.getFullText ();
}
// For 'fill or kill' offers, failure to fully cross means that the
// entire operation should be aborted, with only fees paid.
if (bFillOrKill)
{
m_journal.trace << "Fill or Kill: offer killed";
return { tesSUCCESS, false };
}
// For 'immediate or cancel' offers, the amount remaining doesn't get
// placed - it gets cancelled and the operation succeeds.
if (bImmediateOrCancel)
{
m_journal.trace << "Immediate or cancel: offer cancelled";
return { tesSUCCESS, true };
}
if (mPriorBalance < getAccountReserve (sleCreator))
{
// If we are here, the signing account had an insufficient reserve
// *prior* to our processing. If something actually crossed, then
// we allow this; otherwise, we just claim a fee.
if (!crossed)
result = tecINSUF_RESERVE_OFFER;
if (result != tesSUCCESS)
m_journal.debug << "final result: " << transToken (result);
return { result, true };
}
// We need to place the remainder of the offer into its order book.
auto const offer_index = getOfferIndex (mTxnAccountID, uSequence);
std::uint64_t uOwnerNode;
std::uint64_t uBookNode;
uint256 uDirectory;
// Add offer to owner's directory.
result = dirAdd(view, uOwnerNode,
getOwnerDirIndex (mTxnAccountID), offer_index,
std::bind (
&ownerDirDescriber, std::placeholders::_1,
std::placeholders::_2, mTxnAccountID));
if (result == tesSUCCESS)
{
// Update owner count.
adjustOwnerCount(view, sleCreator, 1);
if (m_journal.trace) m_journal.trace <<
"adding to book: " << to_string (saTakerPays.issue ()) <<
" : " << to_string (saTakerGets.issue ());
uint256 const book_base (getBookBase (
{ saTakerPays.issue (), saTakerGets.issue () }));
// We use the original rate to place the offer.
uDirectory = getQualityIndex (book_base, uRate);
// Add offer to order book.
result = dirAdd (view, uBookNode, uDirectory, offer_index,
std::bind (
&qualityDirDescriber, std::placeholders::_1,
std::placeholders::_2, saTakerPays.getCurrency (),
uPaysIssuerID, saTakerGets.getCurrency (),
uGetsIssuerID, uRate));
}
if (result == tesSUCCESS)
{
auto sleOffer = std::make_shared<SLE>(ltOFFER, offer_index);
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);
view.insert(sleOffer);
}
if (result != tesSUCCESS)
m_journal.debug << "final result: " << transToken (result);
return { result, true };
}
TER
doApply() override
{
bool const openLedger = mParams & tapOPEN_LEDGER;
// This is the ledger view that we work against. Transactions are applied
// as we go on processing transactions.
MetaView view (mEngine->view(), openLedger);
// This is a checkpoint with just the fees paid. If something goes wrong
// with this transaction, we roll back to this ledger.
MetaView viewCancel (mEngine->view(), openLedger);
auto const result = applyGuts(view, viewCancel);
if (result.second)
view.apply();
else
viewCancel.apply();
return result.first;
}
};
TER
transact_CreateOffer (
STTx const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
{
CrossType cross_type = CrossType::IouToIou;
bool const pays_xrp = txn.getFieldAmount (sfTakerPays).native ();
bool const gets_xrp = txn.getFieldAmount (sfTakerGets).native ();
if (pays_xrp && !gets_xrp)
cross_type = CrossType::IouToXrp;
else if (gets_xrp && !pays_xrp)
cross_type = CrossType::XrpToIou;
return CreateOffer (cross_type, txn, params, engine).apply ();
}
}