// Take as much as possible. Adjusts account balances. Charges fees on top to taker. // --> uBookBase: The order book to take against. // --> saTakerPays: What the taker offers (w/ issuer) // --> saTakerGets: What the taker wanted (w/ issuer) // <-- saTakerPaid: What taker could have paid including saved not including fees. To reduce an offer. // <-- saTakerGot: What taker got not including fees. To reduce an offer. // <-- terResult: tesSUCCESS, terNO_ACCOUNT, telFAILED_PROCESSING, or tecFAILED_PROCESSING // <-- bUnfunded: if tesSUCCESS, consider offer unfunded after taking. TER OfferCreateTransactor::takeOffers ( const bool bOpenLedger, const bool bPassive, const bool bSell, uint256 const& uBookBase, const uint160& uTakerAccountID, SLE::ref sleTakerAccount, const STAmount& saTakerPays, const STAmount& saTakerGets, STAmount& saTakerPaid, STAmount& saTakerGot, bool& bUnfunded) { // The book has the most elements. Take the perspective of the book. // Book is ordered for taker: taker pays / taker gets (smaller is better) // The order is for the other books currencys for get and pays are opposites. // We want the same ratio for the respective currencies. // So we swap paid and gets for determing take quality. assert (saTakerPays && saTakerGets); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: bSell: " << bSell << ": against book: " << uBookBase.ToString (); LedgerEntrySet& lesActive = mEngine->getNodes (); uint256 uTipIndex = uBookBase; const uint256 uBookEnd = Ledger::getQualityNext (uBookBase); const uint64 uTakeQuality = STAmount::getRate (saTakerGets, saTakerPays); STAmount saTakerRate = STAmount::setRate (uTakeQuality); const uint160 uTakerPaysAccountID = saTakerPays.getIssuer (); const uint160 uTakerGetsAccountID = saTakerGets.getIssuer (); TER terResult = temUNCERTAIN; boost::unordered_set<uint256> usOfferUnfundedBecame; // Offers that became unfunded. boost::unordered_set<uint160> usAccountTouched; // Accounts touched. saTakerPaid = STAmount (saTakerPays.getCurrency (), saTakerPays.getIssuer ()); saTakerGot = STAmount (saTakerGets.getCurrency (), saTakerGets.getIssuer ()); bUnfunded = false; while (temUNCERTAIN == terResult) { SLE::pointer sleOfferDir; uint64 uTipQuality = 0; STAmount saTakerFunds = lesActive.accountFunds (uTakerAccountID, saTakerPays); STAmount saSubTakerPays = saTakerPays - saTakerPaid; // How much more to spend. STAmount saSubTakerGets = saTakerGets - saTakerGot; // How much more is wanted. // Figure out next offer to take, if needed. if (saTakerFunds.isPositive () // Taker has funds available. && saSubTakerPays.isPositive () && saSubTakerGets.isPositive ()) { sleOfferDir = mEngine->entryCache (ltDIR_NODE, lesActive.getNextLedgerIndex (uTipIndex, uBookEnd)); if (sleOfferDir) { uTipIndex = sleOfferDir->getIndex (); uTipQuality = Ledger::getQuality (uTipIndex); WriteLog (lsDEBUG, OfferCreateTransactor) << boost::str (boost::format ("takeOffers: possible counter offer found: uTipQuality=%d uTipIndex=%s") % uTipQuality % uTipIndex.ToString ()); } else { WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: counter offer book is empty: " << uTipIndex.ToString () << " ... " << uBookEnd.ToString (); } } if (!saTakerFunds.isPositive ()) // Taker has no funds. { // Done. Ran out of funds on previous round. As fees aren't calculated directly in this routine, funds are checked here. WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: done: taker unfunded."; bUnfunded = true; // Don't create an order. terResult = tesSUCCESS; } else if (!sleOfferDir // No offer directory to take. || uTakeQuality < uTipQuality // No offers of sufficient quality available. || (bPassive && uTakeQuality == uTipQuality)) { // Done. STAmount saTipRate = sleOfferDir ? STAmount::setRate (uTipQuality) : saTakerRate; WriteLog (lsDEBUG, OfferCreateTransactor) << boost::str (boost::format ("takeOffers: done: dir=%d uTakeQuality=%d %c uTipQuality=%d saTakerRate=%s %c saTipRate=%s bPassive=%d") % !!sleOfferDir % uTakeQuality % (uTakeQuality == uTipQuality ? '=' : uTakeQuality < uTipQuality ? '<' : '>') % uTipQuality % saTakerRate % (saTakerRate == saTipRate ? '=' : saTakerRate < saTipRate ? '<' : '>') % saTipRate % bPassive); terResult = tesSUCCESS; } else { // Have an offer directory to consider. WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: considering dir: " << sleOfferDir->getJson (0); SLE::pointer sleBookNode; unsigned int uBookEntry; uint256 uOfferIndex; lesActive.dirFirst (uTipIndex, sleBookNode, uBookEntry, uOfferIndex); SLE::pointer sleOffer = mEngine->entryCache (ltOFFER, uOfferIndex); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: considering offer : " << sleOffer->getJson (0); const uint160 uOfferOwnerID = sleOffer->getFieldAccount160 (sfAccount); STAmount saOfferPays = sleOffer->getFieldAmount (sfTakerGets); STAmount saOfferGets = sleOffer->getFieldAmount (sfTakerPays); STAmount saOfferFunds; // Funds of offer owner to payout. bool bValid; bValid = bValidOffer ( sleOfferDir, uOfferIndex, uOfferOwnerID, saOfferPays, saOfferGets, uTakerAccountID, usOfferUnfundedFound, usOfferUnfundedBecame, usAccountTouched, saOfferFunds); if (bValid) { STAmount saSubTakerPaid; STAmount saSubTakerGot; STAmount saTakerIssuerFee; STAmount saOfferIssuerFee; STAmount saOfferRate = STAmount::setRate (uTipQuality); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerPaid: " << saTakerPaid.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerFunds: " << saTakerFunds.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saOfferFunds: " << saOfferFunds.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saOfferPays: " << saOfferPays.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saOfferGets: " << saOfferGets.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saOfferRate: " << saOfferRate.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerPays: " << saSubTakerPays.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerGets: " << saSubTakerGets.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerGets: " << saTakerGets.getFullText (); bool 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); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerPaid: " << saSubTakerPaid.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerGot: " << saSubTakerGot.getFullText (); // Adjust offer // 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) { // Offer now fully claimed or now unfunded. WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: Offer claimed: Delete."; usOfferUnfundedBecame.insert (uOfferIndex); // Delete unfunded offer on success. // Offer owner's account is no longer pristine. usAccountTouched.insert (uOfferOwnerID); } else if (saSubTakerGot) { WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: Offer partial claim."; if (!saOfferPays.isPositive () || !saOfferGets.isPositive ()) { WriteLog (lsWARNING, OfferCreateTransactor) << "takeOffers: ILLEGAL OFFER RESULT."; bUnfunded = true; terResult = bOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING; } } else { // Taker got nothing, probably due to rounding. Consider taker unfunded. WriteLog (lsDEBUG, OfferCreateTransactor) << "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. terResult = lesActive.accountSend (uOfferOwnerID, uTakerAccountID, saSubTakerGot); // Offer owner pays taker. if (tesSUCCESS == terResult) terResult = lesActive.accountSend (uTakerAccountID, uOfferOwnerID, saSubTakerPaid); // Taker pays offer owner. if (!bSell) { // 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. STAmount saTakerCould = saTakerPays - saTakerPaid; // Taker could pay. if (saTakerFunds < saTakerCould) saTakerCould = saTakerFunds; STAmount saTakerUsed = STAmount::multiply (saSubTakerGot, saTakerRate, saTakerPays); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerCould: " << saTakerCould.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerGot: " << saSubTakerGot.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerRate: " << saTakerRate.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerUsed: " << saTakerUsed.getFullText (); saSubTakerPaid = std::min (saTakerCould, saTakerUsed); } saTakerPaid += saSubTakerPaid; saTakerGot += saSubTakerGot; if (tesSUCCESS == terResult) terResult = temUNCERTAIN; } } } } WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: " << transToken (terResult); if (tesSUCCESS == terResult) { // On success, delete offers that became unfunded. BOOST_FOREACH (uint256 const & uOfferIndex, usOfferUnfundedBecame) { WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: became unfunded: " << uOfferIndex.ToString (); terResult = lesActive.offerDelete (uOfferIndex); if (tesSUCCESS != terResult) break; }
// Take as much as possible. Adjusts account balances. Charges fees on top to taker. // --> uBookBase: The order book to take against. // --> saTakerPays: What the taker offers (w/ issuer) // --> saTakerGets: What the taker wanted (w/ issuer) // <-- saTakerPaid: What taker could have paid including saved not including fees. To reduce an offer. // <-- saTakerGot: What taker got not including fees. To reduce an offer. // <-- terResult: tesSUCCESS, terNO_ACCOUNT, telFAILED_PROCESSING, or tecFAILED_PROCESSING // <-- bUnfunded: if tesSUCCESS, consider offer unfunded after taking. TER OfferCreateTransactor::takeOffers ( const bool bOpenLedger, const bool bPassive, const bool bSell, uint256 const& uBookBase, const uint160& uTakerAccountID, SLE::ref sleTakerAccount, const STAmount& saTakerPays, const STAmount& saTakerGets, STAmount& saTakerPaid, STAmount& saTakerGot, bool& bUnfunded) { // The book has the most elements. Take the perspective of the book. // Book is ordered for taker: taker pays / taker gets (smaller is better) // The order is for the other books currencys for get and pays are opposites. // We want the same ratio for the respective currencies. // So we swap paid and gets for determing take quality. assert (saTakerPays && saTakerGets); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: bSell: " << bSell << ": against book: " << uBookBase.ToString (); LedgerEntrySet& lesActive = mEngine->getNodes (); const uint64 uTakeQuality = STAmount::getRate (saTakerGets, saTakerPays); STAmount saTakerRate = STAmount::setRate (uTakeQuality); const uint160 uTakerPaysAccountID = saTakerPays.getIssuer (); const uint160 uTakerGetsAccountID = saTakerGets.getIssuer (); TER terResult = temUNCERTAIN; boost::unordered_set<uint256> usOfferUnfundedBecame; // Offers that became unfunded. boost::unordered_set<uint160> usAccountTouched; // Accounts touched. saTakerPaid = STAmount (saTakerPays.getCurrency (), saTakerPays.getIssuer ()); saTakerGot = STAmount (saTakerGets.getCurrency (), saTakerGets.getIssuer ()); bUnfunded = false; OrderBookIterator bookIterator (lesActive, saTakerPays.getCurrency(), saTakerPays.getIssuer(), saTakerGets.getCurrency(), saTakerGets.getIssuer()); while ((temUNCERTAIN == terResult) && bookIterator.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. uint64 uTipQuality = bookIterator.getCurrentQuality(); if (!saTakerFunds.isPositive ()) { // Taker is out of funds. Don't create the offer. bUnfunded = true; terResult = tesSUCCESS; } else if (!saSubTakerPays.isPositive() || !saSubTakerGets.isPositive()) { // Offer is completely consumed terResult = tesSUCCESS; } else if ((uTakeQuality < uTipQuality) || (bPassive && uTakeQuality == uTipQuality)) { // Offer does not cross this offer STAmount saTipRate = STAmount::setRate (uTipQuality); WriteLog (lsDEBUG, OfferCreateTransactor) << boost::str (boost::format ("takeOffers: done: uTakeQuality=%d %c uTipQuality=%d saTakerRate=%s %c saTipRate=%s bPassive=%d") % uTakeQuality % (uTakeQuality == uTipQuality ? '=' : uTakeQuality < uTipQuality ? '<' : '>') % uTipQuality % saTakerRate % (saTakerRate == saTipRate ? '=' : saTakerRate < saTipRate ? '<' : '>') % saTipRate % bPassive); terResult = tesSUCCESS; } else { // We have a crossing offer to consider. SLE::pointer sleOffer = bookIterator.getCurrentOffer (); if (!sleOffer) { // offer is in directory but not in ledger uint256 offerIndex = bookIterator.getCurrentIndex (); WriteLog (lsWARNING, OfferCreateTransactor) << "takeOffers: offer not found : " << offerIndex; usMissingOffers.insert (missingOffer_t ( bookIterator.getCurrentIndex (), bookIterator.getCurrentDirectory ())); } else { WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: considering offer : " << sleOffer->getJson (0); const uint160& uOfferOwnerID = sleOffer->getFieldAccount160 (sfAccount); STAmount saOfferPays = sleOffer->getFieldAmount (sfTakerGets); STAmount saOfferGets = sleOffer->getFieldAmount (sfTakerPays); STAmount saOfferFunds; // Funds of offer owner to payout. bool bValid; bValid = bValidOffer ( sleOffer, uOfferOwnerID, saOfferPays, saOfferGets, uTakerAccountID, usOfferUnfundedFound, usOfferUnfundedBecame, usAccountTouched, saOfferFunds); if (bValid) { STAmount saSubTakerPaid; STAmount saSubTakerGot; STAmount saTakerIssuerFee; STAmount saOfferIssuerFee; STAmount saOfferRate = STAmount::setRate (uTipQuality); WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText (); WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerPaid: " << saTakerPaid.getFullText (); WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerFunds: " << saTakerFunds.getFullText (); WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer: saOfferFunds: " << saOfferFunds.getFullText (); WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer: saOfferPays: " << saOfferPays.getFullText (); WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer: saOfferGets: " << saOfferGets.getFullText (); WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer: saOfferRate: " << saOfferRate.getFullText (); WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerPays: " << saSubTakerPays.getFullText (); WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerGets: " << saSubTakerGets.getFullText (); WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerPays: " << saTakerPays.getFullText (); WriteLog (lsTRACE, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerGets: " << saTakerGets.getFullText (); bool 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); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerPaid: " << saSubTakerPaid.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerGot: " << saSubTakerGot.getFullText (); // Adjust offer // 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) { // Offer now fully claimed or now unfunded. WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: Offer claimed: Delete."; usOfferUnfundedBecame.insert (sleOffer->getIndex()); // Delete unfunded offer on success. // Offer owner's account is no longer pristine. usAccountTouched.insert (uOfferOwnerID); } else if (saSubTakerGot) { WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: Offer partial claim."; if (!saOfferPays.isPositive () || !saOfferGets.isPositive ()) { WriteLog (lsWARNING, OfferCreateTransactor) << "takeOffers: ILLEGAL OFFER RESULT."; bUnfunded = true; terResult = bOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING; } } else { // Taker got nothing, probably due to rounding. Consider taker unfunded. WriteLog (lsDEBUG, OfferCreateTransactor) << "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. terResult = lesActive.accountSend (uOfferOwnerID, uTakerAccountID, saSubTakerGot); // Offer owner pays taker. if (tesSUCCESS == terResult) terResult = lesActive.accountSend (uTakerAccountID, uOfferOwnerID, saSubTakerPaid); // Taker pays offer owner. if (bSell) { // Sell semantics: // Reduce amount considered received to original offer's rate. // Not by 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. STAmount saTakerCould = saTakerPays - saTakerPaid; // Taker could pay. if (saTakerFunds < saTakerCould) saTakerCould = saTakerFunds; STAmount saTakerUsed = STAmount::multiply (saSubTakerGot, saTakerRate, saTakerPays); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerCould: " << saTakerCould.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saSubTakerGot: " << saSubTakerGot.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerRate: " << saTakerRate.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: applyOffer: saTakerUsed: " << saTakerUsed.getFullText (); saSubTakerPaid = std::min (saTakerCould, saTakerUsed); } saTakerPaid += saSubTakerPaid; saTakerGot += saSubTakerGot; if (tesSUCCESS == terResult) terResult = temUNCERTAIN; } } } } } if (temUNCERTAIN == terResult) terResult = tesSUCCESS; WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: " << transToken (terResult); if (tesSUCCESS == terResult) { // On success, delete offers that became unfunded. BOOST_FOREACH (uint256 const & uOfferIndex, usOfferUnfundedBecame) { WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers: became unfunded: " << uOfferIndex.ToString (); terResult = lesActive.offerDelete (uOfferIndex); if (tesSUCCESS != terResult) break; }