static STAmount amount (Integer integer, std::enable_if_t <! std::is_signed <Integer>::value>* = 0) { static_assert (std::is_integral <Integer>::value, ""); if (integer < 0) return STAmount (-integer, true); return STAmount (integer, false); }
TER Transactor::apply () { preCompute(); // If the transactor requires a valid account and the transaction doesn't // list one, preflight will have already a flagged a failure. auto const sle = view().peek (keylet::account(account_)); // sle must exist except for transactions // that allow zero account. assert(sle != nullptr || account_ == beast::zero); if (sle) { mPriorBalance = STAmount ((*sle)[sfBalance]).xrp (); mSourceBalance = mPriorBalance; setSeq(); auto result = payFee (); if (result != tesSUCCESS) return result; view().update (sle); } return doApply (); }
TER Change::payFee () { if (mTxn.getTransactionFee () != STAmount ()) { m_journal.warning << "Non-zero fee"; return temBAD_FEE; } return tesSUCCESS; }
TER ChangeTransactor::payFee () { if (mTxn.getTransactionFee () != STAmount ()) { WriteLog (lsWARNING, ChangeTransactor) << "Change transaction with non-zero fee"; return temBAD_FEE; } return tesSUCCESS; }
void test_operations () { testcase ("operations"); Quality const q11 (Amounts ( STAmount (noIssue(), 731), STAmount (noIssue(), 731))); Quality qa (q11); Quality qb (q11); BEAST_EXPECT(qa == qb); BEAST_EXPECT(++qa != q11); BEAST_EXPECT(qa != qb); BEAST_EXPECT(--qb != q11); BEAST_EXPECT(qa != qb); BEAST_EXPECT(qb < qa); BEAST_EXPECT(qb++ < qa); BEAST_EXPECT(qb++ < qa); BEAST_EXPECT(qb++ == qa); BEAST_EXPECT(qa < qb); }
bool PathRequest::isValid (RippleLineCache::ref crCache) { ScopedLockType sl (mLock); bValid = raSrcAccount.isSet () && raDstAccount.isSet () && saDstAmount > zero; Ledger::pointer lrLedger = crCache->getLedger (); if (bValid) { AccountState::pointer asSrc = getApp().getOPs ().getAccountState (crCache->getLedger(), raSrcAccount); if (!asSrc) { // no source account bValid = false; jvStatus = rpcError (rpcSRC_ACT_NOT_FOUND); } else { AccountState::pointer asDst = getApp().getOPs ().getAccountState (lrLedger, raDstAccount); Json::Value& jvDestCur = (jvStatus["destination_currencies"] = Json::arrayValue); if (!asDst) { // no destination account jvDestCur.append (Json::Value ("XRP")); if (!saDstAmount.isNative ()) { // only XRP can be send to a non-existent account bValid = false; jvStatus = rpcError (rpcACT_NOT_FOUND); } else if (saDstAmount < STAmount (lrLedger->getReserve (0))) { // payment must meet reserve bValid = false; jvStatus = rpcError (rpcDST_AMT_MALFORMED); } } else { bool const disallowXRP ( asDst->peekSLE ().getFlags() & lsfDisallowXRP); CurrencySet usDestCurrID = usAccountDestCurrencies (raDstAccount, crCache, !disallowXRP); for (auto const& currency : usDestCurrID) jvDestCur.append (to_string (currency)); jvStatus["destination_tag"] = (asDst->peekSLE ().getFlags () & lsfRequireDestTag) != 0; } } } if (bValid) { jvStatus["ledger_hash"] = to_string (lrLedger->getHash ()); jvStatus["ledger_index"] = lrLedger->getLedgerSeq (); } return bValid; }
TER TrustSetTransactor::doApply () { TER terResult = tesSUCCESS; WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet>"; const STAmount saLimitAmount = mTxn.getFieldAmount (sfLimitAmount); const bool bQualityIn = mTxn.isFieldPresent (sfQualityIn); const bool bQualityOut = mTxn.isFieldPresent (sfQualityOut); const uint160 uCurrencyID = saLimitAmount.getCurrency (); uint160 uDstAccountID = saLimitAmount.getIssuer (); const bool bHigh = mTxnAccountID > uDstAccountID; // true, iff current is high account. uint32 uQualityIn = bQualityIn ? mTxn.getFieldU32 (sfQualityIn) : 0; uint32 uQualityOut = bQualityOut ? mTxn.getFieldU32 (sfQualityOut) : 0; if (!saLimitAmount.isLegalNet ()) return temBAD_AMOUNT; if (bQualityIn && QUALITY_ONE == uQualityIn) uQualityIn = 0; if (bQualityOut && QUALITY_ONE == uQualityOut) uQualityOut = 0; const uint32 uTxFlags = mTxn.getFlags (); if (uTxFlags & tfTrustSetMask) { WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } const bool bSetAuth = isSetBit (uTxFlags, tfSetfAuth); const bool bSetNoRipple = isSetBit (uTxFlags, tfSetNoRipple); const bool bClearNoRipple = isSetBit (uTxFlags, tfClearNoRipple); if (bSetAuth && !isSetBit (mTxnAccount->getFieldU32 (sfFlags), lsfRequireAuth)) { WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Retry: Auth not required."; return tefNO_AUTH_REQUIRED; } if (saLimitAmount.isNative ()) { WriteLog (lsINFO, TrustSetTransactor) << boost::str (boost::format ("doTrustSet: Malformed transaction: Native credit limit: %s") % saLimitAmount.getFullText ()); return temBAD_LIMIT; } if (saLimitAmount.isNegative ()) { WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Malformed transaction: Negative credit limit."; return temBAD_LIMIT; } // Check if destination makes sense. if (!uDstAccountID || uDstAccountID == ACCOUNT_ONE) { WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Malformed transaction: Destination account not specified."; return temDST_NEEDED; } if (mTxnAccountID == uDstAccountID) { SLE::pointer selDelete = mEngine->entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, uCurrencyID)); if (selDelete) { WriteLog (lsWARNING, TrustSetTransactor) << "doTrustSet: Clearing redundant line."; return mEngine->getNodes ().trustDelete (selDelete, mTxnAccountID, uDstAccountID); } else { WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Malformed transaction: Can not extend credit to self."; return temDST_IS_SRC; } } SLE::pointer sleDst = mEngine->entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uDstAccountID)); if (!sleDst) { WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Delay transaction: Destination account does not exist."; return tecNO_DST; } const uint32 uOwnerCount = mTxnAccount->getFieldU32 (sfOwnerCount); // The reserve required to create the line. const uint64 uReserveCreate = (uOwnerCount < 2) ? 0 : mEngine->getLedger ()->getReserve (uOwnerCount + 1); STAmount saLimitAllow = saLimitAmount; saLimitAllow.setIssuer (mTxnAccountID); SLE::pointer sleRippleState = mEngine->entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, uCurrencyID)); if (sleRippleState) { STAmount saLowBalance; STAmount saLowLimit; STAmount saHighBalance; STAmount saHighLimit; uint32 uLowQualityIn; uint32 uLowQualityOut; uint32 uHighQualityIn; uint32 uHighQualityOut; const uint160& uLowAccountID = !bHigh ? mTxnAccountID : uDstAccountID; const uint160& uHighAccountID = bHigh ? mTxnAccountID : uDstAccountID; SLE::ref sleLowAccount = !bHigh ? mTxnAccount : sleDst; SLE::ref sleHighAccount = bHigh ? mTxnAccount : sleDst; // // Balances // saLowBalance = sleRippleState->getFieldAmount (sfBalance); saHighBalance = -saLowBalance; // // Limits // sleRippleState->setFieldAmount (!bHigh ? sfLowLimit : sfHighLimit, saLimitAllow); saLowLimit = !bHigh ? saLimitAllow : sleRippleState->getFieldAmount (sfLowLimit); saHighLimit = bHigh ? saLimitAllow : sleRippleState->getFieldAmount (sfHighLimit); // // Quality in // if (!bQualityIn) { // Not setting. Just get it. uLowQualityIn = sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = sleRippleState->getFieldU32 (sfHighQualityIn); } else if (uQualityIn) { // Setting. sleRippleState->setFieldU32 (!bHigh ? sfLowQualityIn : sfHighQualityIn, uQualityIn); uLowQualityIn = !bHigh ? uQualityIn : sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = bHigh ? uQualityIn : sleRippleState->getFieldU32 (sfHighQualityIn); } else { // Clearing. sleRippleState->makeFieldAbsent (!bHigh ? sfLowQualityIn : sfHighQualityIn); uLowQualityIn = !bHigh ? 0 : sleRippleState->getFieldU32 (sfLowQualityIn); uHighQualityIn = bHigh ? 0 : sleRippleState->getFieldU32 (sfHighQualityIn); } if (QUALITY_ONE == uLowQualityIn) uLowQualityIn = 0; if (QUALITY_ONE == uHighQualityIn) uHighQualityIn = 0; // // Quality out // if (!bQualityOut) { // Not setting. Just get it. uLowQualityOut = sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = sleRippleState->getFieldU32 (sfHighQualityOut); } else if (uQualityOut) { // Setting. sleRippleState->setFieldU32 (!bHigh ? sfLowQualityOut : sfHighQualityOut, uQualityOut); uLowQualityOut = !bHigh ? uQualityOut : sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = bHigh ? uQualityOut : sleRippleState->getFieldU32 (sfHighQualityOut); } else { // Clearing. sleRippleState->makeFieldAbsent (!bHigh ? sfLowQualityOut : sfHighQualityOut); uLowQualityOut = !bHigh ? 0 : sleRippleState->getFieldU32 (sfLowQualityOut); uHighQualityOut = bHigh ? 0 : sleRippleState->getFieldU32 (sfHighQualityOut); } const uint32 uFlagsIn = sleRippleState->getFieldU32 (sfFlags); uint32 uFlagsOut = uFlagsIn; if (bSetNoRipple && !bClearNoRipple && (bHigh ? saHighBalance : saLowBalance).isGEZero()) { uFlagsOut |= (bHigh ? lsfHighNoRipple : lsfLowNoRipple); } else if (bClearNoRipple && !bSetNoRipple) { uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple); } if (QUALITY_ONE == uLowQualityOut) uLowQualityOut = 0; if (QUALITY_ONE == uHighQualityOut) uHighQualityOut = 0; const bool bLowReserveSet = uLowQualityIn || uLowQualityOut || isSetBit (uFlagsOut, lsfLowNoRipple) || !!saLowLimit || saLowBalance.isPositive (); const bool bLowReserveClear = !bLowReserveSet; const bool bHighReserveSet = uHighQualityIn || uHighQualityOut || isSetBit (uFlagsOut, lsfHighNoRipple) || !!saHighLimit || saHighBalance.isPositive (); const bool bHighReserveClear = !bHighReserveSet; const bool bDefault = bLowReserveClear && bHighReserveClear; const bool bLowReserved = isSetBit (uFlagsIn, lsfLowReserve); const bool bHighReserved = isSetBit (uFlagsIn, lsfHighReserve); bool bReserveIncrease = false; if (bSetAuth) { uFlagsOut |= (bHigh ? lsfHighAuth : lsfLowAuth); } if (bLowReserveSet && !bLowReserved) { // Set reserve for low account. mEngine->getNodes ().ownerCountAdjust (uLowAccountID, 1, sleLowAccount); uFlagsOut |= lsfLowReserve; if (!bHigh) bReserveIncrease = true; } if (bLowReserveClear && bLowReserved) { // Clear reserve for low account. mEngine->getNodes ().ownerCountAdjust (uLowAccountID, -1, sleLowAccount); uFlagsOut &= ~lsfLowReserve; } if (bHighReserveSet && !bHighReserved) { // Set reserve for high account. mEngine->getNodes ().ownerCountAdjust (uHighAccountID, 1, sleHighAccount); uFlagsOut |= lsfHighReserve; if (bHigh) bReserveIncrease = true; } if (bHighReserveClear && bHighReserved) { // Clear reserve for high account. mEngine->getNodes ().ownerCountAdjust (uHighAccountID, -1, sleHighAccount); uFlagsOut &= ~lsfHighReserve; } if (uFlagsIn != uFlagsOut) sleRippleState->setFieldU32 (sfFlags, uFlagsOut); if (bDefault || CURRENCY_BAD == uCurrencyID) { // Delete. terResult = mEngine->getNodes ().trustDelete (sleRippleState, uLowAccountID, uHighAccountID); } else if (bReserveIncrease && mPriorBalance.getNValue () < uReserveCreate) // Reserve is not scaled by load. { WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Delay transaction: Insufficent reserve to add trust line."; // Another transaction could provide XRP to the account and then this transaction would succeed. terResult = tecINSUF_RESERVE_LINE; } else { mEngine->entryModify (sleRippleState); WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Modify ripple line"; } } // Line does not exist. else if (!saLimitAmount // Setting default limit. && (!bQualityIn || !uQualityIn) // Not setting quality in or setting default quality in. && (!bQualityOut || !uQualityOut)) // Not setting quality out or setting default quality out. { WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Redundant: Setting non-existent ripple line to defaults."; return tecNO_LINE_REDUNDANT; } else if (mPriorBalance.getNValue () < uReserveCreate) // Reserve is not scaled by load. { WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Delay transaction: Line does not exist. Insufficent reserve to create line."; // Another transaction could create the account and then this transaction would succeed. terResult = tecNO_LINE_INSUF_RESERVE; } else if (CURRENCY_BAD == uCurrencyID) { terResult = temBAD_CURRENCY; } else { STAmount saBalance = STAmount (uCurrencyID, ACCOUNT_ONE); // Zero balance in currency. WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet: Creating ripple line: " << Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, uCurrencyID).ToString (); // Create a new ripple line. terResult = mEngine->getNodes ().trustCreate ( bHigh, mTxnAccountID, uDstAccountID, Ledger::getRippleStateIndex (mTxnAccountID, uDstAccountID, uCurrencyID), mTxnAccount, bSetAuth, bSetNoRipple && !bClearNoRipple, saBalance, saLimitAllow, // Limit for who is being charged. uQualityIn, uQualityOut); } WriteLog (lsINFO, TrustSetTransactor) << "doTrustSet<"; return terResult; }
// Append a node, then create and insert before it any implied nodes. Order // book nodes may go back to back. // // For each non-matching pair of IssuedCurrency, there's an order book. // // <-- resultCode: tesSUCCESS, temBAD_PATH, terNO_ACCOUNT, terNO_AUTH, // terNO_LINE, tecPATH_DRY TER PathState::pushNode ( const int iType, AccountID const& account, // If not specified, means an order book. Currency const& currency, // If not specified, default to previous. AccountID const& issuer) // If not specified, default to previous. { path::Node node; const bool pathIsEmpty = nodes_.empty (); // TODO(tom): if pathIsEmpty, we probably don't need to do ANYTHING below. // Indeed, we might just not even call pushNode in the first place! auto const& backNode = pathIsEmpty ? path::Node () : nodes_.back (); // true, iff node is a ripple account. false, iff node is an offer node. const bool hasAccount = (iType & STPathElement::typeAccount); // Is currency specified for the output of the current node? const bool hasCurrency = (iType & STPathElement::typeCurrency); // Issuer is specified for the output of the current node. const bool hasIssuer = (iType & STPathElement::typeIssuer); TER resultCode = tesSUCCESS; JLOG (j_.trace) << "pushNode> " << iType << ": " << (hasAccount ? to_string(account) : std::string("-")) << " " << (hasCurrency ? to_string(currency) : std::string("-")) << "/" << (hasIssuer ? to_string(issuer) : std::string("-")) << "/"; node.uFlags = iType; node.issue_.currency = hasCurrency ? currency : backNode.issue_.currency; // TODO(tom): we can probably just return immediately whenever we hit an // error in these next pages. if (iType & ~STPathElement::typeAll) { // Of course, this could never happen. JLOG (j_.debug) << "pushNode: bad bits."; resultCode = temBAD_PATH; } else if (hasIssuer && isXRP (node.issue_)) { JLOG (j_.debug) << "pushNode: issuer specified for XRP."; resultCode = temBAD_PATH; } else if (hasIssuer && !issuer) { JLOG (j_.debug) << "pushNode: specified bad issuer."; resultCode = temBAD_PATH; } else if (!hasAccount && !hasCurrency && !hasIssuer) { // You can't default everything to the previous node as you would make // no progress. JLOG (j_.debug) << "pushNode: offer must specify at least currency or issuer."; resultCode = temBAD_PATH; } else if (hasAccount) { // Account link node.account_ = account; node.issue_.account = hasIssuer ? issuer : (isXRP (node.issue_) ? xrpAccount() : account); // Zero value - for accounts. node.saRevRedeem = STAmount ({node.issue_.currency, account}); node.saRevIssue = node.saRevRedeem; // For order books only - zero currency with the issuer ID. node.saRevDeliver = STAmount (node.issue_); node.saFwdDeliver = node.saRevDeliver; if (pathIsEmpty) { // The first node is always correct as is. } else if (!account) { JLOG (j_.debug) << "pushNode: specified bad account."; resultCode = temBAD_PATH; } else { // Add required intermediate nodes to deliver to current account. JLOG (j_.trace) << "pushNode: imply for account."; resultCode = pushImpliedNodes ( node.account_, node.issue_.currency, isXRP(node.issue_.currency) ? xrpAccount() : account); // Note: backNode may no longer be the immediately previous node. } if (resultCode == tesSUCCESS && !nodes_.empty ()) { auto const& backNode = nodes_.back (); if (backNode.isAccount()) { auto sleRippleState = view().peek( keylet::line(backNode.account_, node.account_, backNode.issue_.currency)); // A "RippleState" means a balance betweeen two accounts for a // specific currency. if (!sleRippleState) { JLOG (j_.trace) << "pushNode: No credit line between " << backNode.account_ << " and " << node.account_ << " for " << node.issue_.currency << "." ; JLOG (j_.trace) << getJson (); resultCode = terNO_LINE; } else { JLOG (j_.trace) << "pushNode: Credit line found between " << backNode.account_ << " and " << node.account_ << " for " << node.issue_.currency << "." ; auto sleBck = view().peek ( keylet::account(backNode.account_)); // Is the source account the highest numbered account ID? bool bHigh = backNode.account_ > node.account_; if (!sleBck) { JLOG (j_.warning) << "pushNode: delay: can't receive IOUs from " << "non-existent issuer: " << backNode.account_; resultCode = terNO_ACCOUNT; } else if ((sleBck->getFieldU32 (sfFlags) & lsfRequireAuth) && !(sleRippleState->getFieldU32 (sfFlags) & (bHigh ? lsfHighAuth : lsfLowAuth)) && sleRippleState->getFieldAmount(sfBalance) == zero) { JLOG (j_.warning) << "pushNode: delay: can't receive IOUs from " << "issuer without auth."; resultCode = terNO_AUTH; } if (resultCode == tesSUCCESS) { STAmount saOwed = creditBalance (view(), node.account_, backNode.account_, node.issue_.currency); STAmount saLimit; if (saOwed <= zero) { saLimit = creditLimit (view(), node.account_, backNode.account_, node.issue_.currency); if (-saOwed >= saLimit) { JLOG (j_.debug) << "pushNode: dry:" << " saOwed=" << saOwed << " saLimit=" << saLimit; resultCode = tecPATH_DRY; } } } } } } if (resultCode == tesSUCCESS) nodes_.push_back (node); } else { // Offer link. // // Offers bridge a change in currency and issuer, or just a change in // issuer. if (hasIssuer) node.issue_.account = issuer; else if (isXRP (node.issue_.currency)) node.issue_.account = xrpAccount(); else if (isXRP (backNode.issue_.account)) node.issue_.account = backNode.account_; else node.issue_.account = backNode.issue_.account; node.saRateMax = STAmount::saZero; node.saRevDeliver = STAmount (node.issue_); node.saFwdDeliver = node.saRevDeliver; if (!isConsistent (node.issue_)) { JLOG (j_.debug) << "pushNode: currency is inconsistent with issuer."; resultCode = temBAD_PATH; } else if (backNode.issue_ == node.issue_) { JLOG (j_.debug) << "pushNode: bad path: offer to same currency and issuer"; resultCode = temBAD_PATH; } else { JLOG (j_.trace) << "pushNode: imply for offer."; // Insert intermediary issuer account if needed. resultCode = pushImpliedNodes ( xrpAccount(), // Rippling, but offers don't have an account. backNode.issue_.currency, backNode.issue_.account); } if (resultCode == tesSUCCESS) nodes_.push_back (node); } JLOG (j_.trace) << "pushNode< : " << transToken (resultCode); return resultCode; }
TER PaymentTransactor::doApply () { // Ripple if source or destination is non-native or if there are paths. std::uint32_t const uTxFlags = mTxn.getFlags (); //bool const bPartialPayment = is_bit_set(uTxFlags, tfPartialPayment); bool const bLimitQuality = is_bit_set (uTxFlags, tfLimitQuality); bool const bNoRippleDirect = is_bit_set (uTxFlags, tfNoRippleDirect); bool const bPaths = mTxn.isFieldPresent (sfPaths); bool const bMax = mTxn.isFieldPresent (sfSendMax); uint160 const uDstAccountID = mTxn.getFieldAccount160 (sfDestination); STAmount const saDstAmount = mTxn.getFieldAmount (sfAmount); STAmount const saMaxAmount = bMax ? mTxn.getFieldAmount (sfSendMax) : saDstAmount.isNative () ? saDstAmount : STAmount (saDstAmount.getCurrency (), mTxnAccountID, saDstAmount.getMantissa (), saDstAmount.getExponent (), saDstAmount < zero); uint160 const uSrcCurrency = saMaxAmount.getCurrency (); uint160 const uDstCurrency = saDstAmount.getCurrency (); bool const bSTRDirect = uSrcCurrency.isZero () && uDstCurrency.isZero (); m_journal.trace << "saMaxAmount=" << saMaxAmount.getFullText () << " saDstAmount=" << saDstAmount.getFullText (); if (!saDstAmount.isLegalNet () || !saMaxAmount.isLegalNet ()) return temBAD_AMOUNT; if (uTxFlags & tfPaymentMask) { m_journal.trace << "Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } else if (!uDstAccountID) { m_journal.trace << "Malformed transaction: Payment destination account not specified."; return temDST_NEEDED; } else if (bMax && saMaxAmount <= zero) { m_journal.trace << "Malformed transaction: bad max amount: " << saMaxAmount.getFullText (); return temBAD_AMOUNT; } else if (saDstAmount <= zero) { m_journal.trace << "Malformed transaction: bad dst amount: " << saDstAmount.getFullText (); return temBAD_AMOUNT; } else if (CURRENCY_BAD == uSrcCurrency || CURRENCY_BAD == uDstCurrency) { m_journal.trace << "Malformed transaction: Bad currency."; return temBAD_CURRENCY; } else if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) { m_journal.trace << "Malformed transaction: Redundant transaction:" << " src=" << to_string (mTxnAccountID) << " dst=" << to_string (uDstAccountID) << " src_cur=" << to_string (uSrcCurrency) << " dst_cur=" << to_string (uDstCurrency); return temREDUNDANT; } else if (bMax && saMaxAmount == saDstAmount && saMaxAmount.getCurrency () == saDstAmount.getCurrency ()) { m_journal.trace << "Malformed transaction: Redundant SendMax."; return temREDUNDANT_SEND_MAX; } else if (bSTRDirect && bMax) { m_journal.trace << "Malformed transaction: SendMax specified for STR to STR."; return temBAD_SEND_STR_MAX; } else if (bSTRDirect && bPaths) { m_journal.trace << "Malformed transaction: Paths specified for STR to STR."; return temBAD_SEND_STR_PATHS; } else if (bSTRDirect && bLimitQuality) { m_journal.trace << "Malformed transaction: Limit quality specified for STR to STR."; return temBAD_SEND_STR_LIMIT; } else if (bSTRDirect && bNoRippleDirect) { m_journal.trace << "Malformed transaction: No ripple direct specified for STR to STR."; return temBAD_SEND_STR_NO_DIRECT; } SLE::pointer sleDst (mEngine->entryCache ( ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uDstAccountID))); if (!sleDst) { // Destination account does not exist. if (!saDstAmount.isNative ()) { m_journal.trace << "Delay transaction: Destination account does not exist."; // Another transaction could create the account and then this transaction would succeed. return tecNO_DST; } // Note: Reserve is not scaled by load. else if (saDstAmount.getNValue () < mEngine->getLedger ()->getReserve (0)) { m_journal.trace << "Delay transaction: Destination account does not exist. " << "Insufficient payment to create account."; // Another transaction could create the account and then this // transaction would succeed. return tecNO_DST_INSUF_STR; } // Create the account. sleDst = mEngine->entryCreate ( ltACCOUNT_ROOT, Ledger::getAccountRootIndex (uDstAccountID)); sleDst->setFieldAccount (sfAccount, uDstAccountID); sleDst->setFieldU32 (sfSequence, 1); } else if ((sleDst->getFlags () & lsfRequireDestTag) && !mTxn.isFieldPresent (sfDestinationTag)) { m_journal.trace << "Malformed transaction: DestinationTag required."; return tefDST_TAG_NEEDED; } else { mEngine->entryModify (sleDst); } TER terResult; // XXX Should bMax be sufficient to imply ripple? bool const bRipple = bPaths || bMax || !saDstAmount.isNative (); if (bRipple) { // Ripple payment STPathSet spsPaths = mTxn.getFieldPathSet (sfPaths); std::vector<PathState::pointer> vpsExpanded; STAmount saMaxAmountAct; STAmount saDstAmountAct; try { bool const openLedger = is_bit_set (mParams, tapOPEN_LEDGER); bool tooManyPaths = false; if (spsPaths.size() > MAX_NUM_PATHS) tooManyPaths = true; else { for (auto const& path : spsPaths) { if (path.size() > MAX_PATH_SIZE) { tooManyPaths = true; break; } } } terResult = openLedger && tooManyPaths ? telBAD_PATH_COUNT // Too many paths for proposed ledger. : RippleCalc::rippleCalc( mEngine->view(), saMaxAmountAct, saDstAmountAct, vpsExpanded, saMaxAmount, saDstAmount, uDstAccountID, mTxnAccountID, spsPaths, false, bLimitQuality, bNoRippleDirect, // Always compute for finalizing ledger. false, // Not standalone, delete unfundeds. is_bit_set(mParams, tapOPEN_LEDGER)); if (isTerRetry(terResult)) terResult = tecPATH_DRY; if ((tesSUCCESS == terResult) && (saDstAmountAct != saDstAmount)) mEngine->view().setDeliveredAmount(saDstAmountAct); } catch (std::exception const& e) { m_journal.trace << "Caught throw: " << e.what (); terResult = tefEXCEPTION; } } else { // Direct STR payment. std::uint32_t const uOwnerCount (mTxnAccount->getFieldU32 (sfOwnerCount)); std::uint64_t const uReserve (mEngine->getLedger ()->getReserve (uOwnerCount)); // Make sure have enough reserve to send. Allow final spend to use reserve for fee. if (mPriorBalance < saDstAmount + std::max(uReserve, mTxn.getTransactionFee ().getNValue ())) { // Vote no. However, transaction might succeed, if applied in a different order. m_journal.trace << "Delay transaction: Insufficient funds: " << " " << mPriorBalance.getText () << " / " << (saDstAmount + uReserve).getText () << " (" << uReserve << ")"; terResult = tecUNFUNDED_PAYMENT; } else { mTxnAccount->setFieldAmount (sfBalance, mSourceBalance - saDstAmount); sleDst->setFieldAmount (sfBalance, sleDst->getFieldAmount (sfBalance) + saDstAmount); terResult = tesSUCCESS; } } std::string strToken; std::string strHuman; if (transResultInfo (terResult, strToken, strHuman)) { m_journal.trace << strToken << ": " << strHuman; } else { assert (false); } return terResult; }
TER PathCursor::reverseLiquidityForAccount () const { TER terResult = tesSUCCESS; auto const lastNodeIndex = nodeSize () - 1; auto const isFinalNode = (nodeIndex_ == lastNodeIndex); // 0 quality means none has yet been determined. std::uint64_t uRateMax = 0; // Current is allowed to redeem to next. const bool previousNodeIsAccount = !nodeIndex_ || previousNode().isAccount(); const bool nextNodeIsAccount = isFinalNode || nextNode().isAccount(); AccountID const& previousAccountID = previousNodeIsAccount ? previousNode().account_ : node().account_; AccountID const& nextAccountID = nextNodeIsAccount ? nextNode().account_ : node().account_; // Offers are always issue. // This is the quality from from the previous node to this one. auto const qualityIn = (nodeIndex_ != 0) ? quality_in (view(), node().account_, previousAccountID, node().issue_.currency) : parityRate; // And this is the quality from the next one to this one. auto const qualityOut = (nodeIndex_ != lastNodeIndex) ? quality_out (view(), node().account_, nextAccountID, node().issue_.currency) : parityRate; // For previousNodeIsAccount: // Previous account is already owed. const STAmount saPrvOwed = (previousNodeIsAccount && nodeIndex_ != 0) ? creditBalance (view(), node().account_, previousAccountID, node().issue_.currency) : STAmount (node().issue_); // The limit amount that the previous account may owe. const STAmount saPrvLimit = (previousNodeIsAccount && nodeIndex_ != 0) ? creditLimit (view(), node().account_, previousAccountID, node().issue_.currency) : STAmount (node().issue_); // Next account is owed. const STAmount saNxtOwed = (nextNodeIsAccount && nodeIndex_ != lastNodeIndex) ? creditBalance (view(), node().account_, nextAccountID, node().issue_.currency) : STAmount (node().issue_); JLOG (j_.trace()) << "reverseLiquidityForAccount>" << " nodeIndex_=" << nodeIndex_ << "/" << lastNodeIndex << " previousAccountID=" << previousAccountID << " node.account_=" << node().account_ << " nextAccountID=" << nextAccountID << " currency=" << node().issue_.currency << " qualityIn=" << qualityIn << " qualityOut=" << qualityOut << " saPrvOwed=" << saPrvOwed << " saPrvLimit=" << saPrvLimit; // Requests are computed to be the maximum flow possible. // Previous can redeem the owed IOUs it holds. const STAmount saPrvRedeemReq = (saPrvOwed > beast::zero) ? saPrvOwed : STAmount (saPrvOwed.issue ()); // Previous can issue up to limit minus whatever portion of limit already // used (not including redeemable amount) - another "maximum flow". const STAmount saPrvIssueReq = (saPrvOwed < beast::zero) ? saPrvLimit + saPrvOwed : saPrvLimit; // Precompute these values in case we have an order book. auto deliverCurrency = previousNode().saRevDeliver.getCurrency (); const STAmount saPrvDeliverReq ( {deliverCurrency, previousNode().saRevDeliver.getIssuer ()}, -1); // -1 means unlimited delivery. // Set to zero, because we're trying to hit the previous node. auto saCurRedeemAct = node().saRevRedeem.zeroed(); // Track the amount we actually redeem. auto saCurIssueAct = node().saRevIssue.zeroed(); // For !nextNodeIsAccount auto saCurDeliverAct = node().saRevDeliver.zeroed(); JLOG (j_.trace()) << "reverseLiquidityForAccount:" << " saPrvRedeemReq:" << saPrvRedeemReq << " saPrvIssueReq:" << saPrvIssueReq << " previousNode.saRevDeliver:" << previousNode().saRevDeliver << " saPrvDeliverReq:" << saPrvDeliverReq << " node.saRevRedeem:" << node().saRevRedeem << " node.saRevIssue:" << node().saRevIssue << " saNxtOwed:" << saNxtOwed; // VFALCO-FIXME this generates errors //JLOG (j_.trace()) << pathState_.getJson (); // Current redeem req can't be more than IOUs on hand. assert (!node().saRevRedeem || -saNxtOwed >= node().saRevRedeem); assert (!node().saRevIssue // If not issuing, fine. || saNxtOwed >= beast::zero // saNxtOwed >= 0: Sender not holding next IOUs, saNxtOwed < 0: // Sender holding next IOUs. || -saNxtOwed == node().saRevRedeem); // If issue req, then redeem req must consume all owed. if (nodeIndex_ == 0) { // ^ --> ACCOUNT --> account|offer // Nothing to do, there is no previous to adjust. // // TODO(tom): we could have skipped all that setup and just left // or even just never call this whole routine on nodeIndex_ = 0! } // The next four cases correspond to the table at the bottom of this Wiki // page section: https://ripple.com/wiki/Transit_Fees#Implementation else if (previousNodeIsAccount && nextNodeIsAccount) { if (isFinalNode) { // account --> ACCOUNT --> $ // Overall deliverable. const STAmount saCurWantedReq = std::min ( pathState_.outReq() - pathState_.outAct(), saPrvLimit + saPrvOwed); auto saCurWantedAct = saCurWantedReq.zeroed (); JLOG (j_.trace()) << "reverseLiquidityForAccount: account --> " << "ACCOUNT --> $ :" << " saCurWantedReq=" << saCurWantedReq; // Calculate redeem if (saPrvRedeemReq) // Previous has IOUs to redeem. { // Redeem your own IOUs at 1:1 saCurWantedAct = std::min (saPrvRedeemReq, saCurWantedReq); previousNode().saRevRedeem = saCurWantedAct; uRateMax = STAmount::uRateOne; JLOG (j_.trace()) << "reverseLiquidityForAccount: Redeem at 1:1" << " saPrvRedeemReq=" << saPrvRedeemReq << " (available) previousNode.saRevRedeem=" << previousNode().saRevRedeem << " uRateMax=" << amountFromQuality (uRateMax).getText (); } else { previousNode().saRevRedeem.clear (saPrvRedeemReq); } // Calculate issuing. previousNode().saRevIssue.clear (saPrvIssueReq); if (saCurWantedReq != saCurWantedAct // Need more. && saPrvIssueReq) // Will accept IOUs from previous. { // Rate: quality in : 1.0 // If we previously redeemed and this has a poorer rate, this // won't be included the current increment. rippleLiquidity ( rippleCalc_, qualityIn, parityRate, saPrvIssueReq, saCurWantedReq, previousNode().saRevIssue, saCurWantedAct, uRateMax); JLOG (j_.trace()) << "reverseLiquidityForAccount: Issuing: Rate: " << "quality in : 1.0" << " previousNode.saRevIssue:" << previousNode().saRevIssue << " saCurWantedAct:" << saCurWantedAct; } if (!saCurWantedAct) { // Must have processed something. terResult = tecPATH_DRY; } } else { // Not final node. // account --> ACCOUNT --> account previousNode().saRevRedeem.clear (saPrvRedeemReq); previousNode().saRevIssue.clear (saPrvIssueReq); // redeem (part 1) -> redeem if (node().saRevRedeem // Next wants IOUs redeemed from current account. && saPrvRedeemReq) // Previous has IOUs to redeem to the current account. { // TODO(tom): add English. // Rate : 1.0 : quality out - we must accept our own IOUs // as 1:1. rippleLiquidity ( rippleCalc_, parityRate, qualityOut, saPrvRedeemReq, node().saRevRedeem, previousNode().saRevRedeem, saCurRedeemAct, uRateMax); JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "Rate : 1.0 : quality out" << " previousNode.saRevRedeem:" << previousNode().saRevRedeem << " saCurRedeemAct:" << saCurRedeemAct; } // issue (part 1) -> redeem if (node().saRevRedeem != saCurRedeemAct // The current node has more IOUs to redeem. && previousNode().saRevRedeem == saPrvRedeemReq) // The previous node has no IOUs to redeem remaining, so issues. { // Rate: quality in : quality out rippleLiquidity ( rippleCalc_, qualityIn, qualityOut, saPrvIssueReq, node().saRevRedeem, previousNode().saRevIssue, saCurRedeemAct, uRateMax); JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "Rate: quality in : quality out:" << " previousNode.saRevIssue:" << previousNode().saRevIssue << " saCurRedeemAct:" << saCurRedeemAct; } // redeem (part 2) -> issue. if (node().saRevIssue // Next wants IOUs issued. // TODO(tom): this condition seems redundant. && saCurRedeemAct == node().saRevRedeem // Can only issue if completed redeeming. && previousNode().saRevRedeem != saPrvRedeemReq) // Did not complete redeeming previous IOUs. { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), saPrvRedeemReq, node().saRevIssue, previousNode().saRevRedeem, saCurIssueAct, uRateMax); JLOG (j_.debug()) << "reverseLiquidityForAccount: " << "Rate : 1.0 : transfer_rate:" << " previousNode.saRevRedeem:" << previousNode().saRevRedeem << " saCurIssueAct:" << saCurIssueAct; } // issue (part 2) -> issue if (node().saRevIssue != saCurIssueAct // Need wants more IOUs issued. && saCurRedeemAct == node().saRevRedeem // Can only issue if completed redeeming. && saPrvRedeemReq == previousNode().saRevRedeem // Previously redeemed all owed IOUs. && saPrvIssueReq) // Previous can issue. { // Rate: quality in : 1.0 rippleLiquidity ( rippleCalc_, qualityIn, parityRate, saPrvIssueReq, node().saRevIssue, previousNode().saRevIssue, saCurIssueAct, uRateMax); JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "Rate: quality in : 1.0:" << " previousNode.saRevIssue:" << previousNode().saRevIssue << " saCurIssueAct:" << saCurIssueAct; } if (!saCurRedeemAct && !saCurIssueAct) { // Did not make progress. terResult = tecPATH_DRY; } JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "^|account --> ACCOUNT --> account :" << " node.saRevRedeem:" << node().saRevRedeem << " node.saRevIssue:" << node().saRevIssue << " saPrvOwed:" << saPrvOwed << " saCurRedeemAct:" << saCurRedeemAct << " saCurIssueAct:" << saCurIssueAct; } } else if (previousNodeIsAccount && !nextNodeIsAccount) { // account --> ACCOUNT --> offer // Note: deliver is always issue as ACCOUNT is the issuer for the offer // input. JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "account --> ACCOUNT --> offer"; previousNode().saRevRedeem.clear (saPrvRedeemReq); previousNode().saRevIssue.clear (saPrvIssueReq); // We have three cases: the nxt offer can be owned by current account, // previous account or some third party account. // // Also, the current account may or may not have a redeemable balance // with the account for the next offer, so we don't yet know if we're // redeeming or issuing. // // TODO(tom): Make sure deliver was cleared, or check actual is zero. // redeem -> deliver/issue. if (saPrvOwed > beast::zero // Previous has IOUs to redeem. && node().saRevDeliver) // Need some issued. { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), saPrvRedeemReq, node().saRevDeliver, previousNode().saRevRedeem, saCurDeliverAct, uRateMax); } // issue -> deliver/issue if (saPrvRedeemReq == previousNode().saRevRedeem // Previously redeemed all owed. && node().saRevDeliver != saCurDeliverAct) // Still need some issued. { // Rate: quality in : 1.0 rippleLiquidity ( rippleCalc_, qualityIn, parityRate, saPrvIssueReq, node().saRevDeliver, previousNode().saRevIssue, saCurDeliverAct, uRateMax); } if (!saCurDeliverAct) { // Must want something. terResult = tecPATH_DRY; } JLOG (j_.trace()) << "reverseLiquidityForAccount: " << " node.saRevDeliver:" << node().saRevDeliver << " saCurDeliverAct:" << saCurDeliverAct << " saPrvOwed:" << saPrvOwed; } else if (!previousNodeIsAccount && nextNodeIsAccount) { if (isFinalNode) { // offer --> ACCOUNT --> $ // Previous is an offer, no limit: redeem own IOUs. // // This is the final node; we can't look to the right to get values; // we have to go up to get the out value for the entire path state. STAmount const& saCurWantedReq = pathState_.outReq() - pathState_.outAct(); STAmount saCurWantedAct = saCurWantedReq.zeroed(); JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "offer --> ACCOUNT --> $ :" << " saCurWantedReq:" << saCurWantedReq << " saOutAct:" << pathState_.outAct() << " saOutReq:" << pathState_.outReq(); if (saCurWantedReq <= beast::zero) { assert(false); JLOG (j_.fatal()) << "CurWantReq was not positive"; return tefEXCEPTION; } // The previous node is an offer; we are receiving our own currency. // The previous order book's entries might hold our issuances; might // not hold our issuances; might be our own offer. // // Assume the worst case, the case which costs the most to go // through, which is that it is not our own offer or our own // issuances. Later on the forward pass we may be able to do // better. // // TODO: this comment applies generally to this section - move it up // to a document. // Rate: quality in : 1.0 rippleLiquidity ( rippleCalc_, qualityIn, parityRate, saPrvDeliverReq, saCurWantedReq, previousNode().saRevDeliver, saCurWantedAct, uRateMax); if (!saCurWantedAct) { // Must have processed something. terResult = tecPATH_DRY; } JLOG (j_.trace()) << "reverseLiquidityForAccount:" << " previousNode().saRevDeliver:" << previousNode().saRevDeliver << " saPrvDeliverReq:" << saPrvDeliverReq << " saCurWantedAct:" << saCurWantedAct << " saCurWantedReq:" << saCurWantedReq; } else { // offer --> ACCOUNT --> account // Note: offer is always delivering(redeeming) as account is issuer. JLOG (j_.trace()) << "reverseLiquidityForAccount: " << "offer --> ACCOUNT --> account :" << " node.saRevRedeem:" << node().saRevRedeem << " node.saRevIssue:" << node().saRevIssue; // deliver -> redeem // TODO(tom): now we have more checking in nodeRipple, these checks // might be redundant. if (node().saRevRedeem) // Next wants us to redeem. { // cur holds IOUs from the account to the right, the nxt // account. If someone is making the current account get rid of // the nxt account's IOUs, then charge the input for quality // out. // // Rate : 1.0 : quality out rippleLiquidity ( rippleCalc_, parityRate, qualityOut, saPrvDeliverReq, node().saRevRedeem, previousNode().saRevDeliver, saCurRedeemAct, uRateMax); } // deliver -> issue. if (node().saRevRedeem == saCurRedeemAct // Can only issue if previously redeemed all. && node().saRevIssue) // Need some issued. { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), saPrvDeliverReq, node().saRevIssue, previousNode().saRevDeliver, saCurIssueAct, uRateMax); } JLOG (j_.trace()) << "reverseLiquidityForAccount:" << " saCurRedeemAct:" << saCurRedeemAct << " node.saRevRedeem:" << node().saRevRedeem << " previousNode.saRevDeliver:" << previousNode().saRevDeliver << " node.saRevIssue:" << node().saRevIssue; if (!previousNode().saRevDeliver) { // Must want something. terResult = tecPATH_DRY; } } } else { // offer --> ACCOUNT --> offer // deliver/redeem -> deliver/issue. JLOG (j_.trace()) << "reverseLiquidityForAccount: offer --> ACCOUNT --> offer"; // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), saPrvDeliverReq, node().saRevDeliver, previousNode().saRevDeliver, saCurDeliverAct, uRateMax); if (!saCurDeliverAct) { // Must want something. terResult = tecPATH_DRY; } } return terResult; }
void Transactor::calculateFee() { mFeeDue = STAmount(mEngine->getLedger()->scaleFeeLoad(calculateBaseFee())); }
void rippleLiquidity ( RippleCalc& rippleCalc, Rate const& qualityIn, Rate const& qualityOut, STAmount const& saPrvReq, // --> in limit including fees, <0 = unlimited STAmount const& saCurReq, // --> out limit STAmount& saPrvAct, // <-> in limit including achieved so far: <-- <= --> STAmount& saCurAct, // <-> out limit including achieved so far: <-- <= --> std::uint64_t& uRateMax) { JLOG (rippleCalc.j_.trace()) << "rippleLiquidity>" << " qualityIn=" << qualityIn << " qualityOut=" << qualityOut << " saPrvReq=" << saPrvReq << " saCurReq=" << saCurReq << " saPrvAct=" << saPrvAct << " saCurAct=" << saCurAct; // saCurAct was once zero in a production server. assert (saCurReq != zero); assert (saCurReq > zero); assert (saPrvReq.getCurrency () == saCurReq.getCurrency ()); assert (saPrvReq.getCurrency () == saPrvAct.getCurrency ()); assert (saPrvReq.getIssuer () == saPrvAct.getIssuer ()); const bool bPrvUnlimited = (saPrvReq < zero); // -1 means unlimited. // Unlimited stays unlimited - don't do calculations. // How much could possibly flow through the previous node? const STAmount saPrv = bPrvUnlimited ? saPrvReq : saPrvReq - saPrvAct; // How much could possibly flow through the current node? const STAmount saCur = saCurReq - saCurAct; JLOG (rippleCalc.j_.trace()) << "rippleLiquidity: " << " bPrvUnlimited=" << bPrvUnlimited << " saPrv=" << saPrv << " saCur=" << saCur; // If nothing can flow, we might as well not do any work. if (saPrv == zero || saCur == zero) return; if (qualityIn >= qualityOut) { // You're getting better quality than you asked for, so no fee. JLOG (rippleCalc.j_.trace()) << "rippleLiquidity: No fees"; // Only process if the current rate, 1:1, is not worse than the previous // rate, uRateMax - otherwise there is no flow. if (!uRateMax || STAmount::uRateOne <= uRateMax) { // Limit amount to transfer if need - the minimum of amount being // paid and the amount that's wanted. STAmount saTransfer = bPrvUnlimited ? saCur : std::min (saPrv, saCur); // In reverse, we want to propagate the limited cur to prv and set // actual cur. // // In forward, we want to propagate the limited prv to cur and set // actual prv. // // This is the actual flow. saPrvAct += saTransfer; saCurAct += saTransfer; // If no rate limit, set rate limit to avoid combining with // something with a worse rate. if (uRateMax == 0) uRateMax = STAmount::uRateOne; } } else { // If the quality is worse than the previous JLOG (rippleCalc.j_.trace()) << "rippleLiquidity: Fee"; std::uint64_t const uRate = getRate ( STAmount (qualityOut.value), STAmount (qualityIn.value)); // If the next rate is at least as good as the current rate, process. if (!uRateMax || uRate <= uRateMax) { // current actual = current request * (quality out / quality in). auto numerator = multiplyRound (saCur, qualityOut, true); // True means "round up" to get best flow. STAmount saCurIn = divideRound (numerator, qualityIn, true); JLOG (rippleCalc.j_.trace()) << "rippleLiquidity:" << " bPrvUnlimited=" << bPrvUnlimited << " saPrv=" << saPrv << " saCurIn=" << saCurIn; if (bPrvUnlimited || saCurIn <= saPrv) { // All of current. Some amount of previous. saCurAct += saCur; saPrvAct += saCurIn; JLOG (rippleCalc.j_.trace()) << "rippleLiquidity:3c:" << " saCurReq=" << saCurReq << " saPrvAct=" << saPrvAct; } else { // There wasn't enough money to start with - so given the // limited input, how much could we deliver? // current actual = previous request // * (quality in / quality out). // This is inverted compared to the code above because we're // going the other way auto numerator = multiplyRound (saPrv, qualityIn, saCur.issue(), true); // A part of current. All of previous. (Cur is the driver // variable.) STAmount saCurOut = divideRound (numerator, qualityOut, saCur.issue(), true); JLOG (rippleCalc.j_.trace()) << "rippleLiquidity:4: saCurReq=" << saCurReq; saCurAct += saCurOut; saPrvAct = saPrvReq; } if (!uRateMax) uRateMax = uRate; } } JLOG (rippleCalc.j_.trace()) << "rippleLiquidity<" << " qualityIn=" << qualityIn << " qualityOut=" << qualityOut << " saPrvReq=" << saPrvReq << " saCurReq=" << saCurReq << " saPrvAct=" << saPrvAct << " saCurAct=" << saCurAct; }
TER doApply () override { // Divvy if source or destination is non-native or if there are paths. std::uint32_t const uTxFlags = mTxn.getFlags (); bool const partialPaymentAllowed = uTxFlags & tfPartialPayment; bool const limitQuality = uTxFlags & tfLimitQuality; bool const defaultPathsAllowed = !(uTxFlags & tfNoDivvyDirect); bool const bPaths = mTxn.isFieldPresent (sfPaths); bool const bMax = mTxn.isFieldPresent (sfSendMax); AccountID const uDstAccountID (mTxn.getFieldAccount160 (sfDestination)); STAmount const saDstAmount (mTxn.getFieldAmount (sfAmount)); STAmount maxSourceAmount; if (bMax) maxSourceAmount = mTxn.getFieldAmount (sfSendMax); else if (saDstAmount.native ()) maxSourceAmount = saDstAmount; else maxSourceAmount = STAmount ( {saDstAmount.getCurrency (), mTxnAccountID}, saDstAmount.mantissa(), saDstAmount.exponent (), saDstAmount < zero); m_journal.trace << "maxSourceAmount=" << maxSourceAmount.getFullText () << " saDstAmount=" << saDstAmount.getFullText (); // Open a ledger for editing. auto const index = getAccountRootIndex (uDstAccountID); SLE::pointer sleDst (mEngine->view().entryCache (ltACCOUNT_ROOT, index)); if (!sleDst) { // Destination account does not exist. if (!saDstAmount.native ()) { m_journal.trace << "Delay transaction: Destination account does not exist."; // Another transaction could create the account and then this // transaction would succeed. return tecNO_DST; } else if (mParams & tapOPEN_LEDGER && partialPaymentAllowed) { // You cannot fund an account with a partial payment. // Make retry work smaller, by rejecting this. m_journal.trace << "Delay transaction: Partial payment not allowed to create account."; // Another transaction could create the account and then this // transaction would succeed. return telNO_DST_PARTIAL; } else if (saDstAmount < STAmount (mEngine->getLedger ()->getReserve (0))) { // getReserve() is the minimum amount that an account can have. // Reserve is not scaled by load. m_journal.trace << "Delay transaction: Destination account does not exist. " << "Insufficent payment to create account."; // TODO: dedupe // Another transaction could create the account and then this // transaction would succeed. return tecNO_DST_INSUF_XDV; } // Create the account. sleDst = std::make_shared<SLE>(ltACCOUNT_ROOT, getAccountRootIndex (uDstAccountID)); sleDst->setFieldAccount (sfAccount, uDstAccountID); sleDst->setFieldU32 (sfSequence, 1); mEngine->view().entryCreate(sleDst); } else if ((sleDst->getFlags () & lsfRequireDestTag) && !mTxn.isFieldPresent (sfDestinationTag)) { // The tag is basically account-specific information we don't // understand, but we can require someone to fill it in. // We didn't make this test for a newly-formed account because there's // no way for this field to be set. m_journal.trace << "Malformed transaction: DestinationTag required."; return tecDST_TAG_NEEDED; } else { // Tell the engine that we are intending to change the the destination // account. The source account gets always charged a fee so it's always // marked as modified. mEngine->view().entryModify (sleDst); } TER terResult; bool const bDivvy = bPaths || bMax || !saDstAmount.native (); // XXX Should bMax be sufficient to imply divvy? if (bDivvy) { // Divvy payment with at least one intermediate step and uses // transitive balances. // Copy paths into an editable class. STPathSet spsPaths = mTxn.getFieldPathSet (sfPaths); try { path::DivvyCalc::Input rcInput; rcInput.partialPaymentAllowed = partialPaymentAllowed; rcInput.defaultPathsAllowed = defaultPathsAllowed; rcInput.limitQuality = limitQuality; rcInput.deleteUnfundedOffers = true; rcInput.isLedgerOpen = static_cast<bool>(mParams & tapOPEN_LEDGER); bool pathTooBig = spsPaths.size () > MaxPathSize; for (auto const& path : spsPaths) if (path.size () > MaxPathLength) pathTooBig = true; if (rcInput.isLedgerOpen && pathTooBig) { terResult = telBAD_PATH_COUNT; // Too many paths for proposed ledger. } else { path::DivvyCalc::Output rc; { ScopedDeferCredits g (mEngine->view ()); rc = path::DivvyCalc::divvyCalculate ( mEngine->view (), maxSourceAmount, saDstAmount, uDstAccountID, mTxnAccountID, spsPaths, &rcInput); } // TODO: is this right? If the amount is the correct amount, was // the delivered amount previously set? if (rc.result () == tesSUCCESS && rc.actualAmountOut != saDstAmount) mEngine->view ().setDeliveredAmount (rc.actualAmountOut); terResult = rc.result (); } // TODO(tom): what's going on here? if (isTerRetry (terResult)) terResult = tecPATH_DRY; } catch (std::exception const& e) { m_journal.trace << "Caught throw: " << e.what (); terResult = tefEXCEPTION; } } else { // Direct XDV payment. // uOwnerCount is the number of entries in this legder for this // account that require a reserve. auto const uOwnerCount = mTxnAccount->getFieldU32 (sfOwnerCount); // This is the total reserve in drops. std::uint64_t const uReserve = mEngine->getLedger ()->getReserve (uOwnerCount); // mPriorBalance is the balance on the sending account BEFORE the // fees were charged. We want to make sure we have enough reserve // to send. Allow final spend to use reserve for fee. auto const mmm = std::max(mTxn.getTransactionFee (), STAmount (uReserve)); if (mPriorBalance < saDstAmount + mmm) { // Vote no. However the transaction might succeed, if applied in // a different order. m_journal.trace << "Delay transaction: Insufficient funds: " << " " << mPriorBalance.getText () << " / " << (saDstAmount + mmm).getText () << " (" << uReserve << ")"; terResult = tecUNFUNDED_PAYMENT; } else { // The source account does have enough money, so do the // arithmetic for the transfer and make the ledger change. mTxnAccount->setFieldAmount (sfBalance, mSourceBalance - saDstAmount); sleDst->setFieldAmount (sfBalance, sleDst->getFieldAmount (sfBalance) + saDstAmount); // Re-arm the password change fee if we can and need to. if ((sleDst->getFlags () & lsfPasswordSpent)) sleDst->clearFlag (lsfPasswordSpent); terResult = tesSUCCESS; } } std::string strToken; std::string strHuman; if (transResultInfo (terResult, strToken, strHuman)) { m_journal.trace << strToken << ": " << strHuman; } else { assert (false); } return terResult; }
// 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; }
// The reverse pass has been narrowing by credit available and inflating by fees // as it worked backwards. Now, for the current account node, take the actual // amount from previous and adjust forward balances. // // Perform balance adjustments between previous and current node. // - The previous node: specifies what to push through to current. // - All of previous output is consumed. // // Then, compute current node's output for next node. // - Current node: specify what to push through to next. // - Output to next node is computed as input minus quality or transfer fee. // - If next node is an offer and output is non-XRP then we are the issuer and // do not need to push funds. // - If next node is an offer and output is XRP then we need to deliver funds to // limbo. TER PathCursor::forwardLiquidityForAccount () const { TER resultCode = tesSUCCESS; auto const lastNodeIndex = pathState_.nodes().size () - 1; auto viewJ = rippleCalc_.logs_.journal ("View"); std::uint64_t uRateMax = 0; AccountID const& previousAccountID = previousNode().isAccount() ? previousNode().account_ : node().account_; // Offers are always issue. AccountID const& nextAccountID = nextNode().isAccount() ? nextNode().account_ : node().account_; auto const qualityIn = nodeIndex_ ? quality_in (view(), node().account_, previousAccountID, node().issue_.currency) : parityRate; auto const qualityOut = (nodeIndex_ == lastNodeIndex) ? quality_out (view(), node().account_, nextAccountID, node().issue_.currency) : parityRate; // When looking backward (prv) for req we care about what we just // calculated: use fwd. // When looking forward (cur) for req we care about what was desired: use // rev. // For nextNode().isAccount() auto saPrvRedeemAct = previousNode().saFwdRedeem.zeroed(); auto saPrvIssueAct = previousNode().saFwdIssue.zeroed(); // For !previousNode().isAccount() auto saPrvDeliverAct = previousNode().saFwdDeliver.zeroed (); JLOG (j_.trace()) << "forwardLiquidityForAccount> " << "nodeIndex_=" << nodeIndex_ << "/" << lastNodeIndex << " previousNode.saFwdRedeem:" << previousNode().saFwdRedeem << " saPrvIssueReq:" << previousNode().saFwdIssue << " previousNode.saFwdDeliver:" << previousNode().saFwdDeliver << " node.saRevRedeem:" << node().saRevRedeem << " node.saRevIssue:" << node().saRevIssue << " node.saRevDeliver:" << node().saRevDeliver; // Ripple through account. if (previousNode().isAccount() && nextNode().isAccount()) { // Next is an account, must be rippling. if (!nodeIndex_) { // ^ --> ACCOUNT --> account // For the first node, calculate amount to ripple based on what is // available. node().saFwdRedeem = node().saRevRedeem; if (pathState_.inReq() >= beast::zero) { // Limit by send max. node().saFwdRedeem = std::min ( node().saFwdRedeem, pathState_.inReq() - pathState_.inAct()); } pathState_.setInPass (node().saFwdRedeem); node().saFwdIssue = node().saFwdRedeem == node().saRevRedeem // Fully redeemed. ? node().saRevIssue : STAmount (node().saRevIssue); if (node().saFwdIssue && pathState_.inReq() >= beast::zero) { // Limit by send max. node().saFwdIssue = std::min ( node().saFwdIssue, pathState_.inReq() - pathState_.inAct() - node().saFwdRedeem); } pathState_.setInPass (pathState_.inPass() + node().saFwdIssue); JLOG (j_.trace()) << "forwardLiquidityForAccount: ^ --> " << "ACCOUNT --> account :" << " saInReq=" << pathState_.inReq() << " saInAct=" << pathState_.inAct() << " node.saFwdRedeem:" << node().saFwdRedeem << " node.saRevIssue:" << node().saRevIssue << " node.saFwdIssue:" << node().saFwdIssue << " pathState_.saInPass:"******"forwardLiquidityForAccount: account --> " << "ACCOUNT --> $ :" << " previousAccountID=" << to_string (previousAccountID) << " node.account_=" << to_string (node().account_) << " previousNode.saFwdRedeem:" << previousNode().saFwdRedeem << " previousNode.saFwdIssue:" << previousNode().saFwdIssue; // Last node. Accept all funds. Calculate amount actually to credit. auto& saCurReceive = pathState_.outPass(); STAmount saIssueCrd = qualityIn >= parityRate ? previousNode().saFwdIssue // No fee. : multiplyRound ( previousNode().saFwdIssue, qualityIn, true); // Amount to credit. // Amount to credit. Credit for less than received as a surcharge. pathState_.setOutPass (previousNode().saFwdRedeem + saIssueCrd); if (saCurReceive) { // Actually receive. resultCode = rippleCredit(view(), previousAccountID, node().account_, previousNode().saFwdRedeem + previousNode().saFwdIssue, false, viewJ); } else { // After applying quality, total payment was microscopic. resultCode = tecPATH_DRY; } } else { // account --> ACCOUNT --> account JLOG (j_.trace()) << "forwardLiquidityForAccount: account --> " << "ACCOUNT --> account"; node().saFwdRedeem.clear (node().saRevRedeem); node().saFwdIssue.clear (node().saRevIssue); // Previous redeem part 1: redeem -> redeem if (previousNode().saFwdRedeem && node().saRevRedeem) // Previous wants to redeem. { // Rate : 1.0 : quality out rippleLiquidity ( rippleCalc_, parityRate, qualityOut, previousNode().saFwdRedeem, node().saRevRedeem, saPrvRedeemAct, node().saFwdRedeem, uRateMax); } // Previous issue part 1: issue -> redeem if (previousNode().saFwdIssue != saPrvIssueAct // Previous wants to issue. && node().saRevRedeem != node().saFwdRedeem) // Current has more to redeem to next. { // Rate: quality in : quality out rippleLiquidity ( rippleCalc_, qualityIn, qualityOut, previousNode().saFwdIssue, node().saRevRedeem, saPrvIssueAct, node().saFwdRedeem, uRateMax); } // Previous redeem part 2: redeem -> issue. if (previousNode().saFwdRedeem != saPrvRedeemAct // Previous still wants to redeem. && node().saRevRedeem == node().saFwdRedeem // Current redeeming is done can issue. && node().saRevIssue) // Current wants to issue. { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), previousNode().saFwdRedeem, node().saRevIssue, saPrvRedeemAct, node().saFwdIssue, uRateMax); } // Previous issue part 2 : issue -> issue if (previousNode().saFwdIssue != saPrvIssueAct // Previous wants to issue. && node().saRevRedeem == node().saFwdRedeem // Current redeeming is done can issue. && node().saRevIssue) // Current wants to issue. { // Rate: quality in : 1.0 rippleLiquidity ( rippleCalc_, qualityIn, parityRate, previousNode().saFwdIssue, node().saRevIssue, saPrvIssueAct, node().saFwdIssue, uRateMax); } STAmount saProvide = node().saFwdRedeem + node().saFwdIssue; // Adjust prv --> cur balance : take all inbound resultCode = saProvide ? rippleCredit(view(), previousAccountID, node().account_, previousNode().saFwdRedeem + previousNode().saFwdIssue, false, viewJ) : tecPATH_DRY; } } else if (previousNode().isAccount() && !nextNode().isAccount()) { // Current account is issuer to next offer. // Determine deliver to offer amount. // Don't adjust outbound balances- keep funds with issuer as limbo. // If issuer hold's an offer owners inbound IOUs, there is no fee and // redeem/issue will transparently happen. if (nodeIndex_) { // Non-XRP, current node is the issuer. JLOG (j_.trace()) << "forwardLiquidityForAccount: account --> " << "ACCOUNT --> offer"; node().saFwdDeliver.clear (node().saRevDeliver); // redeem -> issue/deliver. // Previous wants to redeem. // Current is issuing to an offer so leave funds in account as // "limbo". if (previousNode().saFwdRedeem) // Previous wants to redeem. { // Rate : 1.0 : transfer_rate // XXX Is having the transfer rate here correct? rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), previousNode().saFwdRedeem, node().saRevDeliver, saPrvRedeemAct, node().saFwdDeliver, uRateMax); } // issue -> issue/deliver if (previousNode().saFwdRedeem == saPrvRedeemAct // Previous done redeeming: Previous has no IOUs. && previousNode().saFwdIssue) // Previous wants to issue. To next must be ok. { // Rate: quality in : 1.0 rippleLiquidity ( rippleCalc_, qualityIn, parityRate, previousNode().saFwdIssue, node().saRevDeliver, saPrvIssueAct, node().saFwdDeliver, uRateMax); } // Adjust prv --> cur balance : take all inbound resultCode = node().saFwdDeliver ? rippleCredit(view(), previousAccountID, node().account_, previousNode().saFwdRedeem + previousNode().saFwdIssue, false, viewJ) : tecPATH_DRY; // Didn't actually deliver anything. } else { // Delivering amount requested from downstream. node().saFwdDeliver = node().saRevDeliver; // If limited, then limit by send max and available. if (pathState_.inReq() >= beast::zero) { // Limit by send max. node().saFwdDeliver = std::min ( node().saFwdDeliver, pathState_.inReq() - pathState_.inAct()); // Limit XRP by available. No limit for non-XRP as issuer. if (isXRP (node().issue_)) node().saFwdDeliver = std::min ( node().saFwdDeliver, accountHolds(view(), node().account_, xrpCurrency(), xrpAccount(), fhIGNORE_FREEZE, viewJ)); // XRP can't be frozen } // Record amount sent for pass. pathState_.setInPass (node().saFwdDeliver); if (!node().saFwdDeliver) { resultCode = tecPATH_DRY; } else if (!isXRP (node().issue_)) { // Non-XRP, current node is the issuer. // We could be delivering to multiple accounts, so we don't know // which ripple balance will be adjusted. Assume just issuing. JLOG (j_.trace()) << "forwardLiquidityForAccount: ^ --> " << "ACCOUNT -- !XRP --> offer"; // As the issuer, would only issue. // Don't need to actually deliver. As from delivering leave in // the issuer as limbo. } else { JLOG (j_.trace()) << "forwardLiquidityForAccount: ^ --> " << "ACCOUNT -- XRP --> offer"; // Deliver XRP to limbo. resultCode = accountSend(view(), node().account_, xrpAccount(), node().saFwdDeliver, viewJ); } } } else if (!previousNode().isAccount() && nextNode().isAccount()) { if (nodeIndex_ == lastNodeIndex) { // offer --> ACCOUNT --> $ JLOG (j_.trace()) << "forwardLiquidityForAccount: offer --> " << "ACCOUNT --> $ : " << previousNode().saFwdDeliver; // Amount to credit. pathState_.setOutPass (previousNode().saFwdDeliver); // No income balance adjustments necessary. The paying side inside // the offer paid to this account. } else { // offer --> ACCOUNT --> account JLOG (j_.trace()) << "forwardLiquidityForAccount: offer --> " << "ACCOUNT --> account"; node().saFwdRedeem.clear (node().saRevRedeem); node().saFwdIssue.clear (node().saRevIssue); // deliver -> redeem if (previousNode().saFwdDeliver && node().saRevRedeem) // Previous wants to deliver and can current redeem. { // Rate : 1.0 : quality out rippleLiquidity ( rippleCalc_, parityRate, qualityOut, previousNode().saFwdDeliver, node().saRevRedeem, saPrvDeliverAct, node().saFwdRedeem, uRateMax); } // deliver -> issue // Wants to redeem and current would and can issue. if (previousNode().saFwdDeliver != saPrvDeliverAct // Previous still wants to deliver. && node().saRevRedeem == node().saFwdRedeem // Current has more to redeem to next. && node().saRevIssue) // Current wants issue. { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), previousNode().saFwdDeliver, node().saRevIssue, saPrvDeliverAct, node().saFwdIssue, uRateMax); } // No income balance adjustments necessary. The paying side inside // the offer paid and the next link will receive. STAmount saProvide = node().saFwdRedeem + node().saFwdIssue; if (!saProvide) resultCode = tecPATH_DRY; } } else { // offer --> ACCOUNT --> offer // deliver/redeem -> deliver/issue. JLOG (j_.trace()) << "forwardLiquidityForAccount: offer --> ACCOUNT --> offer"; node().saFwdDeliver.clear (node().saRevDeliver); if (previousNode().saFwdDeliver && node().saRevDeliver) { // Rate : 1.0 : transfer_rate rippleLiquidity ( rippleCalc_, parityRate, transferRate (view(), node().account_), previousNode().saFwdDeliver, node().saRevDeliver, saPrvDeliverAct, node().saFwdDeliver, uRateMax); } // No income balance adjustments necessary. The paying side inside the // offer paid and the next link will receive. if (!node().saFwdDeliver) resultCode = tecPATH_DRY; } return resultCode; }
TER doApply () override { // Ripple if source or destination is non-native or if there are paths. std::uint32_t const uTxFlags = mTxn.getFlags (); bool const partialPaymentAllowed = uTxFlags & tfPartialPayment; bool const limitQuality = uTxFlags & tfLimitQuality; bool const defaultPathsAllowed = !(uTxFlags & tfNoRippleDirect); bool const bPaths = mTxn.isFieldPresent (sfPaths); bool const bMax = mTxn.isFieldPresent (sfSendMax); Account const uDstAccountID (mTxn.getFieldAccount160 (sfDestination)); STAmount const saDstAmount (mTxn.getFieldAmount (sfAmount)); STAmount maxSourceAmount; if (bMax) maxSourceAmount = mTxn.getFieldAmount (sfSendMax); else if (saDstAmount.isNative ()) maxSourceAmount = saDstAmount; else maxSourceAmount = STAmount ( {saDstAmount.getCurrency (), mTxnAccountID}, saDstAmount.mantissa(), saDstAmount.exponent (), saDstAmount < zero); auto const& uSrcCurrency = maxSourceAmount.getCurrency (); auto const& uDstCurrency = saDstAmount.getCurrency (); // isZero() is XRP. FIX! bool const bXRPDirect = uSrcCurrency.isZero () && uDstCurrency.isZero (); m_journal.trace << "maxSourceAmount=" << maxSourceAmount.getFullText () << " saDstAmount=" << saDstAmount.getFullText (); if (!isLegalNet (saDstAmount) || !isLegalNet (maxSourceAmount)) return temBAD_AMOUNT; if (uTxFlags & tfPaymentMask) { m_journal.trace << "Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } else if (!uDstAccountID) { m_journal.trace << "Malformed transaction: Payment destination account not specified."; return temDST_NEEDED; } else if (bMax && maxSourceAmount <= zero) { m_journal.trace << "Malformed transaction: bad max amount: " << maxSourceAmount.getFullText (); return temBAD_AMOUNT; } else if (saDstAmount <= zero) { m_journal.trace << "Malformed transaction: bad dst amount: " << saDstAmount.getFullText (); return temBAD_AMOUNT; } else if (badCurrency() == uSrcCurrency || badCurrency() == uDstCurrency) { m_journal.trace << "Malformed transaction: Bad currency."; return temBAD_CURRENCY; } else if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) { // You're signing yourself a payment. // If bPaths is true, you might be trying some arbitrage. m_journal.trace << "Malformed transaction: Redundant transaction:" << " src=" << to_string (mTxnAccountID) << " dst=" << to_string (uDstAccountID) << " src_cur=" << to_string (uSrcCurrency) << " dst_cur=" << to_string (uDstCurrency); return temREDUNDANT; } else if (bMax && maxSourceAmount == saDstAmount && maxSourceAmount.getCurrency () == saDstAmount.getCurrency ()) { // Consistent but redundant transaction. m_journal.trace << "Malformed transaction: Redundant SendMax."; return temREDUNDANT_SEND_MAX; } else if (bXRPDirect && bMax) { // Consistent but redundant transaction. m_journal.trace << "Malformed transaction: SendMax specified for XRP to XRP."; return temBAD_SEND_XRP_MAX; } else if (bXRPDirect && bPaths) { // XRP is sent without paths. m_journal.trace << "Malformed transaction: Paths specified for XRP to XRP."; return temBAD_SEND_XRP_PATHS; } else if (bXRPDirect && partialPaymentAllowed) { // Consistent but redundant transaction. m_journal.trace << "Malformed transaction: Partial payment specified for XRP to XRP."; return temBAD_SEND_XRP_PARTIAL; } else if (bXRPDirect && limitQuality) { // Consistent but redundant transaction. m_journal.trace << "Malformed transaction: Limit quality specified for XRP to XRP."; return temBAD_SEND_XRP_LIMIT; } else if (bXRPDirect && !defaultPathsAllowed) { // Consistent but redundant transaction. m_journal.trace << "Malformed transaction: No ripple direct specified for XRP to XRP."; return temBAD_SEND_XRP_NO_DIRECT; } // // Open a ledger for editing. auto const index = getAccountRootIndex (uDstAccountID); SLE::pointer sleDst (mEngine->entryCache (ltACCOUNT_ROOT, index)); if (!sleDst) { // Destination account does not exist. if (!saDstAmount.isNative ()) { m_journal.trace << "Delay transaction: Destination account does not exist."; // Another transaction could create the account and then this // transaction would succeed. return tecNO_DST; } else if (mParams & tapOPEN_LEDGER && partialPaymentAllowed) { // You cannot fund an account with a partial payment. // Make retry work smaller, by rejecting this. m_journal.trace << "Delay transaction: Partial payment not allowed to create account."; // Another transaction could create the account and then this // transaction would succeed. return telNO_DST_PARTIAL; } else if (saDstAmount.getNValue () < mEngine->getLedger ()->getReserve (0)) { // getReserve() is the minimum amount that an account can have. // Reserve is not scaled by load. m_journal.trace << "Delay transaction: Destination account does not exist. " << "Insufficent payment to create account."; // TODO: dedupe // Another transaction could create the account and then this // transaction would succeed. return tecNO_DST_INSUF_XRP; } // Create the account. auto const newIndex = getAccountRootIndex (uDstAccountID); sleDst = mEngine->entryCreate (ltACCOUNT_ROOT, newIndex); sleDst->setFieldAccount (sfAccount, uDstAccountID); sleDst->setFieldU32 (sfSequence, 1); } else if ((sleDst->getFlags () & lsfRequireDestTag) && !mTxn.isFieldPresent (sfDestinationTag)) { // The tag is basically account-specific information we don't // understand, but we can require someone to fill it in. // We didn't make this test for a newly-formed account because there's // no way for this field to be set. m_journal.trace << "Malformed transaction: DestinationTag required."; return tefDST_TAG_NEEDED; } else { // Tell the engine that we are intending to change the the destination // account. The source account gets always charged a fee so it's always // marked as modified. mEngine->entryModify (sleDst); } TER terResult; bool const bRipple = bPaths || bMax || !saDstAmount.isNative (); // XXX Should bMax be sufficient to imply ripple? if (bRipple) { // Ripple payment with at least one intermediate step and uses // transitive balances. // Copy paths into an editable class. STPathSet spsPaths = mTxn.getFieldPathSet (sfPaths); try { path::RippleCalc::Input rcInput; rcInput.partialPaymentAllowed = partialPaymentAllowed; rcInput.defaultPathsAllowed = defaultPathsAllowed; rcInput.limitQuality = limitQuality; rcInput.deleteUnfundedOffers = true; rcInput.isLedgerOpen = static_cast<bool>(mParams & tapOPEN_LEDGER); bool pathTooBig = spsPaths.size () > MaxPathSize; for (auto const& path : spsPaths) if (path.size () > MaxPathLength) pathTooBig = true; if (rcInput.isLedgerOpen && pathTooBig) { terResult = telBAD_PATH_COUNT; // Too many paths for proposed ledger. } else { auto rc = path::RippleCalc::rippleCalculate ( mEngine->view (), maxSourceAmount, saDstAmount, uDstAccountID, mTxnAccountID, spsPaths, &rcInput); // TODO: is this right? If the amount is the correct amount, was // the delivered amount previously set? if (rc.result () == tesSUCCESS && rc.actualAmountOut != saDstAmount) mEngine->view ().setDeliveredAmount (rc.actualAmountOut); terResult = rc.result (); } // TODO(tom): what's going on here? if (isTerRetry (terResult)) terResult = tecPATH_DRY; } catch (std::exception const& e) { m_journal.trace << "Caught throw: " << e.what (); terResult = tefEXCEPTION; } } else { // Direct XRP payment. // uOwnerCount is the number of entries in this legder for this account // that require a reserve. std::uint32_t const uOwnerCount (mTxnAccount->getFieldU32 (sfOwnerCount)); // This is the total reserve in drops. // TODO(tom): there should be a class for this. std::uint64_t const uReserve (mEngine->getLedger ()->getReserve (uOwnerCount)); // mPriorBalance is the balance on the sending account BEFORE the fees were charged. // // Make sure have enough reserve to send. Allow final spend to use // reserve for fee. auto const mmm = std::max(uReserve, mTxn.getTransactionFee ().getNValue ()); if (mPriorBalance < saDstAmount + mmm) { // Vote no. // However, transaction might succeed, if applied in a different order. m_journal.trace << "Delay transaction: Insufficient funds: " << " " << mPriorBalance.getText () << " / " << (saDstAmount + uReserve).getText () << " (" << uReserve << ")"; terResult = tecUNFUNDED_PAYMENT; } else { // The source account does have enough money, so do the arithmetic // for the transfer and make the ledger change. mTxnAccount->setFieldAmount (sfBalance, mSourceBalance - saDstAmount); sleDst->setFieldAmount (sfBalance, sleDst->getFieldAmount (sfBalance) + saDstAmount); // Re-arm the password change fee if we can and need to. if ((sleDst->getFlags () & lsfPasswordSpent)) sleDst->clearFlag (lsfPasswordSpent); terResult = tesSUCCESS; } } std::string strToken; std::string strHuman; if (transResultInfo (terResult, strToken, strHuman)) { m_journal.trace << strToken << ": " << strHuman; } else { assert (false); } return terResult; }
TER preCheck () override { std::uint32_t const uTxFlags = mTxn.getFlags (); if (uTxFlags & tfPaymentMask) { m_journal.trace << "Malformed transaction: " << "Invalid flags set."; return temINVALID_FLAG; } bool const partialPaymentAllowed = uTxFlags & tfPartialPayment; bool const limitQuality = uTxFlags & tfLimitQuality; bool const defaultPathsAllowed = !(uTxFlags & tfNoDivvyDirect); bool const bPaths = mTxn.isFieldPresent (sfPaths); bool const bMax = mTxn.isFieldPresent (sfSendMax); STAmount const saDstAmount (mTxn.getFieldAmount (sfAmount)); STAmount maxSourceAmount; if (bMax) maxSourceAmount = mTxn.getFieldAmount (sfSendMax); else if (saDstAmount.native ()) maxSourceAmount = saDstAmount; else maxSourceAmount = STAmount ( { saDstAmount.getCurrency (), mTxnAccountID }, saDstAmount.mantissa(), saDstAmount.exponent (), saDstAmount < zero); auto const& uSrcCurrency = maxSourceAmount.getCurrency (); auto const& uDstCurrency = saDstAmount.getCurrency (); // isZero() is XDV. FIX! bool const bXDVDirect = uSrcCurrency.isZero () && uDstCurrency.isZero (); if (!isLegalNet (saDstAmount) || !isLegalNet (maxSourceAmount)) return temBAD_AMOUNT; AccountID const uDstAccountID (mTxn.getFieldAccount160 (sfDestination)); if (!uDstAccountID) { m_journal.trace << "Malformed transaction: " << "Payment destination account not specified."; return temDST_NEEDED; } if (bMax && maxSourceAmount <= zero) { m_journal.trace << "Malformed transaction: " << "bad max amount: " << maxSourceAmount.getFullText (); return temBAD_AMOUNT; } if (saDstAmount <= zero) { m_journal.trace << "Malformed transaction: "<< "bad dst amount: " << saDstAmount.getFullText (); return temBAD_AMOUNT; } if (badCurrency() == uSrcCurrency || badCurrency() == uDstCurrency) { m_journal.trace <<"Malformed transaction: " << "Bad currency."; return temBAD_CURRENCY; } if (mTxnAccountID == uDstAccountID && uSrcCurrency == uDstCurrency && !bPaths) { // You're signing yourself a payment. // If bPaths is true, you might be trying some arbitrage. m_journal.trace << "Malformed transaction: " << "Redundant payment from " << to_string (mTxnAccountID) << " to self without path for " << to_string (uDstCurrency); return temREDUNDANT; } if (bXDVDirect && bMax) { // Consistent but redundant transaction. m_journal.trace << "Malformed transaction: " << "SendMax specified for XDV to XDV."; return temBAD_SEND_XDV_MAX; } if (bXDVDirect && bPaths) { // XDV is sent without paths. m_journal.trace << "Malformed transaction: " << "Paths specified for XDV to XDV."; return temBAD_SEND_XDV_PATHS; } if (bXDVDirect && partialPaymentAllowed) { // Consistent but redundant transaction. m_journal.trace << "Malformed transaction: " << "Partial payment specified for XDV to XDV."; return temBAD_SEND_XDV_PARTIAL; } if (bXDVDirect && limitQuality) { // Consistent but redundant transaction. m_journal.trace << "Malformed transaction: " << "Limit quality specified for XDV to XDV."; return temBAD_SEND_XDV_LIMIT; } if (bXDVDirect && !defaultPathsAllowed) { // Consistent but redundant transaction. m_journal.trace << "Malformed transaction: " << "No divvy direct specified for XDV to XDV."; return temBAD_SEND_XDV_NO_DIRECT; } return Transactor::preCheck (); }
void Transactor::calculateFee () { mFeeDue = STAmount (mEngine->getLedger ()->scaleFeeLoad (calculateBaseFee (), isSetBit (mParams, tapADMIN))); }
TER computeReverseLiquidityForAccount ( RippleCalc& rippleCalc, const unsigned int nodeIndex, PathState& pathState, const bool bMultiQuality) { TER terResult = tesSUCCESS; auto const lastNodeIndex = pathState.nodes().size () - 1; auto const isFinalNode = (nodeIndex == lastNodeIndex); // 0 quality means none has yet been determined. std::uint64_t uRateMax = 0; auto& previousNode = pathState.nodes()[nodeIndex ? nodeIndex - 1 : 0]; auto& node = pathState.nodes()[nodeIndex]; auto& nextNode = pathState.nodes()[isFinalNode ? lastNodeIndex : nodeIndex + 1]; // Current is allowed to redeem to next. const bool previousNodeIsAccount = !nodeIndex || previousNode.isAccount(); const bool nextNodeIsAccount = isFinalNode || nextNode.isAccount(); Account const& previousAccountID = previousNodeIsAccount ? previousNode.account_ : node.account_; Account const& nextAccountID = nextNodeIsAccount ? nextNode.account_ : node.account_; // Offers are always issue. // This is the quality from from the previous node to this one. const std::uint32_t uQualityIn = (nodeIndex != 0) ? rippleCalc.mActiveLedger.rippleQualityIn ( node.account_, previousAccountID, node.currency_) : QUALITY_ONE; // And this is the quality from the next one to this one. const std::uint32_t uQualityOut = (nodeIndex != lastNodeIndex) ? rippleCalc.mActiveLedger.rippleQualityOut ( node.account_, nextAccountID, node.currency_) : QUALITY_ONE; // For previousNodeIsAccount: // Previous account is already owed. const STAmount saPrvOwed = (previousNodeIsAccount && nodeIndex != 0) ? rippleCalc.mActiveLedger.rippleOwed ( node.account_, previousAccountID, node.currency_) : STAmount ({node.currency_, node.account_}); // The limit amount that the previous account may owe. const STAmount saPrvLimit = (previousNodeIsAccount && nodeIndex != 0) ? rippleCalc.mActiveLedger.rippleLimit ( node.account_, previousAccountID, node.currency_) : STAmount ({node.currency_, node.account_}); // Next account is owed. const STAmount saNxtOwed = (nextNodeIsAccount && nodeIndex != lastNodeIndex) ? rippleCalc.mActiveLedger.rippleOwed ( node.account_, nextAccountID, node.currency_) : STAmount ({node.currency_, node.account_}); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount>" << " nodeIndex=%d/%d" << nodeIndex << "/" << lastNodeIndex << " previousAccountID=" << previousAccountID << " node.account_=" << node.account_ << " nextAccountID=" << nextAccountID << " currency_=" << node.currency_ << " uQualityIn=" << uQualityIn << " uQualityOut=" << uQualityOut << " saPrvOwed=" << saPrvOwed << " saPrvLimit=" << saPrvLimit; // Requests are computed to be the maximum flow possible. // Previous can redeem the owed IOUs it holds. const STAmount saPrvRedeemReq = (saPrvOwed > zero) ? saPrvOwed : STAmount (saPrvOwed.issue ()); // This is the amount we're actually going to be setting for the previous // node. STAmount& saPrvRedeemAct = previousNode.saRevRedeem; // Previous can issue up to limit minus whatever portion of limit already // used (not including redeemable amount) - another "maximum flow". const STAmount saPrvIssueReq = (saPrvOwed < zero) ? saPrvLimit + saPrvOwed : saPrvLimit; STAmount& saPrvIssueAct = previousNode.saRevIssue; // Precompute these values in case we have an order book. auto deliverCurrency = previousNode.saRevDeliver.getCurrency (); const STAmount saPrvDeliverReq ( {deliverCurrency, previousNode.saRevDeliver.getIssuer ()}, -1); // Unlimited delivery. STAmount& saPrvDeliverAct = previousNode.saRevDeliver; // For nextNodeIsAccount const STAmount& saCurRedeemReq = node.saRevRedeem; // Set to zero, because we're trying to hit the previous node. auto saCurRedeemAct = saCurRedeemReq.zeroed(); const STAmount& saCurIssueReq = node.saRevIssue; // Track the amount we actually redeem. auto saCurIssueAct = saCurIssueReq.zeroed(); // For !nextNodeIsAccount const STAmount& saCurDeliverReq = node.saRevDeliver; auto saCurDeliverAct = saCurDeliverReq.zeroed(); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount:" << " saPrvRedeemReq:" << saPrvRedeemReq << " saPrvIssueReq:" << saPrvIssueReq << " saPrvDeliverAct:" << saPrvDeliverAct << " saPrvDeliverReq:" << saPrvDeliverReq << " saCurRedeemReq:" << saCurRedeemReq << " saCurIssueReq:" << saCurIssueReq << " saNxtOwed:" << saNxtOwed; WriteLog (lsTRACE, RippleCalc) << pathState.getJson (); // Current redeem req can't be more than IOUs on hand. assert (!saCurRedeemReq || (-saNxtOwed) >= saCurRedeemReq); assert (!saCurIssueReq // If not issuing, fine. || saNxtOwed >= zero // saNxtOwed >= 0: Sender not holding next IOUs, saNxtOwed < 0: // Sender holding next IOUs. || -saNxtOwed == saCurRedeemReq); // If issue req, then redeem req must consume all owed. if (nodeIndex == 0) { // ^ --> ACCOUNT --> account|offer // Nothing to do, there is no previous to adjust. // // TODO(tom): we could have skipped all that setup and just left // or even just never call this whole routine on nodeIndex = 0! } // The next four cases correspond to the table at the bottom of this Wiki // page section: https://ripple.com/wiki/Transit_Fees#Implementation else if (previousNodeIsAccount && nextNodeIsAccount) { if (isFinalNode) { // account --> ACCOUNT --> $ // Overall deliverable. const STAmount saCurWantedReq = std::min ( pathState.outReq() - pathState.outAct(), saPrvLimit + saPrvOwed); auto saCurWantedAct = saCurWantedReq.zeroed (); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: account --> ACCOUNT --> $ :" << " saCurWantedReq=" << saCurWantedReq; // Calculate redeem if (saPrvRedeemReq) // Previous has IOUs to redeem. { // Redeem your own IOUs at 1:1 saCurWantedAct = std::min (saPrvRedeemReq, saCurWantedReq); saPrvRedeemAct = saCurWantedAct; uRateMax = STAmount::uRateOne; WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: Redeem at 1:1" << " saPrvRedeemReq=" << saPrvRedeemReq << " (available) saPrvRedeemAct=" << saPrvRedeemAct << " uRateMax=" << STAmount::saFromRate (uRateMax).getText (); } else { saPrvRedeemAct.clear (saPrvRedeemReq); } // Calculate issuing. saPrvIssueAct.clear (saPrvIssueReq); if (saCurWantedReq != saCurWantedAct // Need more. && saPrvIssueReq) // Will accept IOUs from previous. { // Rate: quality in : 1.0 // If we previously redeemed and this has a poorer rate, this // won't be included the current increment. computeRippleLiquidity ( rippleCalc, uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurWantedReq, saPrvIssueAct, saCurWantedAct, uRateMax); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: Issuing: Rate: quality in : 1.0" << " saPrvIssueAct:" << saPrvIssueAct << " saCurWantedAct:" << saCurWantedAct; } if (!saCurWantedAct) { // Must have processed something. terResult = tecPATH_DRY; } } else { // Not final node. // account --> ACCOUNT --> account saPrvRedeemAct.clear (saPrvRedeemReq); saPrvIssueAct.clear (saPrvIssueReq); // redeem (part 1) -> redeem if (saCurRedeemReq // Next wants IOUs redeemed from current account. && saPrvRedeemReq) // Previous has IOUs to redeem to the current account. { // TODO(tom): add English. // Rate : 1.0 : quality out - we must accept our own IOUs as 1:1. computeRippleLiquidity ( rippleCalc, QUALITY_ONE, uQualityOut, saPrvRedeemReq, saCurRedeemReq, saPrvRedeemAct, saCurRedeemAct, uRateMax); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "Rate : 1.0 : quality out" << " saPrvRedeemAct:" << saPrvRedeemAct << " saCurRedeemAct:" << saCurRedeemAct; } // issue (part 1) -> redeem if (saCurRedeemReq != saCurRedeemAct // The current node has more IOUs to redeem. && saPrvRedeemAct == saPrvRedeemReq) // The previous node has no IOUs to redeem remaining, so issues. { // Rate: quality in : quality out computeRippleLiquidity ( rippleCalc, uQualityIn, uQualityOut, saPrvIssueReq, saCurRedeemReq, saPrvIssueAct, saCurRedeemAct, uRateMax); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "Rate: quality in : quality out:" << " saPrvIssueAct:" << saPrvIssueAct << " saCurRedeemAct:" << saCurRedeemAct; } // redeem (part 2) -> issue. if (saCurIssueReq // Next wants IOUs issued. // TODO(tom): this condition seems redundant. && saCurRedeemAct == saCurRedeemReq // Can only issue if completed redeeming. && saPrvRedeemAct != saPrvRedeemReq) // Did not complete redeeming previous IOUs. { // Rate : 1.0 : transfer_rate computeRippleLiquidity ( rippleCalc, QUALITY_ONE, rippleCalc.mActiveLedger.rippleTransferRate (node.account_), saPrvRedeemReq, saCurIssueReq, saPrvRedeemAct, saCurIssueAct, uRateMax); WriteLog (lsDEBUG, RippleCalc) << "computeReverseLiquidityForAccount: " << "Rate : 1.0 : transfer_rate:" << " saPrvRedeemAct:" << saPrvRedeemAct << " saCurIssueAct:" << saCurIssueAct; } // issue (part 2) -> issue if (saCurIssueReq != saCurIssueAct // Need wants more IOUs issued. && saCurRedeemAct == saCurRedeemReq // Can only issue if completed redeeming. && saPrvRedeemReq == saPrvRedeemAct // Previously redeemed all owed IOUs. && saPrvIssueReq) // Previous can issue. { // Rate: quality in : 1.0 computeRippleLiquidity ( rippleCalc, uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurIssueReq, saPrvIssueAct, saCurIssueAct, uRateMax); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "Rate: quality in : 1.0:" << " saPrvIssueAct:" << saPrvIssueAct << " saCurIssueAct:" << saCurIssueAct; } if (!saCurRedeemAct && !saCurIssueAct) { // Did not make progress. terResult = tecPATH_DRY; } WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "^|account --> ACCOUNT --> account :" << " saCurRedeemReq:" << saCurRedeemReq << " saCurIssueReq:" << saCurIssueReq << " saPrvOwed:" << saPrvOwed << " saCurRedeemAct:" << saCurRedeemAct << " saCurIssueAct:" << saCurIssueAct; } } else if (previousNodeIsAccount && !nextNodeIsAccount) { // account --> ACCOUNT --> offer // Note: deliver is always issue as ACCOUNT is the issuer for the offer // input. WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "account --> ACCOUNT --> offer"; saPrvRedeemAct.clear (saPrvRedeemReq); saPrvIssueAct.clear (saPrvIssueReq); // We have three cases: the nxt offer can be owned by current account, // previous account or some third party account. // // Also, the current account may or may not have a redeemable balance // with the account for the next offer, so we don't yet know if we're // redeeming or issuing. // // TODO(tom): Make sure deliver was cleared, or check actual is zero. // redeem -> deliver/issue. if (saPrvOwed > zero // Previous has IOUs to redeem. && saCurDeliverReq) // Need some issued. { // Rate : 1.0 : transfer_rate computeRippleLiquidity ( rippleCalc, QUALITY_ONE, rippleCalc.mActiveLedger.rippleTransferRate (node.account_), saPrvRedeemReq, saCurDeliverReq, saPrvRedeemAct, saCurDeliverAct, uRateMax); } // issue -> deliver/issue if (saPrvRedeemReq == saPrvRedeemAct // Previously redeemed all owed. && saCurDeliverReq != saCurDeliverAct) // Still need some issued. { // Rate: quality in : 1.0 computeRippleLiquidity ( rippleCalc, uQualityIn, QUALITY_ONE, saPrvIssueReq, saCurDeliverReq, saPrvIssueAct, saCurDeliverAct, uRateMax); } if (!saCurDeliverAct) { // Must want something. terResult = tecPATH_DRY; } WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << " saCurDeliverReq:" << saCurDeliverReq << " saCurDeliverAct:" << saCurDeliverAct << " saPrvOwed:" << saPrvOwed; } else if (!previousNodeIsAccount && nextNodeIsAccount) { if (isFinalNode) { // offer --> ACCOUNT --> $ // Previous is an offer, no limit: redeem own IOUs. // // This is the final node; we can't look to the right to get values; // we have to go up to get the out value for the entire path state. const STAmount& saCurWantedReq = pathState.outReq() - pathState.outAct(); STAmount saCurWantedAct = saCurWantedReq.zeroed(); WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "offer --> ACCOUNT --> $ :" << " saCurWantedReq:" << saCurWantedReq << " saOutAct:" << pathState.outAct() << " saOutReq:" << pathState.outReq(); if (saCurWantedReq <= zero) { // TEMPORARY emergency fix // // TODO(tom): why can't saCurWantedReq be -1 if you want to // compute maximum liquidity? This might be unimplemented // functionality. TODO(tom): should the same check appear in // other paths or even be pulled up? WriteLog (lsFATAL, RippleCalc) << "CurWantReq was not positive"; return tefEXCEPTION; } assert (saCurWantedReq > zero); // FIXME: We got one of these // The previous node is an offer; we are receiving our own currency; // The previous order book's entries might hold our issuances; might // not hold our issuances; might be our own offer. // // Assume the worst case, the case which costs the most to go // through, which is that it is not our own offer or our own // issuances. Later on the forward pass we may be able to do // better. // // TODO: this comment applies generally to this section - move it up // to a document. // Rate: quality in : 1.0 computeRippleLiquidity ( rippleCalc, uQualityIn, QUALITY_ONE, saPrvDeliverReq, saCurWantedReq, saPrvDeliverAct, saCurWantedAct, uRateMax); if (!saCurWantedAct) { // Must have processed something. terResult = tecPATH_DRY; } WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount:" << " saPrvDeliverAct:" << saPrvDeliverAct << " saPrvDeliverReq:" << saPrvDeliverReq << " saCurWantedAct:" << saCurWantedAct << " saCurWantedReq:" << saCurWantedReq; } else { // offer --> ACCOUNT --> account // Note: offer is always delivering(redeeming) as account is issuer. WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: " << "offer --> ACCOUNT --> account :" << " saCurRedeemReq:" << saCurRedeemReq << " saCurIssueReq:" << saCurIssueReq; // deliver -> redeem // TODO(tom): now we have more checking in nodeRipple, these checks // might be redundant. if (saCurRedeemReq) // Next wants us to redeem. { // cur holds IOUs from the account to the right, the nxt // account. If someone is making the current account get rid of // the nxt account's IOUs, then charge the input for quality out. // // Rate : 1.0 : quality out computeRippleLiquidity ( rippleCalc, QUALITY_ONE, uQualityOut, saPrvDeliverReq, saCurRedeemReq, saPrvDeliverAct, saCurRedeemAct, uRateMax); } // deliver -> issue. if (saCurRedeemReq == saCurRedeemAct // Can only issue if previously redeemed all. && saCurIssueReq) // Need some issued. { // Rate : 1.0 : transfer_rate computeRippleLiquidity ( rippleCalc, QUALITY_ONE, rippleCalc.mActiveLedger.rippleTransferRate (node.account_), saPrvDeliverReq, saCurIssueReq, saPrvDeliverAct, saCurIssueAct, uRateMax); } WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount:" << " saCurRedeemAct:" << saCurRedeemAct << " saCurRedeemReq:" << saCurRedeemReq << " saPrvDeliverAct:" << saPrvDeliverAct << " saCurIssueReq:" << saCurIssueReq; if (!saPrvDeliverAct) { // Must want something. terResult = tecPATH_DRY; } } } else { // offer --> ACCOUNT --> offer // deliver/redeem -> deliver/issue. WriteLog (lsTRACE, RippleCalc) << "computeReverseLiquidityForAccount: offer --> ACCOUNT --> offer"; // Rate : 1.0 : transfer_rate computeRippleLiquidity ( rippleCalc, QUALITY_ONE, rippleCalc.mActiveLedger.rippleTransferRate (node.account_), saPrvDeliverReq, saCurDeliverReq, saPrvDeliverAct, saCurDeliverAct, uRateMax); if (!saCurDeliverAct) { // Must want something. terResult = tecPATH_DRY; } } return terResult; }
// Create a raw, non-integral amount from mantissa and exponent STAmount static raw (std::uint64_t mantissa, int exponent) { return STAmount ({Currency(3), AccountID(3)}, mantissa, exponent); }
// Append a node and insert before it any implied nodes. // Offers may go back to back. // <-- terResult: tesSUCCESS, temBAD_PATH, terNO_ACCOUNT, terNO_AUTH, terNO_LINE, tecPATH_DRY TER PathState::pushNode ( const int iType, const uint160& uAccountID, const uint160& uCurrencyID, const uint160& uIssuerID) { Node pnCur; const bool bFirst = vpnNodes.empty (); const Node& pnPrv = bFirst ? Node () : vpnNodes.back (); // true, iff node is a ripple account. false, iff node is an offer node. const bool bAccount = is_bit_set (iType, STPathElement::typeAccount); // true, iff currency supplied. // Currency is specified for the output of the current node. const bool bCurrency = is_bit_set (iType, STPathElement::typeCurrency); // Issuer is specified for the output of the current node. const bool bIssuer = is_bit_set (iType, STPathElement::typeIssuer); TER terResult = tesSUCCESS; WriteLog (lsTRACE, RippleCalc) << "pushNode> " << iType << ": " << (bAccount ? RippleAddress::createHumanAccountID (uAccountID) : "-") << " " << (bCurrency ? STAmount::createHumanCurrency (uCurrencyID) : "-") << "/" << (bIssuer ? RippleAddress::createHumanAccountID (uIssuerID) : "-"); pnCur.uFlags = iType; pnCur.uCurrencyID = bCurrency ? uCurrencyID : pnPrv.uCurrencyID; if (iType & ~STPathElement::typeValidBits) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: bad bits."; terResult = temBAD_PATH; } else if (bIssuer && !pnCur.uCurrencyID) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: issuer specified for STR."; terResult = temBAD_PATH; } else if (bIssuer && !uIssuerID) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad issuer."; terResult = temBAD_PATH; } else if (!bAccount && !bCurrency && !bIssuer) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: offer must specify at least currency or issuer."; terResult = temBAD_PATH; } else if (bAccount) { // Account link pnCur.uAccountID = uAccountID; pnCur.uIssuerID = bIssuer ? uIssuerID : !!pnCur.uCurrencyID ? uAccountID : ACCOUNT_STR; pnCur.saRevRedeem = STAmount (pnCur.uCurrencyID, uAccountID); pnCur.saRevIssue = STAmount (pnCur.uCurrencyID, uAccountID); pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID); pnCur.saFwdDeliver = pnCur.saRevDeliver; if (bFirst) { // The first node is always correct as is. nothing (); } else if (!uAccountID) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: specified bad account."; terResult = temBAD_PATH; } else { // Add required intermediate nodes to deliver to current account. WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for account."; terResult = pushImply ( pnCur.uAccountID, // Current account. pnCur.uCurrencyID, // Wanted currency. !!pnCur.uCurrencyID ? uAccountID : ACCOUNT_STR); // Account as wanted issuer. // Note: pnPrv may no longer be the immediately previous node. } if (tesSUCCESS == terResult && !vpnNodes.empty ()) { const Node& pnBck = vpnNodes.back (); bool bBckAccount = is_bit_set (pnBck.uFlags, STPathElement::typeAccount); if (bBckAccount) { SLE::pointer sleRippleState = lesEntries.entryCache (ltRIPPLE_STATE, Ledger::getRippleStateIndex (pnBck.uAccountID, pnCur.uAccountID, pnPrv.uCurrencyID)); if (!sleRippleState) { WriteLog (lsTRACE, RippleCalc) << "pushNode: No credit line between " << RippleAddress::createHumanAccountID (pnBck.uAccountID) << " and " << RippleAddress::createHumanAccountID (pnCur.uAccountID) << " for " << STAmount::createHumanCurrency (pnCur.uCurrencyID) << "." ; WriteLog (lsTRACE, RippleCalc) << getJson (); terResult = terNO_LINE; } else { WriteLog (lsTRACE, RippleCalc) << "pushNode: Credit line found between " << RippleAddress::createHumanAccountID (pnBck.uAccountID) << " and " << RippleAddress::createHumanAccountID (pnCur.uAccountID) << " for " << STAmount::createHumanCurrency (pnCur.uCurrencyID) << "." ; SLE::pointer sleBck = lesEntries.entryCache (ltACCOUNT_ROOT, Ledger::getAccountRootIndex (pnBck.uAccountID)); bool bHigh = pnBck.uAccountID > pnCur.uAccountID; if (!sleBck) { WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from non-existent issuer: " << RippleAddress::createHumanAccountID (pnBck.uAccountID); terResult = terNO_ACCOUNT; } else if ((is_bit_set (sleBck->getFieldU32 (sfFlags), lsfRequireAuth) && !is_bit_set (sleRippleState->getFieldU32 (sfFlags), (bHigh ? lsfHighAuth : lsfLowAuth)))) { WriteLog (lsWARNING, RippleCalc) << "pushNode: delay: can't receive IOUs from issuer without auth."; terResult = terNO_AUTH; } if (tesSUCCESS == terResult) { STAmount saOwed = lesEntries.rippleOwed (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID); STAmount saLimit; if (saOwed <= zero && -saOwed >= (saLimit = lesEntries.rippleLimit (pnCur.uAccountID, pnBck.uAccountID, pnCur.uCurrencyID))) { WriteLog (lsWARNING, RippleCalc) << "pushNode: dry:" << " saOwed=" << saOwed << " saLimit=" << saLimit; terResult = tecPATH_DRY; } } } } } if (tesSUCCESS == terResult) { vpnNodes.push_back (pnCur); } } else { // Offer link // Offers bridge a change in currency & issuer or just a change in issuer. pnCur.uIssuerID = bIssuer ? uIssuerID : !!pnCur.uCurrencyID ? !!pnPrv.uIssuerID ? pnPrv.uIssuerID // Default to previous issuer : pnPrv.uAccountID // Or previous account if no previous issuer. : ACCOUNT_STR; pnCur.saRateMax = saZero; pnCur.saRevDeliver = STAmount (pnCur.uCurrencyID, pnCur.uIssuerID); pnCur.saFwdDeliver = pnCur.saRevDeliver; if (!!pnCur.uCurrencyID != !!pnCur.uIssuerID) { WriteLog (lsDEBUG, RippleCalc) << "pushNode: currency is inconsistent with issuer."; terResult = temBAD_PATH; } else if(!LedgerDump::enactHistoricalQuirk (QuirkSameCurrencyOffer) && pnPrv.uCurrencyID == pnCur.uCurrencyID && pnPrv.uIssuerID == pnCur.uIssuerID) { WriteLog(lsDEBUG, RippleCalc) << "pushNode: bad path: offer to same currency and issuer"; terResult = temBAD_PATH; } else { // Previous is an account. WriteLog (lsTRACE, RippleCalc) << "pushNode: imply for offer."; // Insert intermediary issuer account if needed. terResult = pushImply ( ACCOUNT_STR, // Rippling, but offers don't have an account. pnPrv.uCurrencyID, pnPrv.uIssuerID); } if (tesSUCCESS == terResult) { vpnNodes.push_back (pnCur); } } WriteLog (lsTRACE, RippleCalc) << "pushNode< : " << transToken (terResult); return terResult; }
bool PathRequest::isValid (RippleLineCache::ref crCache) { ScopedLockType sl (mLock); bValid = raSrcAccount && raDstAccount && saDstAmount > zero; auto const& lrLedger = crCache->getLedger (); if (bValid) { if (! crCache->getLedger()->exists( keylet::account(*raSrcAccount))) { // no source account bValid = false; jvStatus = rpcError (rpcSRC_ACT_NOT_FOUND); } } if (bValid) { auto const sleDest = crCache->getLedger()->read( keylet::account(*raDstAccount)); Json::Value& jvDestCur = (jvStatus[jss::destination_currencies] = Json::arrayValue); if (!sleDest) { // no destination account jvDestCur.append (Json::Value ("XRP")); if (!saDstAmount.native ()) { // only XRP can be send to a non-existent account bValid = false; jvStatus = rpcError (rpcACT_NOT_FOUND); } else if (saDstAmount < STAmount (lrLedger->fees().accountReserve (0))) { // payment must meet reserve bValid = false; jvStatus = rpcError (rpcDST_AMT_MALFORMED); } } else { bool const disallowXRP ( sleDest->getFlags() & lsfDisallowXRP); auto usDestCurrID = accountDestCurrencies ( *raDstAccount, crCache, !disallowXRP); for (auto const& currency : usDestCurrID) jvDestCur.append (to_string (currency)); jvStatus["destination_tag"] = (sleDest->getFlags () & lsfRequireDestTag) != 0; } } if (bValid) { jvStatus[jss::ledger_hash] = to_string (lrLedger->info().hash); jvStatus[jss::ledger_index] = lrLedger->seq(); } return bValid; }