Ejemplo n.º 1
0
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;
}
Ejemplo n.º 2
0
TER PathCursor::deliverNodeForward (
    AccountID const& uInAccountID,    // --> Input owner's account.
    STAmount const& saInReq,        // --> Amount to deliver.
    STAmount& saInAct,              // <-- Amount delivered, this invocation.
    STAmount& saInFees,             // <-- Fees charged, this invocation.
    bool callerHasLiquidity) const
{
    TER resultCode   = tesSUCCESS;

    // Don't deliver more than wanted.
    // Zeroed in reverse pass.
    node().directory.restart(multiQuality_);

    saInAct.clear (saInReq);
    saInFees.clear (saInReq);

    int loopCount = 0;
    auto viewJ = rippleCalc_.logs_.journal ("View");

    // XXX Perhaps make sure do not exceed node().saRevDeliver as another way to
    // stop?
    while (resultCode == tesSUCCESS && saInAct + saInFees < saInReq)
    {
        // Did not spend all inbound deliver funds.
        if (++loopCount >
            (multiQuality_ ?
                CALC_NODE_DELIVER_MAX_LOOPS_MQ :
                CALC_NODE_DELIVER_MAX_LOOPS))
        {
            JLOG (j_.warn())
                << "deliverNodeForward: max loops cndf";
            return telFAILED_PROCESSING;
        }

        // Determine values for pass to adjust saInAct, saInFees, and
        // node().saFwdDeliver.
        advanceNode (saInAct, false, callerHasLiquidity);

        // If needed, advance to next funded offer.

        if (resultCode != tesSUCCESS)
        {
        }
        else if (!node().offerIndex_)
        {
            JLOG (j_.warn())
                << "deliverNodeForward: INTERNAL ERROR: Ran out of offers.";
            return telFAILED_PROCESSING;
        }
        else if (resultCode == tesSUCCESS)
        {
            auto const xferRate = effectiveRate (
                previousNode().issue_,
                uInAccountID,
                node().offerOwnerAccount_,
                previousNode().transferRate_);

            // First calculate assuming no output fees: saInPassAct,
            // saInPassFees, saOutPassAct.

            // Offer maximum out - limited by funds with out fees.
            auto saOutFunded = std::min (
                node().saOfferFunds, node().saTakerGets);

            // Offer maximum out - limit by most to deliver.
            auto saOutPassFunded = std::min (
                saOutFunded,
                node().saRevDeliver - node().saFwdDeliver);

            // Offer maximum in - Limited by by payout.
            auto saInFunded = mulRound (
                saOutPassFunded,
                node().saOfrRate,
                node().saTakerPays.issue (),
                true);

            // Offer maximum in with fees.
            auto saInTotal = multiplyRound (
                saInFunded, xferRate, true);
            auto saInRemaining = saInReq - saInAct - saInFees;

            if (saInRemaining < beast::zero)
                saInRemaining.clear();

            // In limited by remaining.
            auto saInSum = std::min (saInTotal, saInRemaining);

            // In without fees.
            auto saInPassAct = std::min (
                node().saTakerPays,
                divideRound (saInSum, xferRate, true));

            // Out limited by in remaining.
            auto outPass = divRound (
                saInPassAct, node().saOfrRate, node().saTakerGets.issue (), true);
            STAmount saOutPassMax    = std::min (saOutPassFunded, outPass);

            STAmount saInPassFeesMax = saInSum - saInPassAct;

            // Will be determined by next node().
            STAmount saOutPassAct;

            // Will be determined by adjusted saInPassAct.
            STAmount saInPassFees;

            JLOG (j_.trace())
                << "deliverNodeForward:"
                << " nodeIndex_=" << nodeIndex_
                << " saOutFunded=" << saOutFunded
                << " saOutPassFunded=" << saOutPassFunded
                << " node().saOfferFunds=" << node().saOfferFunds
                << " node().saTakerGets=" << node().saTakerGets
                << " saInReq=" << saInReq
                << " saInAct=" << saInAct
                << " saInFees=" << saInFees
                << " saInFunded=" << saInFunded
                << " saInTotal=" << saInTotal
                << " saInSum=" << saInSum
                << " saInPassAct=" << saInPassAct
                << " saOutPassMax=" << saOutPassMax;

            // FIXME: We remove an offer if WE didn't want anything out of it?
            if (!node().saTakerPays || saInSum <= beast::zero)
            {
                JLOG (j_.debug())
                    << "deliverNodeForward: Microscopic offer unfunded.";

                // After math offer is effectively unfunded.
                pathState_.unfundedOffers().push_back (node().offerIndex_);
                node().bEntryAdvance = true;
                continue;
            }

            if (!saInFunded)
            {
                // Previous check should catch this.
                JLOG (j_.warn())
                    << "deliverNodeForward: UNREACHABLE REACHED";

                // After math offer is effectively unfunded.
                pathState_.unfundedOffers().push_back (node().offerIndex_);
                node().bEntryAdvance = true;
                continue;
            }

            if (!isXRP(nextNode().account_))
            {
                // ? --> OFFER --> account
                // Input fees: vary based upon the consumed offer's owner.
                // Output fees: none as XRP or the destination account is the
                // issuer.

                saOutPassAct = saOutPassMax;
                saInPassFees = saInPassFeesMax;

                JLOG (j_.trace())
                    << "deliverNodeForward: ? --> OFFER --> account:"
                    << " offerOwnerAccount_="
                    << node().offerOwnerAccount_
                    << " nextNode().account_="
                    << nextNode().account_
                    << " saOutPassAct=" << saOutPassAct
                    << " saOutFunded=" << saOutFunded;

                // Output: Debit offer owner, send XRP or non-XPR to next
                // account.
                resultCode = accountSend(view(),
                    node().offerOwnerAccount_,
                    nextNode().account_,
                    saOutPassAct, viewJ);

                if (resultCode != tesSUCCESS)
                    break;
            }
            else
            {
                // ? --> OFFER --> offer
                //
                // Offer to offer means current order book's output currency and
                // issuer match next order book's input current and issuer.
                //
                // Output fees: possible if issuer has fees and is not on either
                // side.
                STAmount saOutPassFees;

                // Output fees vary as the next nodes offer owners may vary.
                // Therefore, immediately push through output for current offer.
                resultCode = increment().deliverNodeForward (
                    node().offerOwnerAccount_,  // --> Current holder.
                    saOutPassMax,             // --> Amount available.
                    saOutPassAct,             // <-- Amount delivered.
                    saOutPassFees,            // <-- Fees charged.
                    saInAct > beast::zero);

                if (resultCode != tesSUCCESS)
                    break;

                if (saOutPassAct == saOutPassMax)
                {
                    // No fees and entire output amount.

                    saInPassFees = saInPassFeesMax;
                }
                else
                {
                    // Fraction of output amount.
                    // Output fees are paid by offer owner and not passed to
                    // previous.

                    assert (saOutPassAct < saOutPassMax);
                    auto inPassAct = mulRound (
                        saOutPassAct, node().saOfrRate, saInReq.issue (), true);
                    saInPassAct = std::min (node().saTakerPays, inPassAct);
                    auto inPassFees = multiplyRound (
                        saInPassAct, xferRate, true);
                    saInPassFees    = std::min (saInPassFeesMax, inPassFees);
                }

                // Do outbound debiting.
                // Send to issuer/limbo total amount including fees (issuer gets
                // fees).
                auto const& id = isXRP(node().issue_) ?
                        xrpAccount() : node().issue_.account;
                auto outPassTotal = saOutPassAct + saOutPassFees;
                accountSend(view(),
                    node().offerOwnerAccount_,
                    id,
                    outPassTotal,
                    viewJ);

                JLOG (j_.trace())
                    << "deliverNodeForward: ? --> OFFER --> offer:"
                    << " saOutPassAct=" << saOutPassAct
                    << " saOutPassFees=" << saOutPassFees;
            }

            JLOG (j_.trace())
                << "deliverNodeForward: "
                << " nodeIndex_=" << nodeIndex_
                << " node().saTakerGets=" << node().saTakerGets
                << " node().saTakerPays=" << node().saTakerPays
                << " saInPassAct=" << saInPassAct
                << " saInPassFees=" << saInPassFees
                << " saOutPassAct=" << saOutPassAct
                << " saOutFunded=" << saOutFunded;

            // Funds were spent.
            node().bFundsDirty = true;

            // Do inbound crediting.
            //
            // Credit offer owner from in issuer/limbo (input transfer fees left
            // with owner).  Don't attempt to have someone credit themselves, it
            // is redundant.
            if (isXRP (previousNode().issue_.currency)
                || uInAccountID != node().offerOwnerAccount_)
            {
                auto id = !isXRP(previousNode().issue_.currency) ?
                        uInAccountID : xrpAccount();
                resultCode = accountSend(view(),
                    id,
                    node().offerOwnerAccount_,
                    saInPassAct,
                    viewJ);

                if (resultCode != tesSUCCESS)
                    break;
            }

            // Adjust offer.
            //
            // Fees are considered paid from a seperate budget and are not named
            // in the offer.
            STAmount saTakerGetsNew  = node().saTakerGets - saOutPassAct;
            STAmount saTakerPaysNew  = node().saTakerPays - saInPassAct;

            if (saTakerPaysNew < beast::zero || saTakerGetsNew < beast::zero)
            {
                JLOG (j_.warn())
                    << "deliverNodeForward: NEGATIVE:"
                    << " saTakerPaysNew=" << saTakerPaysNew
                    << " saTakerGetsNew=" << saTakerGetsNew;

                resultCode = telFAILED_PROCESSING;
                break;
            }

            node().sleOffer->setFieldAmount (sfTakerGets, saTakerGetsNew);
            node().sleOffer->setFieldAmount (sfTakerPays, saTakerPaysNew);

            view().update (node().sleOffer);

            if (saOutPassAct == saOutFunded || saTakerGetsNew == beast::zero)
            {
                // Offer became unfunded.

                JLOG (j_.debug())
                    << "deliverNodeForward: unfunded:"
                    << " saOutPassAct=" << saOutPassAct
                    << " saOutFunded=" << saOutFunded;

                pathState_.unfundedOffers().push_back (node().offerIndex_);
                node().bEntryAdvance   = true;
            }
            else
            {
                if (saOutPassAct >= saOutFunded)
                {
                    JLOG (j_.warn())
                        << "deliverNodeForward: TOO MUCH:"
                        << " saOutPassAct=" << saOutPassAct
                        << " saOutFunded=" << saOutFunded;
                }

                assert (saOutPassAct < saOutFunded);
            }

            saInAct += saInPassAct;
            saInFees += saInPassFees;

            // Adjust amount available to next node().
            node().saFwdDeliver = std::min (node().saRevDeliver,
                                        node().saFwdDeliver + saOutPassAct);
        }
    }

    JLOG (j_.trace())
        << "deliverNodeForward<"
        << " nodeIndex_=" << nodeIndex_
        << " saInAct=" << saInAct
        << " saInFees=" << saInFees;

    return resultCode;
}
Ejemplo n.º 3
0
// To deliver from an order book, when computing
TER PathCursor::deliverNodeReverseImpl (
    AccountID const& uOutAccountID, // --> Output owner's account.
    STAmount const& saOutReq,       // --> Funds requested to be
                                    // delivered for an increment.
    STAmount& saOutAct,             // <-- Funds actually delivered for an
                                    // increment
    bool callerHasLiquidity
                                        ) const
{
    TER resultCode   = tesSUCCESS;

    // Accumulation of what the previous node must deliver.
    // Possible optimization: Note this gets zeroed on each increment, ideally
    // only on first increment, then it could be a limit on the forward pass.
    saOutAct.clear (saOutReq);

    JLOG (j_.trace())
        << "deliverNodeReverse>"
        << " saOutAct=" << saOutAct
        << " saOutReq=" << saOutReq
        << " saPrvDlvReq=" << previousNode().saRevDeliver;

    assert (saOutReq != zero);

    int loopCount = 0;
    auto viewJ = rippleCalc_.logs_.journal ("View");

    // While we did not deliver as much as requested:
    while (saOutAct < saOutReq)
    {
        if (++loopCount >
            (multiQuality_ ?
                CALC_NODE_DELIVER_MAX_LOOPS_MQ :
                CALC_NODE_DELIVER_MAX_LOOPS))
        {
            JLOG (j_.warn()) << "loop count exceeded";
            return telFAILED_PROCESSING;
        }

        resultCode = advanceNode (saOutAct, true, callerHasLiquidity);
        // If needed, advance to next funded offer.

        if (resultCode != tesSUCCESS || !node().offerIndex_)
            // Error or out of offers.
            break;

        auto const xferRate = effectiveRate (
            node().issue_,
            uOutAccountID,
            node().offerOwnerAccount_,
            node().transferRate_);

        JLOG (j_.trace())
            << "deliverNodeReverse:"
            << " offerOwnerAccount_=" << node().offerOwnerAccount_
            << " uOutAccountID=" << uOutAccountID
            << " node().issue_.account=" << node().issue_.account
            << " xferRate=" << xferRate;

        // Only use rate when not in multi-quality mode
        if (!multiQuality_)
        {
            if (!node().rateMax)
            {
                // Set initial rate.
                JLOG (j_.trace())
                    << "Set initial rate";

                node().rateMax = xferRate;
            }
            else if (xferRate > node().rateMax)
            {
                // Offer exceeds initial rate.
                JLOG (j_.trace())
                    << "Offer exceeds initial rate: " << *node().rateMax;

                break;  // Done. Don't bother looking for smaller transferRates.
            }
            else if (xferRate < node().rateMax)
            {
                // Reducing rate. Additional offers will only
                // be considered for this increment if they
                // are at least this good.
                //
                // At this point, the overall rate is reducing,
                // while the overall rate is not xferRate, it
                // would be wrong to add anything with a rate
                // above xferRate.
                //
                // The rate would be reduced if the current
                // offer was from the issuer and the previous
                // offer wasn't.

                JLOG (j_.trace())
                    << "Reducing rate: " << *node().rateMax;

                node().rateMax = xferRate;
            }
        }

        // Amount that goes to the taker.
        STAmount saOutPassReq = std::min (
            std::min (node().saOfferFunds, node().saTakerGets),
            saOutReq - saOutAct);

        // Maximum out - assuming no out fees.
        STAmount saOutPassAct = saOutPassReq;

        // Amount charged to the offer owner.
        //
        // The fee goes to issuer. The fee is paid by offer owner and not passed
        // as a cost to taker.
        //
        // Round down: prefer liquidity rather than microscopic fees.
        STAmount saOutPlusFees   = multiplyRound (
            saOutPassAct, xferRate, false);


        // Offer out with fees.

        JLOG (j_.trace())
            << "deliverNodeReverse:"
            << " saOutReq=" << saOutReq
            << " saOutAct=" << saOutAct
            << " node().saTakerGets=" << node().saTakerGets
            << " saOutPassAct=" << saOutPassAct
            << " saOutPlusFees=" << saOutPlusFees
            << " node().saOfferFunds=" << node().saOfferFunds;

        if (saOutPlusFees > node().saOfferFunds)
        {
            // Offer owner can not cover all fees, compute saOutPassAct based on
            // node().saOfferFunds.
            saOutPlusFees   = node().saOfferFunds;

            // Round up: prefer liquidity rather than microscopic fees. But,
            // limit by requested.
            auto fee = divideRound (saOutPlusFees, xferRate, true);
            saOutPassAct = std::min (saOutPassReq, fee);

            JLOG (j_.trace())
                << "deliverNodeReverse: Total exceeds fees:"
                << " saOutPassAct=" << saOutPassAct
                << " saOutPlusFees=" << saOutPlusFees
                << " node().saOfferFunds=" << node().saOfferFunds;
        }

        // Compute portion of input needed to cover actual output.
        auto outputFee = mulRound (
            saOutPassAct, node().saOfrRate, node().saTakerPays.issue (), true);
        if (*stAmountCalcSwitchover == false && ! outputFee)
        {
            JLOG (j_.fatal())
                << "underflow computing outputFee "
                << "saOutPassAct: " << saOutPassAct
                << " saOfrRate: " << node ().saOfrRate;
            return telFAILED_PROCESSING;
        }
        STAmount saInPassReq = std::min (node().saTakerPays, outputFee);
        STAmount saInPassAct;

        JLOG (j_.trace())
            << "deliverNodeReverse:"
            << " outputFee=" << outputFee
            << " saInPassReq=" << saInPassReq
            << " node().saOfrRate=" << node().saOfrRate
            << " saOutPassAct=" << saOutPassAct
            << " saOutPlusFees=" << saOutPlusFees;

        if (!saInPassReq) // FIXME: This is bogus
        {
            // After rounding did not want anything.
            JLOG (j_.debug())
                << "deliverNodeReverse: micro offer is unfunded.";

            node().bEntryAdvance   = true;
            continue;
        }
        // Find out input amount actually available at current rate.
        else if (!isXRP(previousNode().account_))
        {
            // account --> OFFER --> ?
            // Due to node expansion, previous is guaranteed to be the issuer.
            //
            // Previous is the issuer and receiver is an offer, so no fee or
            // quality.
            //
            // Previous is the issuer and has unlimited funds.
            //
            // Offer owner is obtaining IOUs via an offer, so credit line limits
            // are ignored.  As limits are ignored, don't need to adjust
            // previous account's balance.

            saInPassAct = saInPassReq;

            JLOG (j_.trace())
                << "deliverNodeReverse: account --> OFFER --> ? :"
                << " saInPassAct=" << saInPassAct;
        }
        else
        {
            // offer --> OFFER --> ?
            // Compute in previous offer node how much could come in.

            // TODO(tom): Fix nasty recursion here!
            resultCode = increment(-1).deliverNodeReverseImpl(
                node().offerOwnerAccount_,
                saInPassReq,
                saInPassAct,
                saOutAct > zero);

            if (amendmentRIPD1141(view().info().parentCloseTime))
            {
                // The recursive call is dry this time, but we have liquidity
                // from previous calls
                if (resultCode == tecPATH_DRY && saOutAct > zero)
                {
                    resultCode = tesSUCCESS;
                    break;
                }
            }

            JLOG (j_.trace())
                << "deliverNodeReverse: offer --> OFFER --> ? :"
                << " saInPassAct=" << saInPassAct;
        }

        if (resultCode != tesSUCCESS)
            break;

        if (saInPassAct < saInPassReq)
        {
            // Adjust output to conform to limited input.
            auto outputRequirements = divRound (saInPassAct, node ().saOfrRate,
                node ().saTakerGets.issue (), true);
            saOutPassAct = std::min (saOutPassReq, outputRequirements);
            auto outputFees = multiplyRound (saOutPassAct, xferRate, true);
            saOutPlusFees   = std::min (node().saOfferFunds, outputFees);

            JLOG (j_.trace())
                << "deliverNodeReverse: adjusted:"
                << " saOutPassAct=" << saOutPassAct
                << " saOutPlusFees=" << saOutPlusFees;
        }
        else
        {
            // TODO(tom): more logging here.
            assert (saInPassAct == saInPassReq);
        }

        // Funds were spent.
        node().bFundsDirty = true;

        // Want to deduct output to limit calculations while computing reverse.
        // Don't actually need to send.
        //
        // Sending could be complicated: could fund a previous offer not yet
        // visited.  However, these deductions and adjustments are tenative.
        //
        // Must reset balances when going forward to perform actual transfers.
        resultCode   = accountSend(view(),
            node().offerOwnerAccount_, node().issue_.account, saOutPassAct, viewJ);

        if (resultCode != tesSUCCESS)
            break;

        // Adjust offer
        STAmount saTakerGetsNew  = node().saTakerGets - saOutPassAct;
        STAmount saTakerPaysNew  = node().saTakerPays - saInPassAct;

        if (saTakerPaysNew < zero || saTakerGetsNew < zero)
        {
            JLOG (j_.warn())
                << "deliverNodeReverse: NEGATIVE:"
                << " node().saTakerPaysNew=" << saTakerPaysNew
                << " node().saTakerGetsNew=" << saTakerGetsNew;

            resultCode = telFAILED_PROCESSING;
            break;
        }

        node().sleOffer->setFieldAmount (sfTakerGets, saTakerGetsNew);
        node().sleOffer->setFieldAmount (sfTakerPays, saTakerPaysNew);

        view().update (node().sleOffer);

        if (saOutPassAct == node().saTakerGets)
        {
            // Offer became unfunded.
            JLOG (j_.debug())
                << "deliverNodeReverse: offer became unfunded.";

            node().bEntryAdvance   = true;
            // XXX When don't we want to set advance?
        }
        else
        {
            assert (saOutPassAct < node().saTakerGets);
        }

        saOutAct += saOutPassAct;
        // Accumulate what is to be delivered from previous node.
        previousNode().saRevDeliver += saInPassAct;
    }

    if (saOutAct > saOutReq)
    {
        JLOG (j_.warn())
            << "deliverNodeReverse: TOO MUCH:"
            << " saOutAct=" << saOutAct
            << " saOutReq=" << saOutReq;
    }

    assert(saOutAct <= saOutReq);

    if (resultCode == tesSUCCESS && !saOutAct)
        resultCode = tecPATH_DRY;
    // Unable to meet request, consider path dry.
    // Design invariant: if nothing was actually delivered, return tecPATH_DRY.

    JLOG (j_.trace())
        << "deliverNodeReverse<"
        << " saOutAct=" << saOutAct
        << " saOutReq=" << saOutReq
        << " saPrvDlvReq=" << previousNode().saRevDeliver;

    return resultCode;
}