//------------------------------------------------------------------------------ /* Copyright (c) 2011-2013, OpenCoin, Inc. */ //============================================================================== SETUP_LOG (OfferCreateTransactor) // Make sure an offer is still valid. If not, mark it unfunded. bool OfferCreateTransactor::bValidOffer ( SLE::ref sleOfferDir, uint256 const& uOfferIndex, const uint160& uOfferOwnerID, const STAmount& saOfferPays, const STAmount& saOfferGets, const uint160& uTakerAccountID, boost::unordered_set& usOfferUnfundedFound, boost::unordered_set& usOfferUnfundedBecame, boost::unordered_set& usAccountTouched, STAmount& saOfferFunds) // <-- { bool bValid; if (sleOfferDir->isFieldPresent (sfExpiration) && sleOfferDir->getFieldU32 (sfExpiration) <= mEngine->getLedger ()->getParentCloseTimeNC ()) { // Offer is expired. Expired offers are considered unfunded. Delete it. WriteLog (lsINFO, OfferCreateTransactor) << "bValidOffer: encountered expired offer"; usOfferUnfundedFound.insert (uOfferIndex); bValid = false; } else if (uOfferOwnerID == uTakerAccountID) { // Would take own offer. Consider old offer expired. Delete it. WriteLog (lsINFO, OfferCreateTransactor) << "bValidOffer: encountered taker's own old offer"; usOfferUnfundedFound.insert (uOfferIndex); bValid = false; } else if (!saOfferGets.isPositive () || !saOfferPays.isPositive ()) { // Offer has bad amounts. Consider offer expired. Delete it. WriteLog (lsWARNING, OfferCreateTransactor) << boost::str (boost::format ("bValidOffer: BAD OFFER: saOfferPays=%s saOfferGets=%s") % saOfferPays % saOfferGets); usOfferUnfundedFound.insert (uOfferIndex); } else { WriteLog (lsTRACE, OfferCreateTransactor) << "bValidOffer: saOfferPays=" << saOfferPays.getFullText (); saOfferFunds = mEngine->getNodes ().accountFunds (uOfferOwnerID, saOfferPays); if (!saOfferFunds.isPositive ()) { // Offer is unfunded, possibly due to previous balance action. WriteLog (lsDEBUG, OfferCreateTransactor) << "bValidOffer: offer unfunded: delete"; boost::unordered_set::iterator account = usAccountTouched.find (uOfferOwnerID); if (account != usAccountTouched.end ()) { // Previously touched account. usOfferUnfundedBecame.insert (uOfferIndex); // Delete unfunded offer on success. } else { // Never touched source account. usOfferUnfundedFound.insert (uOfferIndex); // Delete found unfunded offer when possible. } bValid = false; } else { bValid = true; } } return bValid; } // 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 usOfferUnfundedBecame; // Offers that became unfunded. boost::unordered_set 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; } } WriteLog (lsDEBUG, OfferCreateTransactor) << "takeOffers< " << transToken (terResult); return terResult; } TER OfferCreateTransactor::doApply () { WriteLog (lsTRACE, OfferCreateTransactor) << "OfferCreate> " << mTxn.getJson (0); const uint32 uTxFlags = mTxn.getFlags (); const bool bPassive = isSetBit (uTxFlags, tfPassive); const bool bImmediateOrCancel = isSetBit (uTxFlags, tfImmediateOrCancel); const bool bFillOrKill = isSetBit (uTxFlags, tfFillOrKill); const bool bSell = isSetBit (uTxFlags, tfSell); STAmount saTakerPays = mTxn.getFieldAmount (sfTakerPays); STAmount saTakerGets = mTxn.getFieldAmount (sfTakerGets); if (!saTakerPays.isLegalNet () || !saTakerGets.isLegalNet ()) return temBAD_AMOUNT; WriteLog (lsTRACE, OfferCreateTransactor) << boost::str (boost::format ("OfferCreate: saTakerPays=%s saTakerGets=%s") % saTakerPays.getFullText () % saTakerGets.getFullText ()); const uint160 uPaysIssuerID = saTakerPays.getIssuer (); const uint160 uGetsIssuerID = saTakerGets.getIssuer (); const uint32 uExpiration = mTxn.getFieldU32 (sfExpiration); const bool bHaveExpiration = mTxn.isFieldPresent (sfExpiration); const bool bHaveCancel = mTxn.isFieldPresent (sfOfferSequence); const uint32 uCancelSequence = mTxn.getFieldU32 (sfOfferSequence); const uint32 uAccountSequenceNext = mTxnAccount->getFieldU32 (sfSequence); const uint32 uSequence = mTxn.getSequence (); const uint256 uLedgerIndex = Ledger::getOfferIndex (mTxnAccountID, uSequence); WriteLog (lsTRACE, OfferCreateTransactor) << "OfferCreate: Creating offer node: " << uLedgerIndex.ToString () << " uSequence=" << uSequence; const uint160 uPaysCurrency = saTakerPays.getCurrency (); const uint160 uGetsCurrency = saTakerGets.getCurrency (); const uint64 uRate = STAmount::getRate (saTakerGets, saTakerPays); TER terResult = tesSUCCESS; uint256 uDirectory; // Delete hints. uint64 uOwnerNode; uint64 uBookNode; LedgerEntrySet& lesActive = mEngine->getNodes (); LedgerEntrySet lesCheckpoint = lesActive; // Checkpoint with just fees paid. lesActive.bumpSeq (); // Begin ledger variance. SLE::pointer sleCreator = mEngine->entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (mTxnAccountID)); if (uTxFlags & tfOfferCreateMask) { WriteLog (lsINFO, OfferCreateTransactor) << "OfferCreate: Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } else if (bImmediateOrCancel && bFillOrKill) { WriteLog (lsINFO, OfferCreateTransactor) << "OfferCreate: Malformed transaction: both IoC and FoK set."; return temINVALID_FLAG; } else if (bHaveExpiration && !uExpiration) { WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: Malformed offer: bad expiration"; terResult = temBAD_EXPIRATION; } else if (bHaveExpiration && mEngine->getLedger ()->getParentCloseTimeNC () >= uExpiration) { WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: Expired transaction: offer expired"; terResult = tesSUCCESS; // Only charged fee. } else if (saTakerPays.isNative () && saTakerGets.isNative ()) { WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: Malformed offer: XRP for XRP"; terResult = temBAD_OFFER; } else if (!saTakerPays.isPositive () || !saTakerGets.isPositive ()) { WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: Malformed offer: bad amount"; terResult = temBAD_OFFER; } else if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID) { WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: Malformed offer: redundant offer"; terResult = temREDUNDANT; } else if (CURRENCY_BAD == uPaysCurrency || CURRENCY_BAD == uGetsCurrency) { WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: Malformed offer: Bad currency."; terResult = temBAD_CURRENCY; } else if (saTakerPays.isNative () != !uPaysIssuerID || saTakerGets.isNative () != !uGetsIssuerID) { WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: Malformed offer: bad issuer"; terResult = temBAD_ISSUER; } else if (!lesActive.accountFunds (mTxnAccountID, saTakerGets).isPositive ()) { WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: delay: Offers must be at least partially funded."; terResult = tecUNFUNDED_OFFER; } else if (bHaveCancel && (!uCancelSequence || uAccountSequenceNext - 1 <= uCancelSequence)) { WriteLog (lsINFO, OfferCreateTransactor) << "OfferCreate: uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uCancelSequence; terResult = temBAD_SEQUENCE; } // Cancel offer. if (tesSUCCESS == terResult && bHaveCancel) { const uint256 uCancelIndex = Ledger::getOfferIndex (mTxnAccountID, uCancelSequence); SLE::pointer sleCancel = mEngine->entryCache (ltOFFER, uCancelIndex); if (sleCancel) { WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: uCancelSequence=" << uCancelSequence; terResult = mEngine->getNodes ().offerDelete (sleCancel, uCancelIndex, mTxnAccountID); } else { WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: offer not found: " << RippleAddress::createHumanAccountID (mTxnAccountID) << " : " << uCancelSequence << " : " << uCancelIndex.ToString (); } } // Make sure authorized to hold what taker will pay. if (tesSUCCESS == terResult && !saTakerPays.isNative ()) { SLE::pointer sleTakerPays = mEngine->entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uPaysIssuerID)); if (!sleTakerPays) { WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::createHumanAccountID (uPaysIssuerID); terResult = terNO_ACCOUNT; } else if (isSetBit (sleTakerPays->getFieldU32 (sfFlags), lsfRequireAuth)) { SLE::pointer sleRippleState = mEngine->entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (mTxnAccountID, uPaysIssuerID, uPaysCurrency)); bool bHigh = mTxnAccountID > uPaysIssuerID; if (!sleRippleState || !isSetBit (sleRippleState->getFieldU32 (sfFlags), (bHigh ? lsfHighAuth : lsfLowAuth))) { WriteLog (lsWARNING, OfferCreateTransactor) << "OfferCreate: delay: can't receive IOUs from issuer without auth."; terResult = terNO_AUTH; } } } STAmount saPaid; STAmount saGot; bool bUnfunded = false; const bool bOpenLedger = isSetBit (mParams, tapOPEN_LEDGER); if (tesSUCCESS == terResult) { const uint256 uTakeBookBase = Ledger::getBookBase (uGetsCurrency, uGetsIssuerID, uPaysCurrency, uPaysIssuerID); WriteLog (lsINFO, OfferCreateTransactor) << boost::str (boost::format ("OfferCreate: take against book: %s for %s -> %s") % uTakeBookBase.ToString () % saTakerGets.getFullText () % saTakerPays.getFullText ()); // Take using the parameters of the offer. WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: BEFORE saTakerGets=" << saTakerGets.getFullText (); terResult = takeOffers ( bOpenLedger, bPassive, bSell, uTakeBookBase, mTxnAccountID, sleCreator, saTakerGets, // Reverse as we are the taker for taking. saTakerPays, saPaid, // Buy semantics: how much would have sold at full price. Sell semantics: how much was sold. saGot, // How much was got. bUnfunded); WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers=" << terResult; WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: saPaid=" << saPaid.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: saGot=" << saGot.getFullText (); if (tesSUCCESS == terResult && !bUnfunded) { saTakerPays -= saGot; // Reduce pay in from takers by what offer just got. saTakerGets -= saPaid; // Reduce pay out to takers by what srcAccount just paid. WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: AFTER saTakerPays=" << saTakerPays.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: AFTER saTakerGets=" << saTakerGets.getFullText (); } } WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: saTakerPays=" << saTakerPays.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: saTakerGets=" << saTakerGets.getFullText (); WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: mTxnAccountID=" << RippleAddress::createHumanAccountID (mTxnAccountID); WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: FUNDS=" << lesActive.accountFunds (mTxnAccountID, saTakerGets).getFullText (); // WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: uPaysIssuerID=" << RippleAddress::createHumanAccountID(uPaysIssuerID); // WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: takeOffers: uGetsIssuerID=" << RippleAddress::createHumanAccountID(uGetsIssuerID); if (tesSUCCESS != terResult) { // Fail as is. nothing (); } else if (saTakerPays.isNegative () || saTakerGets.isNegative ()) { // If ledger is not final, can vote no. terResult = bOpenLedger ? telFAILED_PROCESSING : tecFAILED_PROCESSING; } else if (bFillOrKill && (saTakerPays || saTakerGets)) { // Fill or kill and have leftovers. lesActive.swapWith (lesCheckpoint); // Restore with just fees paid. } else if ( !saTakerPays // Wants nothing more. || !saTakerGets // Offering nothing more. || bImmediateOrCancel // Do not persist. || !lesActive.accountFunds (mTxnAccountID, saTakerGets).isPositive () // Not funded. || bUnfunded) // Consider unfunded. { // Complete as is. nothing (); } else if (mPriorBalance.getNValue () < mEngine->getLedger ()->getReserve (sleCreator->getFieldU32 (sfOwnerCount) + 1)) { if (bOpenLedger) // Ledger is not final, can vote no. { // Hope for more reserve to come in or more offers to consume. terResult = tecINSUF_RESERVE_OFFER; } else if (!saPaid && !saGot) { // Ledger is final, insufficent reserve to create offer, processed nothing. terResult = tecINSUF_RESERVE_OFFER; } else { // Ledger is final, insufficent reserve to create offer, processed something. // Consider the offer unfunded. Treat as tesSUCCESS. nothing (); } } else { // We need to place the remainder of the offer into its order book. WriteLog (lsINFO, OfferCreateTransactor) << boost::str (boost::format ("OfferCreate: offer not fully consumed: saTakerPays=%s saTakerGets=%s") % saTakerPays.getFullText () % saTakerGets.getFullText ()); // Add offer to owner's directory. terResult = lesActive.dirAdd (uOwnerNode, Ledger::getOwnerDirIndex (mTxnAccountID), uLedgerIndex, BIND_TYPE (&Ledger::ownerDirDescriber, P_1, mTxnAccountID)); if (tesSUCCESS == terResult) { lesActive.ownerCountAdjust (mTxnAccountID, 1, sleCreator); // Update owner count. uint256 uBookBase = Ledger::getBookBase (uPaysCurrency, uPaysIssuerID, uGetsCurrency, uGetsIssuerID); WriteLog (lsINFO, OfferCreateTransactor) << boost::str (boost::format ("OfferCreate: adding to book: %s : %s/%s -> %s/%s") % uBookBase.ToString () % saTakerPays.getHumanCurrency () % RippleAddress::createHumanAccountID (saTakerPays.getIssuer ()) % saTakerGets.getHumanCurrency () % RippleAddress::createHumanAccountID (saTakerGets.getIssuer ())); uDirectory = Ledger::getQualityIndex (uBookBase, uRate); // Use original rate. // Add offer to order book. terResult = lesActive.dirAdd (uBookNode, uDirectory, uLedgerIndex, BIND_TYPE (&Ledger::qualityDirDescriber, P_1, saTakerPays.getCurrency (), uPaysIssuerID, saTakerGets.getCurrency (), uGetsIssuerID, uRate)); } if (tesSUCCESS == terResult) { WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: sfAccount=" << RippleAddress::createHumanAccountID (mTxnAccountID); WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: uPaysIssuerID=" << RippleAddress::createHumanAccountID (uPaysIssuerID); WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: uGetsIssuerID=" << RippleAddress::createHumanAccountID (uGetsIssuerID); WriteLog (lsTRACE, OfferCreateTransactor) << "OfferCreate: saTakerPays.isNative()=" << saTakerPays.isNative (); WriteLog (lsTRACE, OfferCreateTransactor) << "OfferCreate: saTakerGets.isNative()=" << saTakerGets.isNative (); WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: uPaysCurrency=" << saTakerPays.getHumanCurrency (); WriteLog (lsDEBUG, OfferCreateTransactor) << "OfferCreate: uGetsCurrency=" << saTakerGets.getHumanCurrency (); SLE::pointer sleOffer = mEngine->entryCreate (ltOFFER, uLedgerIndex); sleOffer->setFieldAccount (sfAccount, mTxnAccountID); sleOffer->setFieldU32 (sfSequence, uSequence); sleOffer->setFieldH256 (sfBookDirectory, uDirectory); sleOffer->setFieldAmount (sfTakerPays, saTakerPays); sleOffer->setFieldAmount (sfTakerGets, saTakerGets); sleOffer->setFieldU64 (sfOwnerNode, uOwnerNode); sleOffer->setFieldU64 (sfBookNode, uBookNode); if (uExpiration) sleOffer->setFieldU32 (sfExpiration, uExpiration); if (bPassive) sleOffer->setFlag (lsfPassive); if (bSell) sleOffer->setFlag (lsfSell); WriteLog (lsINFO, OfferCreateTransactor) << boost::str (boost::format ("OfferCreate: final terResult=%s sleOffer=%s") % transToken (terResult) % sleOffer->getJson (0)); } } // On storing meta data, delete offers that were found unfunded to prevent encountering them in future. if (tesSUCCESS == terResult) { BOOST_FOREACH (uint256 const & uOfferIndex, usOfferUnfundedFound) { WriteLog (lsINFO, OfferCreateTransactor) << "takeOffers: found unfunded: " << uOfferIndex.ToString (); terResult = lesActive.offerDelete (uOfferIndex); if (tesSUCCESS != terResult) break; } } CondLog (tesSUCCESS != terResult, lsINFO, OfferCreateTransactor) << boost::str (boost::format ("OfferCreate: final terResult=%s") % transToken (terResult)); if (isTesSuccess (terResult)) theApp->getOrderBookDB ().invalidate (); return terResult; } // vim:ts=4