ter createoffer::doapply() { if (m_journal.debug) m_journal.debug << "offercreate> " << mtxn.getjson (0); 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 (); auto const& ugetscurrency = satakergets.getcurrency (); 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 seuqnce // number insufficient? std::uint32_t const uaccountsequencenext = mtxnaccount->getfieldu32 (sfsequence); std::uint32_t const usequence = mtxn.getsequence (); const uint256 uledgerindex = getofferindex (mtxnaccountid, usequence); if (m_journal.debug) { m_journal.debug << "creating offer node: " << to_string (uledgerindex) << " usequence=" << usequence; if (bimmediateorcancel) m_journal.debug << "transaction: ioc set."; if (bfillorkill) m_journal.debug << "transaction: fok set."; } // this is the original rate of this offer, and is the rate at which it will // be placed, even if crossing offers change the amounts. std::uint64_t const urate = getrate (satakergets, satakerpays); ter terresult (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->entrycache ( ltaccount_root, getaccountrootindex (mtxnaccountid)); // additional checking for currency asset. // buy asset if (assetcurrency() == upayscurrency) { if (assetcurrency() == ugetscurrency || // asset for asset bsell) // tfsell set while buying asset return temdisabled; if (satakerpays < stamount(satakerpays.issue(), getconfig().asset_tx_min) || !satakerpays.ismathematicalinteger()) return tembad_offer; if (upaysissuerid == mtxnaccountid || ugetsissuerid == mtxnaccountid) { m_journal.trace << "creating asset offer is not allowed for issuer"; return temdisabled; } } // sell asset if (assetcurrency() == ugetscurrency) { if (!bsell) // tfsell not set while selling asset return temdisabled; if (satakergets < stamount(satakergets.issue(), getconfig().asset_tx_min) || !satakergets.ismathematicalinteger()) return tembad_offer; } if (utxflags & tfoffercreatemask) { if (m_journal.debug) m_journal.debug << "malformed transaction: invalid flags set."; terresult = teminvalid_flag; } else if (bimmediateorcancel && bfillorkill) { if (m_journal.debug) m_journal.debug << "malformed transaction: both ioc and fok set."; terresult = teminvalid_flag; } else if (bhaveexpiration && !uexpiration) { m_journal.warning << "malformed offer: bad expiration"; terresult = tembad_expiration; } else if (satakerpays.isnative () && satakergets.isnative ()) { m_journal.warning << "malformed offer: xrp for xrp"; terresult = tembad_offer; } else if (satakerpays <= zero || satakergets <= zero) { m_journal.warning << "malformed offer: bad amount"; terresult = tembad_offer; } else if (upayscurrency == ugetscurrency && upaysissuerid == ugetsissuerid) { m_journal.warning << "malformed offer: redundant offer"; terresult = temredundant; } // we don't allow a non-native currency to use the currency code vrp. else if (badcurrency() == upayscurrency || badcurrency() == ugetscurrency) { m_journal.warning << "malformed offer: bad currency."; terresult = tembad_currency; } else if (satakerpays.isnative () != isnative(upaysissuerid) || satakergets.isnative () != isnative(ugetsissuerid)) { m_journal.warning << "malformed offer: bad issuer"; terresult = tembad_issuer; } else if (view.isglobalfrozen (upaysissuerid) || view.isglobalfrozen (ugetsissuerid)) { m_journal.warning << "offer involves frozen asset"; terresult = tecfrozen; } else if (view.accountfunds ( mtxnaccountid, satakergets, fhzero_if_frozen) <= zero) { m_journal.warning << "delay: offers must be at least partially funded."; terresult = tecunfunded_offer; } // this can probably be simplified to make sure that you cancel sequences // before the transaction sequence number. else if (bhavecancel && (!ucancelsequence || uaccountsequencenext - 1 <= ucancelsequence)) { if (m_journal.debug) m_journal.debug << "uaccountsequencenext=" << uaccountsequencenext << " uoffersequence=" << ucancelsequence; terresult = tembad_sequence; } if (terresult != tessuccess) { if (m_journal.debug) m_journal.debug << "final terresult=" << transtoken (terresult); return terresult; } // process a cancellation request that's passed along with an offer. if ((terresult == tessuccess) && bhavecancel) { uint256 const ucancelindex ( getofferindex (mtxnaccountid, ucancelsequence)); sle::pointer slecancel = mengine->entrycache (ltoffer, ucancelindex); // it's not an error to not find the offer to cancel: it might have // been consumed or removed as we are processing. if (slecancel) { m_journal.warning << "cancelling order with sequence " << ucancelsequence; terresult = 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 (terresult == tessuccess && !satakerpays.isnative ()) terresult = checkacceptasset (issue (upayscurrency, upaysissuerid)); bool crossed = false; bool const bopenledger (mparams & tapopen_ledger); if (terresult == tessuccess) { // we reverse gets and pays because during offer crossing we are taking. core::amounts const taker_amount (satakergets, satakerpays); // the amount of the offer that we will need to place, after we finish // offer crossing processing. it may be equal to the original amount, // empty (fully crossed), or something in-between. core::amounts place_offer; std::tie(terresult, place_offer) = crossoffers (view, taker_amount); if (terresult == tecfailed_processing && bopenledger) terresult = telfailed_processing; if (terresult == tessuccess) { // we now need to reduce the offer by the cross flow. we reverse // in and out here, since during crossing we were takers. assert (satakerpays.getcurrency () == place_offer.out.getcurrency ()); assert (satakerpays.getissuer () == place_offer.out.getissuer ()); assert (satakergets.getcurrency () == place_offer.in.getcurrency ()); assert (satakergets.getissuer () == place_offer.in.getissuer ()); if (taker_amount != place_offer) crossed = true; if (m_journal.debug) { m_journal.debug << "offer crossing: " << transtoken (terresult); if (terresult == tessuccess) { m_journal.debug << " takerpays: " << satakerpays.getfulltext () << " -> " << place_offer.out.getfulltext (); m_journal.debug << " takergets: " << satakergets.getfulltext () << " -> " << place_offer.in.getfulltext (); } } satakerpays = place_offer.out; satakergets = place_offer.in; } } if (terresult != tessuccess) { m_journal.debug << "final terresult=" << transtoken (terresult); return terresult; } if (m_journal.debug) { m_journal.debug << "takeoffers: satakerpays=" <<satakerpays.getfulltext (); m_journal.debug << "takeoffers: satakergets=" << satakergets.getfulltext (); m_journal.debug << "takeoffers: mtxnaccountid=" << to_string (mtxnaccountid); m_journal.debug << "takeoffers: funds=" << view.accountfunds ( mtxnaccountid, satakergets, fhzero_if_frozen).getfulltext (); } if (satakerpays < zero || satakergets < zero) { // earlier, we verified that the amounts, as specified in the offer, // were not negative. that they are now suggests that something went // very wrong with offer crossing. m_journal.fatal << (crossed ? "partially consumed" : "full") << " offer has negative component:" << " pays=" << satakerpays.getfulltext () << " gets=" << satakergets.getfulltext (); assert (satakerpays >= zero); assert (satakergets >= zero); return tefinternal; } if (bfillorkill && (satakerpays != zero || satakergets != zero)) { // fill or kill and have leftovers. view.swapwith (view_checkpoint); // restore with just fees paid. return tessuccess; } // what the reserve would be if this offer was placed. auto const accountreserve (mengine->getledger ()->getreserve ( slecreator->getfieldu32 (sfownercount) + 1)); if (satakerpays == zero || // wants nothing more. satakergets == zero || // offering nothing more. bimmediateorcancel) // do not persist. { // complete as is. } else if (mpriorbalance.getnvalue () < accountreserve) { // if we are here, the signing account had an insufficient reserve // *prior* to our processing. we use the prior balance to simplify // client writing and make the user experience better. if (bopenledger) // ledger is not final, can vote no. { // hope for more reserve to come in or more offers to consume. if we // specified a local error this transaction will not be retried, so // specify a tec to distribute the transaction and allow it to be // retried. in particular, it may have been successful to a // degree (partially filled) and if it hasn't, it might succeed. terresult = tecinsuf_reserve_offer; } else if (!crossed) { // 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. } } else { assert (satakerpays > zero); assert (satakergets > zero); // we need to place the remainder of the offer into its order book. if (m_journal.debug) m_journal.debug << "offer not fully consumed:" << " satakerpays=" << satakerpays.getfulltext () << " satakergets=" << satakergets.getfulltext (); std::uint64_t uownernode; std::uint64_t ubooknode; uint256 udirectory; // add offer to owner's directory. terresult = view.diradd (uownernode, getownerdirindex (mtxnaccountid), uledgerindex, std::bind ( &ledger::ownerdirdescriber, std::placeholders::_1, std::placeholders::_2, mtxnaccountid)); if (tessuccess == terresult) { // update owner count. view.incrementownercount (slecreator); uint256 const ubookbase (getbookbase ( {{upayscurrency, upaysissuerid}, {ugetscurrency, ugetsissuerid}})); if (m_journal.debug) m_journal.debug << "adding to book: " << to_string (ubookbase) << " : " << satakerpays.gethumancurrency () << "/" << to_string (satakerpays.getissuer ()) << " -> " << satakergets.gethumancurrency () << "/" << to_string (satakergets.getissuer ()); // we use the original rate to place the offer. udirectory = getqualityindex (ubookbase, urate); // add offer to order book. terresult = view.diradd (ubooknode, udirectory, uledgerindex, std::bind ( &ledger::qualitydirdescriber, std::placeholders::_1, std::placeholders::_2, satakerpays.getcurrency (), upaysissuerid, satakergets.getcurrency (), ugetsissuerid, urate)); } if (tessuccess == terresult) { if (m_journal.debug) { m_journal.debug << "sfaccount=" << to_string (mtxnaccountid); m_journal.debug << "upaysissuerid=" << to_string (upaysissuerid); m_journal.debug << "ugetsissuerid=" << to_string (ugetsissuerid); m_journal.debug << "satakerpays.isnative()=" << satakerpays.isnative (); m_journal.debug << "satakergets.isnative()=" << satakergets.isnative (); m_journal.debug << "upayscurrency=" << satakerpays.gethumancurrency (); m_journal.debug << "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); if (m_journal.debug) m_journal.debug << "final terresult=" << transtoken (terresult) << " sleoffer=" << sleoffer->getjson (0); } } if (terresult != tessuccess) { m_journal.debug << "final terresult=" << transtoken (terresult); } return terresult; }
TER CreateOffer::doApply () { if (m_journal.debug) m_journal.debug << "OfferCreate> " << mTxn.getJson (0); 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 (!saTakerPays.isLegalNet () || !saTakerGets.isLegalNet ()) return temBAD_AMOUNT; auto const& uPaysIssuerID = saTakerPays.getIssuer (); auto const& uPaysCurrency = saTakerPays.getCurrency (); auto const& uGetsIssuerID = saTakerGets.getIssuer (); auto const& uGetsCurrency = saTakerGets.getCurrency (); 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 seuqnce // number insufficient? std::uint32_t const uAccountSequenceNext = mTxnAccount->getFieldU32 (sfSequence); std::uint32_t const uSequence = mTxn.getSequence (); const uint256 uLedgerIndex = Ledger::getOfferIndex (mTxnAccountID, uSequence); if (m_journal.debug) { m_journal.debug << "Creating offer node: " << to_string (uLedgerIndex) << " uSequence=" << uSequence; if (bImmediateOrCancel) m_journal.debug << "Transaction: IoC set."; if (bFillOrKill) m_journal.debug << "Transaction: FoK set."; } // This is the original rate of this offer, and is the rate at which it will // be placed, even if crossing offers change the amounts. std::uint64_t const uRate = STAmount::getRate (saTakerGets, saTakerPays); TER terResult (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->entryCache ( ltACCOUNT_ROOT, Ledger::getAccountRootIndex (mTxnAccountID)); if (uTxFlags & tfOfferCreateMask) { if (m_journal.debug) m_journal.debug << "Malformed transaction: Invalid flags set."; terResult = temINVALID_FLAG; } else if (bImmediateOrCancel && bFillOrKill) { if (m_journal.debug) m_journal.debug << "Malformed transaction: both IoC and FoK set."; terResult = temINVALID_FLAG; } else if (bHaveExpiration && !uExpiration) { m_journal.warning << "Malformed offer: bad expiration"; terResult = temBAD_EXPIRATION; } else if (saTakerPays.isNative () && saTakerGets.isNative ()) { m_journal.warning << "Malformed offer: XRP for XRP"; terResult = temBAD_OFFER; } else if (saTakerPays <= zero || saTakerGets <= zero) { m_journal.warning << "Malformed offer: bad amount"; terResult = temBAD_OFFER; } else if (uPaysCurrency == uGetsCurrency && uPaysIssuerID == uGetsIssuerID) { m_journal.warning << "Malformed offer: redundant offer"; terResult = temREDUNDANT; } // We don't allow a non-native currency to use the currency code XRP. else if (badCurrency() == uPaysCurrency || badCurrency() == uGetsCurrency) { m_journal.warning << "Malformed offer: Bad currency."; terResult = temBAD_CURRENCY; } else if (saTakerPays.isNative () != !uPaysIssuerID || saTakerGets.isNative () != !uGetsIssuerID) { m_journal.warning << "Malformed offer: bad issuer"; terResult = temBAD_ISSUER; } else if (view.isGlobalFrozen (uPaysIssuerID) || view.isGlobalFrozen (uGetsIssuerID)) { m_journal.warning << "Offer involves frozen asset"; terResult = tecFROZEN; } else if (view.accountFunds ( mTxnAccountID, saTakerGets, fhZERO_IF_FROZEN) <= zero) { m_journal.warning << "delay: Offers must be at least partially funded."; terResult = tecUNFUNDED_OFFER; } // This can probably be simplified to make sure that you cancel sequences // before the transaction sequence number. else if (bHaveCancel && (!uCancelSequence || uAccountSequenceNext - 1 <= uCancelSequence)) { if (m_journal.debug) m_journal.debug << "uAccountSequenceNext=" << uAccountSequenceNext << " uOfferSequence=" << uCancelSequence; terResult = temBAD_SEQUENCE; } if (terResult != tesSUCCESS) { if (m_journal.debug) m_journal.debug << "final terResult=" << transToken (terResult); return terResult; } // Process a cancellation request that's passed along with an offer. if ((terResult == tesSUCCESS) && bHaveCancel) { uint256 const uCancelIndex ( Ledger::getOfferIndex (mTxnAccountID, uCancelSequence)); SLE::pointer sleCancel = mEngine->entryCache (ltOFFER, uCancelIndex); // It's not an error to not find the offer to cancel: it might have // been consumed or removed as we are processing. if (sleCancel) { m_journal.warning << "Cancelling order with sequence " << uCancelSequence; terResult = 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 (terResult == tesSUCCESS && !saTakerPays.isNative ()) terResult = checkAcceptAsset (Issue (uPaysCurrency, uPaysIssuerID)); bool crossed = false; bool const bOpenLedger (mParams & tapOPEN_LEDGER); if (terResult == tesSUCCESS) { // We reverse gets and pays because during offer crossing we are taking. core::Amounts const taker_amount (saTakerGets, saTakerPays); // The amount of the offer that we will need to place, after we finish // offer crossing processing. It may be equal to the original amount, // empty (fully crossed), or something in-between. core::Amounts place_offer; std::tie(terResult, place_offer) = crossOffers (view, taker_amount); if (terResult == tecFAILED_PROCESSING && bOpenLedger) terResult = telFAILED_PROCESSING; if (terResult == tesSUCCESS) { // We now need to reduce the offer by the cross flow. We reverse // in and out here, since during crossing we were takers. assert (saTakerPays.getCurrency () == place_offer.out.getCurrency ()); assert (saTakerPays.getIssuer () == place_offer.out.getIssuer ()); assert (saTakerGets.getCurrency () == place_offer.in.getCurrency ()); assert (saTakerGets.getIssuer () == place_offer.in.getIssuer ()); if (taker_amount != place_offer) crossed = true; if (m_journal.debug) { m_journal.debug << "Offer Crossing: " << transToken (terResult); if (terResult == tesSUCCESS) { m_journal.debug << " takerPays: " << saTakerPays.getFullText () << " -> " << place_offer.out.getFullText (); m_journal.debug << " takerGets: " << saTakerGets.getFullText () << " -> " << place_offer.in.getFullText (); } } saTakerPays = place_offer.out; saTakerGets = place_offer.in; } } if (terResult != tesSUCCESS) { m_journal.debug << "final terResult=" << transToken (terResult); return terResult; } if (m_journal.debug) { m_journal.debug << "takeOffers: saTakerPays=" <<saTakerPays.getFullText (); m_journal.debug << "takeOffers: saTakerGets=" << saTakerGets.getFullText (); m_journal.debug << "takeOffers: mTxnAccountID=" << to_string (mTxnAccountID); m_journal.debug << "takeOffers: FUNDS=" << view.accountFunds ( mTxnAccountID, saTakerGets, fhZERO_IF_FROZEN).getFullText (); } if (saTakerPays < zero || saTakerGets < zero) { // Earlier, we verified that the amounts, as specified in the offer, // were not negative. That they are now suggests that something went // very wrong with offer crossing. m_journal.fatal << (crossed ? "Partially consumed" : "Full") << " offer has negative component:" << " pays=" << saTakerPays.getFullText () << " gets=" << saTakerGets.getFullText (); assert (saTakerPays >= zero); assert (saTakerGets >= zero); return tefINTERNAL; } if (bFillOrKill && (saTakerPays != zero || saTakerGets != zero)) { // Fill or kill and have leftovers. view.swapWith (view_checkpoint); // Restore with just fees paid. return tesSUCCESS; } // What the reserve would be if this offer was placed. auto const accountReserve (mEngine->getLedger ()->getReserve ( sleCreator->getFieldU32 (sfOwnerCount) + 1)); if (saTakerPays == zero || // Wants nothing more. saTakerGets == zero || // Offering nothing more. bImmediateOrCancel) // Do not persist. { // Complete as is. } else if (mPriorBalance.getNValue () < accountReserve) { // If we are here, the signing account had an insufficient reserve // *prior* to our processing. We use the prior balance to simplify // client writing and make the user experience better. if (bOpenLedger) // Ledger is not final, can vote no. { // Hope for more reserve to come in or more offers to consume. If we // specified a local error this transaction will not be retried, so // specify a tec to distribute the transaction and allow it to be // retried. In particular, it may have been successful to a // degree (partially filled) and if it hasn't, it might succeed. terResult = tecINSUF_RESERVE_OFFER; } else if (!crossed) { // 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. } } else { assert (saTakerPays > zero); assert (saTakerGets > zero); // We need to place the remainder of the offer into its order book. if (m_journal.debug) m_journal.debug << "offer not fully consumed:" << " saTakerPays=" << saTakerPays.getFullText () << " saTakerGets=" << saTakerGets.getFullText (); std::uint64_t uOwnerNode; std::uint64_t uBookNode; uint256 uDirectory; // Add offer to owner's directory. terResult = view.dirAdd (uOwnerNode, Ledger::getOwnerDirIndex (mTxnAccountID), uLedgerIndex, std::bind ( &Ledger::ownerDirDescriber, std::placeholders::_1, std::placeholders::_2, mTxnAccountID)); if (tesSUCCESS == terResult) { // Update owner count. view.ownerCountAdjust (mTxnAccountID, 1, sleCreator); uint256 const uBookBase (Ledger::getBookBase ( {{uPaysCurrency, uPaysIssuerID}, {uGetsCurrency, uGetsIssuerID}})); if (m_journal.debug) m_journal.debug << "adding to book: " << to_string (uBookBase) << " : " << saTakerPays.getHumanCurrency () << "/" << to_string (saTakerPays.getIssuer ()) << " -> " << saTakerGets.getHumanCurrency () << "/" << to_string (saTakerGets.getIssuer ()); // We use the original rate to place the offer. uDirectory = Ledger::getQualityIndex (uBookBase, uRate); // Add offer to order book. terResult = view.dirAdd (uBookNode, uDirectory, uLedgerIndex, std::bind ( &Ledger::qualityDirDescriber, std::placeholders::_1, std::placeholders::_2, saTakerPays.getCurrency (), uPaysIssuerID, saTakerGets.getCurrency (), uGetsIssuerID, uRate)); } if (tesSUCCESS == terResult) { if (m_journal.debug) { m_journal.debug << "sfAccount=" << to_string (mTxnAccountID); m_journal.debug << "uPaysIssuerID=" << to_string (uPaysIssuerID); m_journal.debug << "uGetsIssuerID=" << to_string (uGetsIssuerID); m_journal.debug << "saTakerPays.isNative()=" << saTakerPays.isNative (); m_journal.debug << "saTakerGets.isNative()=" << saTakerGets.isNative (); m_journal.debug << "uPaysCurrency=" << saTakerPays.getHumanCurrency (); m_journal.debug << "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); if (m_journal.debug) m_journal.debug << "final terResult=" << transToken (terResult) << " sleOffer=" << sleOffer->getJson (0); } } if (terResult != tesSUCCESS) { m_journal.debug << "final terResult=" << transToken (terResult); } return terResult; }