mirror of
https://github.com/Xahau/xahaud.git
synced 2025-12-06 17:27:52 +00:00
* Reduce public interfaces * Remove wrapper functions * Remove freeze timed cutover code * Return results directly instead of via ref parameters
889 lines
32 KiB
C++
889 lines
32 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/book/OfferStream.h>
|
|
#include <ripple/app/book/Taker.h>
|
|
#include <ripple/app/book/Types.h>
|
|
#include <ripple/app/book/Amounts.h>
|
|
#include <ripple/app/book/Quality.h>
|
|
#include <ripple/app/transactors/Transactor.h>
|
|
#include <ripple/basics/Log.h>
|
|
#include <ripple/json/to_string.h>
|
|
|
|
#include <ripple/legacy/0.27/CreateOffer.h>
|
|
#include <ripple/legacy/0.27/Emulate027.h>
|
|
|
|
#include <beast/cxx14/memory.h>
|
|
#include <stdexcept>
|
|
|
|
namespace ripple {
|
|
|
|
class CreateOffer
|
|
: public Transactor
|
|
{
|
|
private:
|
|
// What kind of offer we are placing
|
|
core::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));
|
|
|
|
SLE::pointer const issuerAccount = mEngine->view().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->view().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;
|
|
}
|
|
|
|
static
|
|
bool
|
|
dry_offer (core::LedgerView& view, core::Offer const& offer)
|
|
{
|
|
if (offer.fully_consumed ())
|
|
return true;
|
|
|
|
auto const funds (view.accountFunds (offer.owner(),
|
|
offer.amount().out, fhZERO_IF_FROZEN));
|
|
|
|
return (funds <= zero);
|
|
}
|
|
|
|
static
|
|
std::pair<bool, core::Quality>
|
|
select_path (
|
|
bool have_direct, core::OfferStream const& direct,
|
|
bool have_bridge, core::OfferStream const& leg1, core::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 ());
|
|
|
|
core::Quality const bridged_quality (core::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.
|
|
core::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, core::Amounts>
|
|
bridged_cross (
|
|
core::Taker& taker,
|
|
core::LedgerView& view,
|
|
core::LedgerView& view_cancel,
|
|
core::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.");
|
|
|
|
core::OfferStream offers_direct (view, view_cancel,
|
|
Book (taker.issue_in (), taker.issue_out ()), when, m_journal);
|
|
|
|
core::OfferStream offers_leg1 (view, view_cancel,
|
|
Book (taker.issue_in (), xrpIssue ()), when, m_journal);
|
|
|
|
core::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;
|
|
|
|
core::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: " << view.accountFunds (
|
|
offers_direct.tip ().owner (),
|
|
offers_direct.tip ().amount ().out,
|
|
fhIGNORE_FREEZE);
|
|
}
|
|
|
|
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 = view.accountFunds (
|
|
offers_leg1.tip ().owner (),
|
|
offers_leg1.tip ().amount ().out,
|
|
fhIGNORE_FREEZE);
|
|
|
|
auto const owner2_funds_before = view.accountFunds (
|
|
offers_leg2.tip ().owner (),
|
|
offers_leg2.tip ().amount ().out,
|
|
fhIGNORE_FREEZE);
|
|
|
|
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, core::Amounts>
|
|
direct_cross (
|
|
core::Taker& taker,
|
|
core::LedgerView& view,
|
|
core::LedgerView& view_cancel,
|
|
core::Clock::time_point const when)
|
|
{
|
|
core::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: " << view.accountFunds (
|
|
offer.owner (), offer.amount ().out, fhIGNORE_FREEZE);
|
|
}
|
|
|
|
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 (core::OfferStream& stream, core::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, core::Amounts>
|
|
cross (
|
|
core::LedgerView& view,
|
|
core::LedgerView& cancel_view,
|
|
core::Amounts const& taker_amount)
|
|
{
|
|
core::Clock::time_point const when (
|
|
mEngine->getLedger ()->getParentCloseTimeNC ());
|
|
|
|
core::Taker taker (cross_type_, view, mTxnAccountID, taker_amount, mTxn.getFlags());
|
|
|
|
try
|
|
{
|
|
if (m_journal.debug)
|
|
{
|
|
auto const funds = view.accountFunds (
|
|
taker.account(), taker_amount.in, fhIGNORE_FREEZE);
|
|
|
|
m_journal.debug << "Crossing:";
|
|
m_journal.debug << " Taker: " << to_string (mTxnAccountID);
|
|
m_journal.debug << " Balance: " << format_amount (funds);
|
|
}
|
|
|
|
if (cross_type_ == core::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 += amount.getHumanCurrency ();
|
|
return txt;
|
|
}
|
|
|
|
public:
|
|
CreateOffer (
|
|
core::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. */
|
|
std::uint32_t
|
|
getAccountReserve (SLE::pointer account)
|
|
{
|
|
return 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.isNative () && saTakerGets.isNative ())
|
|
{
|
|
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.isNative () != !uPaysIssuerID ||
|
|
saTakerGets.isNative () != !uGetsIssuerID)
|
|
{
|
|
if (m_journal.warning) m_journal.warning <<
|
|
"Malformed offer: bad issuer";
|
|
return temBAD_ISSUER;
|
|
}
|
|
|
|
return Transactor::preCheck ();
|
|
}
|
|
|
|
TER
|
|
doApply () override
|
|
{
|
|
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 ();
|
|
|
|
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.
|
|
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.
|
|
core::LedgerView view_checkpoint (view);
|
|
|
|
view.bumpSeq (); // Begin ledger variance.
|
|
|
|
SLE::pointer sleCreator = mEngine->view().entryCache (
|
|
ltACCOUNT_ROOT, getAccountRootIndex (mTxnAccountID));
|
|
|
|
if (view.isGlobalFrozen (uPaysIssuerID) || view.isGlobalFrozen (uGetsIssuerID))
|
|
{
|
|
m_journal.warning <<
|
|
"Offer involves frozen asset";
|
|
|
|
result = tecFROZEN;
|
|
}
|
|
else if (view.accountFunds (
|
|
mTxnAccountID, saTakerGets, fhZERO_IF_FROZEN) <= zero)
|
|
{
|
|
m_journal.warning <<
|
|
"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;
|
|
}
|
|
|
|
// Process a cancellation request that's passed along with an offer.
|
|
if (bHaveCancel)
|
|
{
|
|
SLE::pointer sleCancel = mEngine->view().entryCache (ltOFFER,
|
|
getOfferIndex (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 = 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 (result == tesSUCCESS && !saTakerPays.isNative ())
|
|
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.
|
|
core::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.
|
|
core::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_checkpoint, 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (place_offer.in == zero || place_offer.out == zero)
|
|
{
|
|
m_journal.debug << "Offer fully crossed!";
|
|
return result;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
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";
|
|
view.swapWith (view_checkpoint);
|
|
return tesSUCCESS;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
if (mPriorBalance.getNValue () < 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 (bOpenLedger && ripple::legacy::emulate027 (mEngine->getLedger()))
|
|
result = tecINSUF_RESERVE_OFFER;
|
|
|
|
if (result != tesSUCCESS)
|
|
m_journal.debug << "final result: " << transToken (result);
|
|
|
|
return result;
|
|
}
|
|
|
|
// 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 = view.dirAdd (uOwnerNode,
|
|
getOwnerDirIndex (mTxnAccountID), offer_index,
|
|
std::bind (
|
|
&Ledger::ownerDirDescriber, std::placeholders::_1,
|
|
std::placeholders::_2, mTxnAccountID));
|
|
|
|
if (result == tesSUCCESS)
|
|
{
|
|
// Update owner count.
|
|
view.incrementOwnerCount (sleCreator);
|
|
|
|
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 = view.dirAdd (uBookNode, uDirectory, offer_index,
|
|
std::bind (
|
|
&Ledger::qualityDirDescriber, std::placeholders::_1,
|
|
std::placeholders::_2, saTakerPays.getCurrency (),
|
|
uPaysIssuerID, saTakerGets.getCurrency (),
|
|
uGetsIssuerID, uRate));
|
|
}
|
|
|
|
if (result == tesSUCCESS)
|
|
{
|
|
SLE::pointer sleOffer (mEngine->view().entryCreate (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);
|
|
}
|
|
|
|
if (result != tesSUCCESS)
|
|
m_journal.debug << "final result: " << transToken (result);
|
|
|
|
return result;
|
|
}
|
|
};
|
|
|
|
TER
|
|
transact_CreateOffer (
|
|
STTx const& txn,
|
|
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 ();
|
|
bool const gets_xrp = txn.getFieldAmount (sfTakerGets).isNative ();
|
|
|
|
if (pays_xrp && !gets_xrp)
|
|
cross_type = core::CrossType::IouToXrp;
|
|
else if (gets_xrp && !pays_xrp)
|
|
cross_type = core::CrossType::XrpToIou;
|
|
|
|
return CreateOffer (cross_type, txn, params, engine).apply ();
|
|
}
|
|
|
|
}
|