Autobridging hooks and cleanups:

- Detect when we should autobridge and get bridge books.
- Move applyOffer from STAmount into the OfferCreate transactor.
This commit is contained in:
Nik Bougalis
2014-03-26 12:25:39 -07:00
committed by Vinnie Falco
parent 166b8963bb
commit a5488c3727
4 changed files with 538 additions and 425 deletions

View File

@@ -95,6 +95,225 @@ bool OfferCreateTransactor::isValidOffer (
return true;
}
/**
*/
bool OfferCreateTransactor::canCross (
STAmount const& saTakerFunds,
STAmount const& saSubTakerPays,
STAmount const& saSubTakerGets,
std::uint64_t uTipQuality,
std::uint64_t uTakeQuality,
bool isPassive,
bool& isUnfunded,
TER& terResult) const
{
if (!saTakerFunds.isPositive ())
{
// Taker is out of funds. Don't create the offer.
isUnfunded = true;
terResult = tesSUCCESS;
return false;
}
if (!saSubTakerPays.isPositive() || !saSubTakerGets.isPositive())
{
// Offer is completely consumed
terResult = tesSUCCESS;
return false;
}
// TODO We must also consider the synthesized tip as well
if ((uTakeQuality < uTipQuality) || (isPassive && (uTakeQuality == uTipQuality)))
{
// Offer does not cross this offer
terResult = tesSUCCESS;
return false;
}
return true;
}
/** Apply a particular offer
An existing offer is on the books. The price is the offer owner's, which
might be better for taker. The taker pays what they can and gets all taker
can pay for with `saTakerFunds`/`uTakerPaysRate`, limited by `saOfferPays`
and `saOfferFunds`/`uOfferPaysRate`.
If taker is an offer, taker is spending at same or better rate than they
wanted.
The taker should consider themselves as wanting to buy X amount and as
willing to pay at most the rate of Y/X each.
After having some part of their offer fulfilled at a better rate their offer
should be reduced accordingly.
There are no quality costs for offer vs offer taking.
@param bSell `true` if using sell semantics.
@param uTakerPaysRate >= QUALITY_ONE | TransferRate for third party IOUs paid by taker.
@param uOfferPaysRate >= QUALITY_ONE | TransferRate for third party IOUs paid by offer owner.
@param saOfferRate Original saOfferGets/saOfferPays, when offer was made.
@param saOfferFunds Limit for saOfferPays : How much can pay including fees.
@param saTakerFunds Limit for saOfferGets : How much can pay including fees.
@param saOfferPays Request : this should be reduced as the offer is fullfilled.
@param saOfferGets Request : this should be reduced as the offer is fullfilled.
@param saTakerPays Limit for taker to pay.
@param saTakerGets Limit for taker to get.
@param saTakerPaid Actual amount the taker paid
@param saTakerGot Actual amount the taker got
@param saTakerIssuerFee Actual
@param saOfferIssuerFee Actual
@note Buy vs. Sell semantics:
In Ripple, placed offers are always executed at the rate at which they were
placed. However, the offer we are processing hasn't been placed yet and it
can cross with offers that have. Such crossing offers can be consumed by
existing offers that give a better rate.
The only difference between buy and sell offers is the way in which we
calculate what remains to be placed on the books after crossing offers
are consumed.
First, consider the case where a user is trying to place an offer to buy
1 BTC for 700 USD:
If an offer to giving 0.5 BTC for 300 USD is on the books, the offers cross
and under buy semantics the user buys 0.5 BTC for 300 USD (less than the
350 USD he was willing to pay) and the offer is adjusted and placed on the
books as: "give 0.5 BTC for 350 USD" which is the amount left to buy at the
original rate.
Now consider the case where a user is trying to place an offer to sell
700 USD for 1 BTC instead:
If an offer to giving 0.5 BTC for 300 USD is on the books, the offers cross
and under sell semantics the user will sell 350 USD for 0.5 BTC (more than
he was willing to get) and his offer will be be adjusted and placed on the
books as: "give 0.57 BTC for 400 USD" which is what is left to sell at the
original rate.
*/
bool OfferCreateTransactor::applyOffer (
const bool bSell,
const std::uint32_t uTakerPaysRate, const std::uint32_t uOfferPaysRate,
const STAmount& saOfferRate,
const STAmount& saOfferFunds, const STAmount& saTakerFunds,
const STAmount& saOfferPays, const STAmount& saOfferGets,
const STAmount& saTakerPays, const STAmount& saTakerGets,
STAmount& saTakerPaid, STAmount& saTakerGot,
STAmount& saTakerIssuerFee, STAmount& saOfferIssuerFee)
{
saOfferGets.throwComparable (saTakerFunds);
assert (saOfferFunds.isPositive () && saTakerFunds.isPositive ()); // Both must have funds.
assert (saOfferGets.isPositive () && saOfferPays.isPositive ()); // Must not be a null offer.
// Available = limited by funds.
// Limit offerer funds available, by transfer fees.
STAmount saOfferFundsAvailable = QUALITY_ONE == uOfferPaysRate
? saOfferFunds // As is.
: STAmount::divide (saOfferFunds, STAmount (CURRENCY_ONE, ACCOUNT_ONE, uOfferPaysRate, -9)); // Reduce by offer fees.
WriteLog (lsINFO, STAmount) << "applyOffer: uOfferPaysRate=" << uOfferPaysRate;
WriteLog (lsINFO, STAmount) << "applyOffer: saOfferFundsAvailable=" << saOfferFundsAvailable.getFullText ();
// Limit taker funds available, by transfer fees.
STAmount saTakerFundsAvailable = QUALITY_ONE == uTakerPaysRate
? saTakerFunds // As is.
: STAmount::divide (saTakerFunds, STAmount (CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9)); // Reduce by taker fees.
WriteLog (lsINFO, STAmount) << "applyOffer: TAKER_FEES=" << STAmount (CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9).getFullText ();
WriteLog (lsINFO, STAmount) << "applyOffer: uTakerPaysRate=" << uTakerPaysRate;
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerFundsAvailable=" << saTakerFundsAvailable.getFullText ();
STAmount saOfferPaysAvailable; // Amount offer can pay out, limited by offer and offerer funds.
STAmount saOfferGetsAvailable; // Amount offer would get, limited by offer funds.
if (saOfferFundsAvailable >= saOfferPays)
{
// Offer was fully funded, avoid math shenanigans.
saOfferPaysAvailable = saOfferPays;
saOfferGetsAvailable = saOfferGets;
}
else
{
// Offer has limited funding, limit offer gets and pays by funds available.
saOfferPaysAvailable = saOfferFundsAvailable;
saOfferGetsAvailable = std::min (saOfferGets, STAmount::mulRound (saOfferPaysAvailable, saOfferRate, saOfferGets, true));
}
WriteLog (lsINFO, STAmount) << "applyOffer: saOfferPaysAvailable=" << saOfferPaysAvailable.getFullText ();
WriteLog (lsINFO, STAmount) << "applyOffer: saOfferGetsAvailable=" << saOfferGetsAvailable.getFullText ();
STAmount saTakerPaysAvailable = std::min (saTakerPays, saTakerFundsAvailable);
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerPaysAvailable=" << saTakerPaysAvailable.getFullText ();
// Limited = limited by other sides raw numbers.
// Taker can't pay more to offer than offer can get.
STAmount saTakerPaysLimited = std::min (saTakerPaysAvailable, saOfferGetsAvailable);
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerPaysLimited=" << saTakerPaysLimited.getFullText ();
// Align saTakerGetsLimited with saTakerPaysLimited.
STAmount saTakerGetsLimited = saTakerPaysLimited >= saOfferGetsAvailable // Cannot actually be greater
? saOfferPaysAvailable // Potentially take entire offer. Avoid math shenanigans.
: std::min (saOfferPaysAvailable, STAmount::divRound (saTakerPaysLimited, saOfferRate, saTakerGets, true)); // Take a portion of offer.
WriteLog (lsINFO, STAmount) << "applyOffer: saOfferRate=" << saOfferRate.getFullText ();
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerGetsLimited=" << saTakerGetsLimited.getFullText ();
// Got & Paid = Calculated by price and transfered without fees.
// Compute from got as when !bSell, we want got to be exact to finish off offer if possible.
saTakerGot = bSell
? saTakerGetsLimited // Get all available that are paid for.
: std::min (saTakerGets, saTakerGetsLimited); // Limit by wanted.
saTakerPaid = saTakerGot >= saTakerGetsLimited
? saTakerPaysLimited
: std::min (saTakerPaysLimited, STAmount::mulRound (saTakerGot, saOfferRate, saTakerFunds, true));
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerGot=" << saTakerGot.getFullText ();
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerPaid=" << saTakerPaid.getFullText ();
if (uTakerPaysRate == QUALITY_ONE)
{
saTakerIssuerFee = STAmount (saTakerPaid.getCurrency (), saTakerPaid.getIssuer ());
}
else
{
// Compute fees in a rounding safe way.
STAmount saTransferRate = STAmount (CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9);
WriteLog (lsINFO, STAmount) << "applyOffer: saTransferRate=" << saTransferRate.getFullText ();
// TakerCost includes transfer fees.
STAmount saTakerCost = STAmount::mulRound (saTakerPaid, saTransferRate, true);
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerCost=" << saTakerCost.getFullText ();
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerFunds=" << saTakerFunds.getFullText ();
saTakerIssuerFee = saTakerCost > saTakerFunds
? saTakerFunds - saTakerPaid // Not enough funds to cover fee, stiff issuer the rounding error.
: saTakerCost - saTakerPaid;
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerIssuerFee=" << saTakerIssuerFee.getFullText ();
assert (!saTakerIssuerFee.isNegative ());
}
if (uOfferPaysRate == QUALITY_ONE)
{
saOfferIssuerFee = STAmount (saTakerGot.getCurrency (), saTakerGot.getIssuer ());
}
else
{
// Compute fees in a rounding safe way.
STAmount saOfferCost = STAmount::mulRound (saTakerGot, STAmount (CURRENCY_ONE, ACCOUNT_ONE, uOfferPaysRate, -9), true);
saOfferIssuerFee = saOfferCost > saOfferFunds
? saOfferFunds - saTakerGot // Not enough funds to cover fee, stiff issuer the rounding error.
: saOfferCost - saTakerGot;
}
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerGot=" << saTakerGot.getFullText ();
return saTakerGot >= saOfferPaysAvailable; // True, if consumed offer.
}
/** Take as much as possible.
We adjusts account balances and charges fees on top to taker.
@@ -153,292 +372,329 @@ TER OfferCreateTransactor::takeOffers (
// TODO: need to track the synthesized book (source->XRP + XRP->target)
// here as well.
OrderBookIterator bookIterator (lesActive,
OrderBookIterator directBookIter (lesActive,
saTakerPays.getCurrency(), saTakerPays.getIssuer(),
saTakerGets.getCurrency(), saTakerGets.getIssuer());
while ((temUNCERTAIN == terResult) && bookIterator.nextOffer())
#ifdef RIPPLE_AUTOBRIDGE
std::unique_ptr<OrderBookIterator> bridgeBookIn;
std::unique_ptr<OrderBookIterator> bridgeBookOut;
// If XRP isn't an already an endpoint, then we also create two additional
// iterators:
// (1) source -> XPR
// (2) XRP -> target
if (!saTakerPays.isNative () && !saTakerGets.isNative ())
{
if (m_journal.trace)
{
beast::Journal::ScopedStream ss (m_journal.trace);
ss <<
"Autobridging offer '" << saTakerPays.getFullText () <<
"' to '" << saTakerGets.getFullText () << "'" << std::endl;
ss <<
"Selected bridge: '" <<
STAmount::createHumanCurrency(saTakerPays.getCurrency()) <<
"/" <<
RippleAddress::createHumanAccountID (saTakerPays.getIssuer()) <<
"' - XRP - '" <<
STAmount::createHumanCurrency(saTakerGets.getCurrency ()) <<
"/" <<
RippleAddress::createHumanAccountID (saTakerGets.getIssuer()) <<
"'";
}
}
#endif
while ((temUNCERTAIN == terResult) && directBookIter.nextOffer())
{
STAmount saTakerFunds = lesActive.accountFunds (uTakerAccountID, saTakerPays);
STAmount saSubTakerPays = saTakerPays - saTakerPaid; // How much more to spend.
STAmount saSubTakerGets = saTakerGets - saTakerGot; // How much more is wanted.
std::uint64_t uTipQuality = bookIterator.getCurrentQuality();
std::uint64_t uTipQuality = directBookIter.getCurrentQuality();
if (!saTakerFunds.isPositive ())
#ifdef RIPPLE_AUTOBRIDGE
if (bridgeBookIn && bridgeBookIn->nextOffer() &&
bridgeBookOut && bridgeBookOut->nextOffer())
{
// Taker is out of funds. Don't create the offer.
bUnfunded = true;
terResult = tesSUCCESS;
STAmount bridgeIn = STAmount::setRate (bridgeBookIn->getCurrentQuality());
STAmount bridgeOut = STAmount::setRate (bridgeBookOut->getCurrentQuality());
m_journal.error <<
"The direct quality is: " << STAmount::setRate (uTipQuality);
m_journal.error <<
"The bridge-to-XRP quality is: " << bridgeIn;
m_journal.error <<
"The bridge-from-XRP quality is: " << bridgeOut;
m_journal.error <<
"The bridge synthetic quality is: " <<
STAmount::multiply (bridgeIn, bridgeOut, CURRENCY_ONE, ACCOUNT_ONE);
}
else if (!saSubTakerPays.isPositive() || !saSubTakerGets.isPositive())
{
// Offer is completely consumed
terResult = tesSUCCESS;
#endif
if (!canCross(
saTakerFunds,
saSubTakerPays,
saSubTakerGets,
uTipQuality,
uTakeQuality,
bPassive,
bUnfunded,
terResult))
break;
// We have a crossing offer to consider.
// TODO Must consider the synthesized orderbook instead of just the
// direct one (i.e. look at A->XRP->B)
SLE::pointer sleOffer = directBookIter.getCurrentOffer ();
if (!sleOffer)
{ // offer is in directory but not in ledger
// CHECKME is this cleaning up corruption?
uint256 offerIndex = directBookIter.getCurrentIndex ();
m_journal.warning <<
"takeOffers: offer not found : " << offerIndex;
usMissingOffers.insert (missingOffer_t (
directBookIter.getCurrentIndex (),
directBookIter.getCurrentDirectory ()));
continue;
}
// TODO We must also consider the synthesized tip as well
else if ((uTakeQuality < uTipQuality)
|| (bPassive && uTakeQuality == uTipQuality))
m_journal.debug <<
"takeOffers: considering offer : " <<
sleOffer->getJson (0);
uint160 const& uOfferOwnerID = sleOffer->getFieldAccount160 (sfAccount);
STAmount saOfferPays = sleOffer->getFieldAmount (sfTakerGets);
STAmount saOfferGets = sleOffer->getFieldAmount (sfTakerPays);
STAmount saOfferFunds; // Funds of offer owner to payout.
bool const bValid = isValidOffer (
sleOffer,
uOfferOwnerID,
saOfferPays,
saOfferGets,
uTakerAccountID,
usOfferUnfundedBecame,
usAccountTouched,
saOfferFunds);
if (bValid)
{
// Offer does not cross this offer
STAmount saTipRate = STAmount::setRate (uTipQuality);
STAmount saSubTakerPaid;
STAmount saSubTakerGot;
STAmount saTakerIssuerFee;
STAmount saOfferIssuerFee;
STAmount saOfferRate = STAmount::setRate (uTipQuality);
if (m_journal.debug) m_journal.debug <<
"takeOffers: done:" <<
" uTakeQuality=" << uTakeQuality <<
" " << get_compare_sign (uTakeQuality, uTipQuality) <<
" uTipQuality=" << uTipQuality <<
" saTakerRate=" << saTakerRate <<
" " << get_compare_sign (saTakerRate, saTipRate) <<
" saTipRate=" << saTakerRate <<
" bPassive=" << bPassive;
if (m_journal.trace)
{
m_journal.trace <<
"takeOffers: applyOffer: saTakerPays: " <<
saTakerPays.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saTakerPaid: " <<
saTakerPaid.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saTakerFunds: " <<
saTakerFunds.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saOfferFunds: " <<
saOfferFunds.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saOfferPays: " <<
saOfferPays.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saOfferGets: " <<
saOfferGets.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saOfferRate: " <<
saOfferRate.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saSubTakerPays: " <<
saSubTakerPays.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saSubTakerGets: " <<
saSubTakerGets.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saTakerPays: " <<
saTakerPays.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saTakerGets: " <<
saTakerGets.getFullText ();
}
terResult = tesSUCCESS;
}
else
{
// We have a crossing offer to consider.
bool const bOfferDelete = applyOffer (
bSell,
lesActive.rippleTransferRate (
uTakerAccountID,
uOfferOwnerID,
uTakerPaysAccountID),
lesActive.rippleTransferRate (
uOfferOwnerID,
uTakerAccountID,
uTakerGetsAccountID),
saOfferRate,
saOfferFunds,
saTakerFunds,
saOfferPays,
saOfferGets,
saSubTakerPays,
saSubTakerGets,
saSubTakerPaid,
saSubTakerGot,
saTakerIssuerFee,
saOfferIssuerFee);
// TODO Must consider the synthesized orderbook instead of just the
// direct one (i.e. look at A->XRP->B)
SLE::pointer sleOffer = bookIterator.getCurrentOffer ();
if (m_journal.debug)
{
m_journal.debug <<
"takeOffers: applyOffer: saSubTakerPaid: " <<
saSubTakerPaid.getFullText ();
m_journal.debug <<
"takeOffers: applyOffer: saSubTakerGot: " <<
saSubTakerGot.getFullText ();
}
if (!sleOffer)
{ // offer is in directory but not in ledger
// CHECKME is this cleaning up corruption?
uint256 offerIndex = bookIterator.getCurrentIndex ();
m_journal.warning <<
"takeOffers: offer not found : " << offerIndex;
usMissingOffers.insert (missingOffer_t (
bookIterator.getCurrentIndex (), bookIterator.getCurrentDirectory ()));
// Adjust offer
// TODO We need to consider the combined (synthesized)
// order book.
// Offer owner will pay less. Subtract what taker just got.
saOfferPays -= saSubTakerGot;
sleOffer->setFieldAmount (sfTakerGets, saOfferPays);
// Offer owner will get less. Subtract what owner just paid.
saOfferGets -= saSubTakerPaid;
sleOffer->setFieldAmount (sfTakerPays, saOfferGets);
mEngine->entryModify (sleOffer);
if (bOfferDelete)
{
// TODO need to handle the synthetic case here
// Offer now fully claimed or now unfunded.
m_journal.debug <<
"takeOffers: Offer claimed: Delete.";
// Delete unfunded offer on success.
usOfferUnfundedBecame.insert (sleOffer->getIndex());
// Offer owner's account is no longer pristine.
usAccountTouched.insert (uOfferOwnerID);
}
else if (saSubTakerGot)
{
m_journal.debug <<
"takeOffers: Offer partial claim.";
// TODO check the synthetic case here (to ensure there
// is no corruption)
if (!saOfferPays.isPositive () || !saOfferGets.isPositive ())
{
m_journal.warning <<
"takeOffers: ILLEGAL OFFER RESULT.";
bUnfunded = true;
terResult = bOpenLedger
? telFAILED_PROCESSING
: tecFAILED_PROCESSING;
}
}
else
{
m_journal.debug <<
"takeOffers: considering offer : " <<
sleOffer->getJson (0);
// Taker got nothing, probably due to rounding. Consider
// taker unfunded.
m_journal.debug << "takeOffers: No claim.";
uint160 const& uOfferOwnerID = sleOffer->getFieldAccount160 (sfAccount);
STAmount saOfferPays = sleOffer->getFieldAmount (sfTakerGets);
STAmount saOfferGets = sleOffer->getFieldAmount (sfTakerPays);
bUnfunded = true;
terResult = tesSUCCESS; // Done.
}
STAmount saOfferFunds; // Funds of offer owner to payout.
assert (uTakerGetsAccountID == saSubTakerGot.getIssuer ());
assert (uTakerPaysAccountID == saSubTakerPaid.getIssuer ());
bool const bValid = isValidOffer (
sleOffer,
uOfferOwnerID,
saOfferPays,
saOfferGets,
uTakerAccountID,
usOfferUnfundedBecame,
usAccountTouched,
saOfferFunds);
if (!bUnfunded)
{
// Distribute funds. The sends charge appropriate fees
// which are implied by offer.
if (bValid)
// TODO Adjust for synthetic transfers - pay into A->XRP
// and pay out of XRP->B
// Offer owner pays taker.
terResult = lesActive.accountSend (
uOfferOwnerID, uTakerAccountID, saSubTakerGot);
if (tesSUCCESS == terResult)
{
STAmount saSubTakerPaid;
STAmount saSubTakerGot;
STAmount saTakerIssuerFee;
STAmount saOfferIssuerFee;
STAmount saOfferRate = STAmount::setRate (uTipQuality);
// TODO: in the synthesized case, pay from B to the original taker
// Taker -> A -> XRP -> B -> ... -> Taker
if (m_journal.trace)
{
m_journal.trace <<
"takeOffers: applyOffer: saTakerPays: " <<
saTakerPays.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saTakerPaid: " <<
saTakerPaid.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saTakerFunds: " <<
saTakerFunds.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saOfferFunds: " <<
saOfferFunds.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saOfferPays: " <<
saOfferPays.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saOfferGets: " <<
saOfferGets.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saOfferRate: " <<
saOfferRate.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saSubTakerPays: " <<
saSubTakerPays.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saSubTakerGets: " <<
saSubTakerGets.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saTakerPays: " <<
saTakerPays.getFullText ();
m_journal.trace <<
"takeOffers: applyOffer: saTakerGets: " <<
saTakerGets.getFullText ();
}
bool const bOfferDelete = STAmount::applyOffer (
bSell,
lesActive.rippleTransferRate (
uTakerAccountID,
uOfferOwnerID,
uTakerPaysAccountID),
lesActive.rippleTransferRate (
uOfferOwnerID,
uTakerAccountID,
uTakerGetsAccountID),
saOfferRate,
saOfferFunds,
saTakerFunds,
saOfferPays,
saOfferGets,
saSubTakerPays,
saSubTakerGets,
saSubTakerPaid,
saSubTakerGot,
saTakerIssuerFee,
saOfferIssuerFee);
m_journal.debug <<
"takeOffers: applyOffer: saSubTakerPaid: " <<
saSubTakerPaid.getFullText ();
m_journal.debug <<
"takeOffers: applyOffer: saSubTakerGot: " <<
saSubTakerGot.getFullText ();
// Adjust offer
// TODO We need to consider the combined (synthesized)
// order book.
// Offer owner will pay less. Subtract what taker just got.
sleOffer->setFieldAmount (sfTakerGets, saOfferPays -= saSubTakerGot);
// Offer owner will get less. Subtract what owner just paid.
sleOffer->setFieldAmount (sfTakerPays, saOfferGets -= saSubTakerPaid);
mEngine->entryModify (sleOffer);
if (bOfferDelete)
{
// TODO need to handle the synthetic case here
// Offer now fully claimed or now unfunded.
m_journal.debug <<
"takeOffers: Offer claimed: Delete.";
// Delete unfunded offer on success.
usOfferUnfundedBecame.insert (sleOffer->getIndex());
// Offer owner's account is no longer pristine.
usAccountTouched.insert (uOfferOwnerID);
}
else if (saSubTakerGot)
{
m_journal.debug <<
"takeOffers: Offer partial claim.";
// TODO check the synthetic case here (to ensure there
// is no corruption)
if (!saOfferPays.isPositive () || !saOfferGets.isPositive ())
{
m_journal.warning <<
"takeOffers: ILLEGAL OFFER RESULT.";
bUnfunded = true;
terResult = bOpenLedger
? telFAILED_PROCESSING
: tecFAILED_PROCESSING;
}
}
else
{
// Taker got nothing, probably due to rounding. Consider
// taker unfunded.
m_journal.debug << "takeOffers: No claim.";
bUnfunded = true;
terResult = tesSUCCESS; // Done.
}
assert (uTakerGetsAccountID == saSubTakerGot.getIssuer ());
assert (uTakerPaysAccountID == saSubTakerPaid.getIssuer ());
if (!bUnfunded)
{
// Distribute funds. The sends charge appropriate fees
// which are implied by offer.
// TODO Adjust for synthetic transfers - pay into A->XRP
// and pay out of XRP->B
// Offer owner pays taker.
terResult = lesActive.accountSend (
uOfferOwnerID, uTakerAccountID, saSubTakerGot);
if (tesSUCCESS == terResult)
{
// TODO: in the synthesized case, pay from B to the original taker
// Taker -> A -> XRP -> B -> ... -> Taker
// Taker pays offer owner.
terResult = lesActive.accountSend (
uTakerAccountID, uOfferOwnerID, saSubTakerPaid);
}
if (bSell)
{
// Sell semantics:
// Reduce amount considered received to original
// offer's rate. Not by the crossing rate, which is
// higher.
STAmount saEffectiveGot = STAmount::divide(
saSubTakerPaid, saTakerRate, saTakerGets);
saSubTakerGot = std::min(saEffectiveGot, saSubTakerGot);
}
else
{
// Buy semantics: Reduce amount considered paid by
// taker's rate. Not by actual cost which is lower.
// That is, take less as to just satify our buy
// requirement.
// Taker could pay.
STAmount saTakerCould = saTakerPays - saTakerPaid;
if (saTakerFunds < saTakerCould)
saTakerCould = saTakerFunds;
STAmount saTakerUsed = STAmount::multiply (
saSubTakerGot, saTakerRate, saTakerPays);
if (m_journal.debug)
{
m_journal.debug <<
"takeOffers: applyOffer: saTakerCould: " <<
saTakerCould.getFullText ();
m_journal.debug <<
"takeOffers: applyOffer: saSubTakerGot: " <<
saSubTakerGot.getFullText ();
m_journal.debug <<
"takeOffers: applyOffer: saTakerRate: " <<
saTakerRate.getFullText ();
m_journal.debug <<
"takeOffers: applyOffer: saTakerUsed: " <<
saTakerUsed.getFullText ();
}
saSubTakerPaid = std::min (saTakerCould, saTakerUsed);
}
saTakerPaid += saSubTakerPaid;
saTakerGot += saSubTakerGot;
if (tesSUCCESS == terResult)
terResult = temUNCERTAIN;
}
// Taker pays offer owner.
terResult = lesActive.accountSend (
uTakerAccountID, uOfferOwnerID, saSubTakerPaid);
}
if (bSell)
{
// Sell semantics:
// Reduce amount considered received to original
// offer's rate. Not by the crossing rate, which is
// higher.
STAmount saEffectiveGot = STAmount::divide(
saSubTakerPaid, saTakerRate, saTakerGets);
saSubTakerGot = std::min(saEffectiveGot, saSubTakerGot);
}
else
{
// Buy semantics: Reduce amount considered paid by
// taker's rate. Not by actual cost which is lower.
// That is, take less as to just satify our buy
// requirement.
// Taker could pay.
STAmount saTakerCould = saTakerPays - saTakerPaid;
if (saTakerFunds < saTakerCould)
saTakerCould = saTakerFunds;
STAmount saTakerUsed = STAmount::multiply (
saSubTakerGot, saTakerRate, saTakerPays);
if (m_journal.debug)
{
m_journal.debug <<
"takeOffers: applyOffer: saTakerCould: " <<
saTakerCould.getFullText ();
m_journal.debug <<
"takeOffers: applyOffer: saSubTakerGot: " <<
saSubTakerGot.getFullText ();
m_journal.debug <<
"takeOffers: applyOffer: saTakerRate: " <<
saTakerRate.getFullText ();
m_journal.debug <<
"takeOffers: applyOffer: saTakerUsed: " <<
saTakerUsed.getFullText ();
}
saSubTakerPaid = std::min (saTakerCould, saTakerUsed);
}
saTakerPaid += saSubTakerPaid;
saTakerGot += saSubTakerGot;
if (tesSUCCESS == terResult)
terResult = temUNCERTAIN;
}
}
}
@@ -925,8 +1181,8 @@ TER OfferCreateTransactor::doApply ()
}
// Go through the list of offers not found and remove them from the
// order book. This may be a fix to remove corrupted entries - check
// with David.
// order book.
// DAVIDSCHWARTZ - is this a fix to remove corrupted entries?
for (auto const& indexes : usMissingOffers)
{
SLE::pointer sleDirectory (

View File

@@ -78,6 +78,26 @@ private:
std::unordered_set<uint160, beast::hardened_hash<uint160>>& usAccountTouched,
STAmount& saOfferFunds);
bool applyOffer (
const bool bSell,
const std::uint32_t uTakerPaysRate, const std::uint32_t uOfferPaysRate,
const STAmount& saOfferRate,
const STAmount& saOfferFunds, const STAmount& saTakerFunds,
const STAmount& saOfferPays, const STAmount& saOfferGets,
const STAmount& saTakerPays, const STAmount& saTakerGets,
STAmount& saTakerPaid, STAmount& saTakerGot,
STAmount& saTakerIssuerFee, STAmount& saOfferIssuerFee);
bool canCross (
STAmount const& saTakerFunds,
STAmount const& saSubTakerPays,
STAmount const& saSubTakerGets,
std::uint64_t uTipQuality,
std::uint64_t uTakeQuality,
bool isPassive,
bool& isUnfunded,
TER& terResult) const;
TER takeOffers (
bool const bOpenLedger,
bool const bPassive,

View File

@@ -1052,157 +1052,6 @@ STAmount STAmount::setRate (std::uint64_t rate)
return STAmount (CURRENCY_ONE, ACCOUNT_ONE, mantissa, exponent);
}
// Existing offer is on the books.
// Price is offer owner's, which might be better for taker.
// Taker pays what they can.
// Taker gets all taker can pay for with saTakerFunds/uTakerPaysRate, limited by saOfferPays and saOfferFunds/uOfferPaysRate.
// If taker is an offer, taker is spending at same or better rate than they wanted.
// Taker should consider themselves as wanting to buy X amount.
// Taker is willing to pay at most the rate of Y/X each.
// Buy semantics:
// - After having some part of their offer fulfilled at a better rate their offer should be reduced accordingly.
//
// There are no quality costs for offer vs offer taking.
//
// --> bSell: True for sell semantics.
// --> uTakerPaysRate: >= QUALITY_ONE | TransferRate for third party IOUs paid by taker.
// --> uOfferPaysRate: >= QUALITY_ONE | TransferRate for third party IOUs paid by offer owner.
// --> saOfferRate: Original saOfferGets/saOfferPays, when offer was made.
// --> saOfferFunds: Limit for saOfferPays : How much can pay including fees.
// --> saTakerFunds: Limit for saOfferGets : How much can pay including fees.
// --> saOfferPays: Request : this should be reduced as the offer is fullfilled.
// --> saOfferGets: Request : this should be reduced as the offer is fullfilled.
// --> saTakerPays: Limit for taker to pay.
// --> saTakerGets: Limit for taker to get.
// <-- saTakerPaid: Actual
// <-- saTakerGot: Actual
// <-- saTakerIssuerFee: Actual
// <-- saOfferIssuerFee: Actual
bool STAmount::applyOffer (
const bool bSell,
const std::uint32_t uTakerPaysRate, const std::uint32_t uOfferPaysRate,
const STAmount& saOfferRate,
const STAmount& saOfferFunds, const STAmount& saTakerFunds,
const STAmount& saOfferPays, const STAmount& saOfferGets,
const STAmount& saTakerPays, const STAmount& saTakerGets,
STAmount& saTakerPaid, STAmount& saTakerGot,
STAmount& saTakerIssuerFee, STAmount& saOfferIssuerFee)
{
saOfferGets.throwComparable (saTakerFunds);
assert (saOfferFunds.isPositive () && saTakerFunds.isPositive ()); // Both must have funds.
assert (saOfferGets.isPositive () && saOfferPays.isPositive ()); // Must not be a null offer.
// Available = limited by funds.
// Limit offerer funds available, by transfer fees.
STAmount saOfferFundsAvailable = QUALITY_ONE == uOfferPaysRate
? saOfferFunds // As is.
: STAmount::divide (saOfferFunds, STAmount (CURRENCY_ONE, ACCOUNT_ONE, uOfferPaysRate, -9)); // Reduce by offer fees.
WriteLog (lsINFO, STAmount) << "applyOffer: uOfferPaysRate=" << uOfferPaysRate;
WriteLog (lsINFO, STAmount) << "applyOffer: saOfferFundsAvailable=" << saOfferFundsAvailable.getFullText ();
// Limit taker funds available, by transfer fees.
STAmount saTakerFundsAvailable = QUALITY_ONE == uTakerPaysRate
? saTakerFunds // As is.
: STAmount::divide (saTakerFunds, STAmount (CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9)); // Reduce by taker fees.
WriteLog (lsINFO, STAmount) << "applyOffer: TAKER_FEES=" << STAmount (CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9).getFullText ();
WriteLog (lsINFO, STAmount) << "applyOffer: uTakerPaysRate=" << uTakerPaysRate;
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerFundsAvailable=" << saTakerFundsAvailable.getFullText ();
STAmount saOfferPaysAvailable; // Amount offer can pay out, limited by offer and offerer funds.
STAmount saOfferGetsAvailable; // Amount offer would get, limited by offer funds.
if (saOfferFundsAvailable >= saOfferPays)
{
// Offer was fully funded, avoid math shenanigans.
saOfferPaysAvailable = saOfferPays;
saOfferGetsAvailable = saOfferGets;
}
else
{
// Offer has limited funding, limit offer gets and pays by funds available.
saOfferPaysAvailable = saOfferFundsAvailable;
saOfferGetsAvailable = std::min (saOfferGets, mulRound (saOfferPaysAvailable, saOfferRate, saOfferGets, true));
}
WriteLog (lsINFO, STAmount) << "applyOffer: saOfferPaysAvailable=" << saOfferPaysAvailable.getFullText ();
WriteLog (lsINFO, STAmount) << "applyOffer: saOfferGetsAvailable=" << saOfferGetsAvailable.getFullText ();
STAmount saTakerPaysAvailable = std::min (saTakerPays, saTakerFundsAvailable);
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerPaysAvailable=" << saTakerPaysAvailable.getFullText ();
// Limited = limited by other sides raw numbers.
// Taker can't pay more to offer than offer can get.
STAmount saTakerPaysLimited = std::min (saTakerPaysAvailable, saOfferGetsAvailable);
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerPaysLimited=" << saTakerPaysLimited.getFullText ();
// Align saTakerGetsLimited with saTakerPaysLimited.
STAmount saTakerGetsLimited = saTakerPaysLimited >= saOfferGetsAvailable // Cannot actually be greater
? saOfferPaysAvailable // Potentially take entire offer. Avoid math shenanigans.
: std::min (saOfferPaysAvailable, divRound (saTakerPaysLimited, saOfferRate, saTakerGets, true)); // Take a portion of offer.
WriteLog (lsINFO, STAmount) << "applyOffer: saOfferRate=" << saOfferRate.getFullText ();
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerGetsLimited=" << saTakerGetsLimited.getFullText ();
// Got & Paid = Calculated by price and transfered without fees.
// Compute from got as when !bSell, we want got to be exact to finish off offer if possible.
saTakerGot = bSell
? saTakerGetsLimited // Get all available that are paid for.
: std::min (saTakerGets, saTakerGetsLimited); // Limit by wanted.
saTakerPaid = saTakerGot >= saTakerGetsLimited
? saTakerPaysLimited
: std::min (saTakerPaysLimited, mulRound (saTakerGot, saOfferRate, saTakerFunds, true));
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerGot=" << saTakerGot.getFullText ();
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerPaid=" << saTakerPaid.getFullText ();
if (uTakerPaysRate == QUALITY_ONE)
{
saTakerIssuerFee = STAmount (saTakerPaid.getCurrency (), saTakerPaid.getIssuer ());
}
else
{
// Compute fees in a rounding safe way.
STAmount saTransferRate = STAmount (CURRENCY_ONE, ACCOUNT_ONE, uTakerPaysRate, -9);
WriteLog (lsINFO, STAmount) << "applyOffer: saTransferRate=" << saTransferRate.getFullText ();
// TakerCost includes transfer fees.
STAmount saTakerCost = STAmount::mulRound (saTakerPaid, saTransferRate, true);
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerCost=" << saTakerCost.getFullText ();
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerFunds=" << saTakerFunds.getFullText ();
saTakerIssuerFee = saTakerCost > saTakerFunds
? saTakerFunds - saTakerPaid // Not enough funds to cover fee, stiff issuer the rounding error.
: saTakerCost - saTakerPaid;
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerIssuerFee=" << saTakerIssuerFee.getFullText ();
assert (!saTakerIssuerFee.isNegative ());
}
if (uOfferPaysRate == QUALITY_ONE)
{
saOfferIssuerFee = STAmount (saTakerGot.getCurrency (), saTakerGot.getIssuer ());
}
else
{
// Compute fees in a rounding safe way.
STAmount saOfferCost = STAmount::mulRound (saTakerGot, STAmount (CURRENCY_ONE, ACCOUNT_ONE, uOfferPaysRate, -9), true);
saOfferIssuerFee = saOfferCost > saOfferFunds
? saOfferFunds - saTakerGot // Not enough funds to cover fee, stiff issuer the rounding error.
: saOfferCost - saTakerGot;
}
WriteLog (lsINFO, STAmount) << "applyOffer: saTakerGot=" << saTakerGot.getFullText ();
return saTakerGot >= saOfferPaysAvailable; // True, if consumed offer.
}
STAmount STAmount::getPay (const STAmount& offerOut, const STAmount& offerIn, const STAmount& needed)
{
// Someone wants to get (needed) out of the offer, how much should they pay in?

View File

@@ -763,18 +763,6 @@ public:
static std::uint64_t getRate (const STAmount& offerOut, const STAmount& offerIn);
static STAmount setRate (std::uint64_t rate);
// Someone is offering X for Y, I try to pay Z, how much do I get?
// And what's left of the offer? And how much do I actually pay?
static bool applyOffer (
const bool bSell,
const std::uint32_t uTakerPaysRate, const std::uint32_t uOfferPaysRate,
const STAmount& saOfferRate,
const STAmount& saOfferFunds, const STAmount& saTakerFunds,
const STAmount& saOfferPays, const STAmount& saOfferGets,
const STAmount& saTakerPays, const STAmount& saTakerGets,
STAmount& saTakerPaid, STAmount& saTakerGot,
STAmount& saTakerIssuerFee, STAmount& saOfferIssuerFee);
// Someone is offering X for Y, I need Z, how much do I pay
static STAmount getPay (const STAmount& offerOut, const STAmount& offerIn, const STAmount& needed);