diff --git a/src/ripple/app/paths/PathRequest.cpp b/src/ripple/app/paths/PathRequest.cpp index 704adab0e..c0276dec9 100644 --- a/src/ripple/app/paths/PathRequest.cpp +++ b/src/ripple/app/paths/PathRequest.cpp @@ -24,15 +24,18 @@ #include #include #include +#include #include #include #include #include #include #include + #include #include #include + #include namespace ripple { @@ -489,6 +492,10 @@ PathRequest::findPaths( Json::Value& jvArray) { auto sourceCurrencies = sciSourceCurrencies; + if (sourceCurrencies.empty() && saSendMax) + { + sourceCurrencies.insert(saSendMax->issue()); + } if (sourceCurrencies.empty()) { auto currencies = accountSourceCurrencies(*raSrcAccount, cache, true); @@ -505,10 +512,7 @@ PathRequest::findPaths( } } - auto const dst_amount = convert_all_ - ? STAmount( - saDstAmount.issue(), STAmount::cMaxValue, STAmount::cMaxOffset) - : saDstAmount; + auto const dst_amount = convertAmount(saDstAmount, convert_all_); hash_map> currency_map; for (auto const& issue : sourceCurrencies) { diff --git a/src/ripple/app/paths/Pathfinder.cpp b/src/ripple/app/paths/Pathfinder.cpp index 752a155f3..1e698f3b5 100644 --- a/src/ripple/app/paths/Pathfinder.cpp +++ b/src/ripple/app/paths/Pathfinder.cpp @@ -23,11 +23,13 @@ #include #include #include +#include #include #include #include #include #include + #include /* @@ -143,6 +145,13 @@ pathTypeToString(Pathfinder::PathType const& type) return ret; } +// Return the smallest amount of useful liquidity for a given amount, and the +// total number of paths we have to evaluate. +STAmount +smallestUsefulAmount(STAmount const& amount, int maxPaths) +{ + return divide(amount, STAmount(maxPaths + 2), amount.issue()); +} } // namespace Pathfinder::Pathfinder( @@ -169,12 +178,7 @@ Pathfinder::Pathfinder( 1u, 0, true))) - , convert_all_( - mDstAmount == - STAmount( - mDstAmount.issue(), - STAmount::cMaxValue, - STAmount::cMaxOffset)) + , convert_all_(convertAllCheck(mDstAmount)) , mLedger(cache->getLedger()) , mRLCache(cache) , app_(app) @@ -395,25 +399,10 @@ Pathfinder::getPathLiquidity( } } -namespace { - -// Return the smallest amount of useful liquidity for a given amount, and the -// total number of paths we have to evaluate. -STAmount -smallestUsefulAmount(STAmount const& amount, int maxPaths) -{ - return divide(amount, STAmount(maxPaths + 2), amount.issue()); -} - -} // namespace - void Pathfinder::computePathRanks(int maxPaths) { - mRemainingAmount = convert_all_ - ? STAmount( - mDstAmount.issue(), STAmount::cMaxValue, STAmount::cMaxOffset) - : mDstAmount; + mRemainingAmount = convertAmount(mDstAmount, convert_all_); // Must subtract liquidity in default path from remaining amount. try @@ -496,19 +485,17 @@ Pathfinder::rankPaths( rankedPaths.clear(); rankedPaths.reserve(paths.size()); - STAmount saMinDstAmount; - if (convert_all_) - { + auto const saMinDstAmount = [&]() -> STAmount { + if (!convert_all_) + { + // Ignore paths that move only very small amounts. + return smallestUsefulAmount(mDstAmount, maxPaths); + } + // On convert_all_ partialPaymentAllowed will be set to true // and requiring a huge amount will find the highest liquidity. - saMinDstAmount = STAmount( - mDstAmount.issue(), STAmount::cMaxValue, STAmount::cMaxOffset); - } - else - { - // Ignore paths that move only very small amounts. - saMinDstAmount = smallestUsefulAmount(mDstAmount, maxPaths); - } + return largestAmount(mDstAmount); + }(); for (int i = 0; i < paths.size(); ++i) { diff --git a/src/ripple/app/paths/impl/PathfinderUtils.h b/src/ripple/app/paths/impl/PathfinderUtils.h new file mode 100644 index 000000000..066d905a1 --- /dev/null +++ b/src/ripple/app/paths/impl/PathfinderUtils.h @@ -0,0 +1,53 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2020 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_PATH_IMPL_PATHFINDERUTILS_H_INCLUDED +#define RIPPLE_PATH_IMPL_PATHFINDERUTILS_H_INCLUDED + +#include + +namespace ripple { + +inline STAmount +largestAmount(STAmount const& amt) +{ + if (amt.native()) + return INITIAL_XRP; + + return STAmount(amt.issue(), STAmount::cMaxValue, STAmount::cMaxOffset); +} + +inline STAmount +convertAmount(STAmount const& amt, bool all) +{ + if (!all) + return amt; + + return largestAmount(amt); +}; + +inline bool +convertAllCheck(STAmount const& a) +{ + return a == largestAmount(a); +} + +} // namespace ripple + +#endif diff --git a/src/test/app/Path_test.cpp b/src/test/app/Path_test.cpp index 1889a790e..8bb870e99 100644 --- a/src/test/app/Path_test.cpp +++ b/src/test/app/Path_test.cpp @@ -1303,6 +1303,65 @@ public: BEAST_EXPECT(same(st, stpath(M1, G2), stpath(IPE(G2["HKD"]), G2))); } + void + receive_max() + { + testcase("Receive max"); + using namespace jtx; + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const charlie = Account("charlie"); + auto const gw = Account("gw"); + auto const USD = gw["USD"]; + { + // XRP -> IOU receive max + Env env(*this); + env.fund(XRP(10000), alice, bob, charlie, gw); + env.close(); + env.trust(USD(100), alice, bob, charlie); + env.close(); + env(pay(gw, charlie, USD(10))); + env.close(); + env(offer(charlie, XRP(10), USD(10))); + env.close(); + auto [st, sa, da] = + find_paths(env, alice, bob, USD(-1), XRP(100).value()); + BEAST_EXPECT(sa == XRP(10)); + BEAST_EXPECT(equal(da, USD(10))); + if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1)) + { + auto const& pathElem = st[0][0]; + BEAST_EXPECT( + pathElem.isOffer() && pathElem.getIssuerID() == gw.id() && + pathElem.getCurrency() == USD.currency); + } + } + { + // IOU -> XRP receive max + Env env(*this); + env.fund(XRP(10000), alice, bob, charlie, gw); + env.close(); + env.trust(USD(100), alice, bob, charlie); + env.close(); + env(pay(gw, alice, USD(10))); + env.close(); + env(offer(charlie, USD(10), XRP(10))); + env.close(); + auto [st, sa, da] = + find_paths(env, alice, bob, drops(-1), USD(100).value()); + BEAST_EXPECT(sa == USD(10)); + BEAST_EXPECT(equal(da, XRP(10))); + if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1)) + { + auto const& pathElem = st[0][0]; + BEAST_EXPECT( + pathElem.isOffer() && + pathElem.getIssuerID() == xrpAccount() && + pathElem.getCurrency() == xrpCurrency()); + } + } + } + void run() override { @@ -1325,6 +1384,7 @@ public: trust_auto_clear_trust_normal_clear(); trust_auto_clear_trust_auto_clear(); xrp_to_xrp(); + receive_max(); // The following path_find_NN tests are data driven tests // that were originally implemented in js/coffee and migrated