1#include <test/jtx/Account.h>
2#include <test/jtx/Env.h>
3#include <test/jtx/TestHelpers.h>
4#include <test/jtx/amount.h>
5#include <test/jtx/balance.h>
6#include <test/jtx/domain.h>
7#include <test/jtx/envconfig.h>
8#include <test/jtx/mpt.h>
9#include <test/jtx/offer.h>
10#include <test/jtx/pay.h>
11#include <test/jtx/permissioned_dex.h>
13#include <xrpld/core/Config.h>
14#include <xrpld/rpc/Context.h>
15#include <xrpld/rpc/RPCHandler.h>
16#include <xrpld/rpc/Role.h>
17#include <xrpld/rpc/detail/AccountAssets.h>
18#include <xrpld/rpc/detail/AssetCache.h>
19#include <xrpld/rpc/detail/RPCHelpers.h>
20#include <xrpld/rpc/detail/Tuning.h>
22#include <xrpl/basics/base_uint.h>
23#include <xrpl/beast/unit_test/suite.h>
24#include <xrpl/core/Job.h>
25#include <xrpl/core/JobQueue.h>
26#include <xrpl/json/json_value.h>
27#include <xrpl/protocol/AccountID.h>
28#include <xrpl/protocol/ApiVersion.h>
29#include <xrpl/protocol/Indexes.h>
30#include <xrpl/protocol/PathAsset.h>
31#include <xrpl/protocol/SField.h>
32#include <xrpl/protocol/STAmount.h>
33#include <xrpl/protocol/STPathSet.h>
34#include <xrpl/protocol/UintTypes.h>
35#include <xrpl/protocol/jss.h>
36#include <xrpl/resource/Charge.h>
37#include <xrpl/resource/Consumer.h>
38#include <xrpl/resource/Fees.h>
57 jv[jss::command] =
"ripple_path_find";
58 jv[jss::source_account] =
toBase58(src);
64 for (
auto const&
id : numSrc)
72 jv[jss::destination_account] = d;
95 cfg->pathSearchOld = 7;
97 cfg->pathSearchMax = 10;
107 using namespace std::chrono_literals;
110 auto const gw =
Account(
"gateway");
111 auto const alice =
Account(
"alice");
112 auto const bob =
Account(
"bob");
114 env.
fund(
XRP(10'000),
"alice",
"bob", gw);
117 MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}, .maxAmt = 100});
119 auto& app = env.
app();
126 .loadType = loadType,
127 .netOps = app.getOPs(),
128 .ledgerMaster = app.getLedgerMaster(),
143 app.getJobQueue().postCoro(
JtClient,
"RPC-Client", [&](
auto const& coro) {
150 BEAST_EXPECT(!result.
isMember(jss::error));
154 app.getJobQueue().postCoro(
JtClient,
"RPC-Client", [&](
auto const& coro) {
161 BEAST_EXPECT(result.
isMember(jss::error));
167 auto curm =
MPTTester({.env = env, .issuer = alice, .holders = {bob}});
170 app.getJobQueue().postCoro(
JtClient,
"RPC-Client", [&](
auto const& coro) {
177 BEAST_EXPECT(!result.
isMember(jss::error));
180 auto curm =
MPTTester({.env = env, .issuer = alice, .holders = {bob}});
181 app.getJobQueue().postCoro(
JtClient,
"RPC-Client", [&](
auto const& coro) {
188 BEAST_EXPECT(result.
isMember(jss::error));
194 testcase(
"no direct path no intermediary no alternatives");
199 env.
fund(
XRP(10'000),
"alice",
"bob");
201 auto usdm =
MPTTester({.env = env, .issuer =
"bob"});
203 auto const result =
findPaths(env,
"alice",
"bob", usdm(5));
204 BEAST_EXPECT(std::get<0>(result).empty());
210 testcase(
"direct path no intermediary");
213 env.
fund(
XRP(10'000),
"alice",
"bob");
215 MPT const usd =
MPTTester({.env = env, .issuer =
"alice", .holders = {
"bob"}});
220 BEAST_EXPECT(st.empty());
221 BEAST_EXPECT(
equal(sa, usd(5)));
230 auto const gw =
Account(
"gateway");
231 env.
fund(
XRP(10'000),
"alice",
"bob", gw);
232 MPT const usd =
MPTTester({.env = env, .issuer = gw, .holders = {
"alice",
"bob"}});
233 env(
pay(gw,
"alice", usd(70)));
234 env(
pay(
"alice",
"bob", usd(24)));
242 testcase(
"source currency with send_max");
246 auto const alice =
Account(
"alice");
247 auto const bob =
Account(
"bob");
248 auto const gw =
Account(
"gateway");
249 env.
fund(
XRP(10'000), alice, bob, gw);
251 MPT const usd =
MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}});
252 env(
pay(gw, alice, usd(25)));
265 BEAST_EXPECTS(!result.isMember(jss::error), result.toStyledString());
267 auto const& alternatives = result[jss::alternatives];
268 if (BEAST_EXPECT(alternatives.size() == 1))
272 BEAST_EXPECTS(
equal(sa, usd(10)), sa.getFullText());
273 BEAST_EXPECTS(
equal(da, usd(10)), da.getFullText());
280 testcase(
"maxed-out MPT pathfinding");
283 auto hasMPT = [](
auto const& assets,
MPT const& mpt) {
285 return asset.template holds<MPTID>() && asset.template
get<MPTID>() == mpt.mpt();
290 auto const gw =
Account(
"gateway");
291 auto const alice =
Account(
"alice");
292 auto const bob =
Account(
"bob");
293 auto const carol =
Account(
"carol");
295 env.
fund(
XRP(10'000), gw, alice, bob, carol);
299 MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, carol}, .maxAmt = 100});
300 env(
pay(gw, alice, usd(90)));
301 env(
pay(gw, bob, usd(10)));
315 auto [st, sa, da] =
findPaths(env, alice, bob, usd(5));
316 BEAST_EXPECT(st.empty());
317 BEAST_EXPECT(
equal(sa, usd(5)));
318 BEAST_EXPECT(
equal(da, usd(5)));
324 BEAST_EXPECT(sa == usd(5));
326 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
328 auto const& pathElem = st[0][0];
330 pathElem.isOffer() && pathElem.getIssuerID() ==
xrpAccount() &&
341 auto const gw =
Account(
"gateway");
342 env.
fund(
XRP(10'000),
"alice",
"bob", gw);
343 MPT const usd =
MPTTester({.env = env, .issuer = gw, .holders = {
"alice",
"bob"}});
344 env(
pay(gw,
"alice", usd(70)));
345 env(
pay(gw,
"bob", usd(50)));
355 env,
"alice",
"bob", usd(5), std::nullopt, std::nullopt, std::nullopt, domainID);
358 BEAST_EXPECT(st.empty());
359 BEAST_EXPECT(
equal(sa, usd(5)));
360 BEAST_EXPECT(
equal(da, usd(5)));
367 std::string(
"path find consume all") + (domainEnabled ?
" w/ " :
" w/o ") +
"domain");
372 auto const gw =
Account(
"gateway");
373 env.
fund(
XRP(10'000),
"alice",
"bob",
"carol", gw);
374 MPT const usd =
MPTTester({.env = env, .issuer = gw, .holders = {
"bob",
"carol"}});
376 env(
pay(gw,
"carol", usd(100)));
380 domainID =
setupDomain(env, {
"alice",
"bob",
"carol",
"gateway"});
385 env(
offer(
"carol",
XRP(100), usd(100)));
401 BEAST_EXPECT(st.
empty());
411 if (BEAST_EXPECT(st.
size() == 1 && st[0].
size() == 1))
413 auto const& pathElem = st[0][0];
415 pathElem.isOffer() && pathElem.getIssuerID() == gw.id() &&
418 BEAST_EXPECT(sa ==
XRP(100));
419 BEAST_EXPECT(
equal(da, usd(100)));
433 BEAST_EXPECT(st.
empty());
442 std::string(
"alternative path consume best transfer") +
443 (domainEnabled ?
" w/ " :
" w/o ") +
"domain");
446 auto const gw =
Account(
"gateway");
447 auto const gw2 =
Account(
"gateway2");
448 env.
fund(
XRP(10'000),
"alice",
"bob", gw, gw2);
449 MPT const usd =
MPTTester({.env = env, .issuer = gw, .holders = {
"alice",
"bob"}});
451 {.env = env, .issuer = gw2, .holders = {
"alice",
"bob"}, .transferFee = 1'000});
455 domainID =
setupDomain(env, {
"alice",
"bob",
"gateway",
"gateway2"});
456 env(
pay(gw,
"alice", usd(70)),
Domain(*domainID));
457 env(
pay(gw2,
"alice", gw2Usd(70)),
Domain(*domainID));
458 env(
pay(
"alice",
"bob", usd(70)),
Domain(*domainID));
462 env(
pay(gw,
"alice", usd(70)));
463 env(
pay(gw2,
"alice", gw2Usd(70)));
464 env(
pay(
"alice",
"bob", usd(70)));
477 auto const alice =
Account(
"alice");
478 auto const bob =
Account(
"bob");
479 auto const charlie =
Account(
"charlie");
484 env.
fund(
XRP(10'000), alice, bob, charlie, gw);
486 MPT const usd =
MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, charlie}});
487 env(
pay(gw, charlie, usd(10)));
492 domainID =
setupDomain(env, {alice, bob, charlie, gw});
497 env(
offer(charlie,
XRP(10), usd(10)));
501 env, alice, bob, usd(-1),
XRP(100).value(), std::nullopt, std::nullopt, domainID);
502 BEAST_EXPECT(sa ==
XRP(10));
503 BEAST_EXPECT(
equal(da, usd(10)));
504 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
506 auto const& pathElem = st[0][0];
508 pathElem.isOffer() && pathElem.getIssuerID() == gw.id() &&
509 pathElem.getMPTID() == usd.
mpt());
515 env.
fund(
XRP(10'000), alice, bob, charlie, gw);
517 MPT const usd =
MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, charlie}});
518 env(
pay(gw, alice, usd(10)));
523 domainID =
setupDomain(env, {alice, bob, charlie, gw});
528 env(
offer(charlie, usd(10),
XRP(10)));
532 env, alice, bob,
drops(-1), usd(100).value(), std::nullopt, std::nullopt, domainID);
533 BEAST_EXPECT(sa == usd(10));
535 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
537 auto const& pathElem = st[0][0];
539 pathElem.isOffer() && pathElem.getIssuerID() ==
xrpAccount() &&
600 testcase(
"convert_all + send_max: srcAmount governs path ranking");
604 auto const alice =
Account(
"alice");
605 auto const bob =
Account(
"bob");
606 auto const gw =
Account(
"gateway");
607 auto const charlie =
Account(
"charlie");
608 auto const dave =
Account(
"dave");
609 auto const eve =
Account(
"eve");
610 auto const frank =
Account(
"frank");
612 env.
fund(
XRP(10'000), alice, bob, gw, charlie, dave, eve, frank);
619 MPTTester({.env = env, .issuer = gw, .holders = {charlie, dave, eve, frank, bob}});
622 env(
pay(gw, charlie, usd(500)));
623 env(
pay(gw, dave, usd(500)));
624 env(
pay(gw, eve, usd(500)));
625 env(
pay(gw, frank, usd(75)));
629 auto const aud = charlie[
"AUD"];
630 auto const eur = dave[
"EUR"];
631 auto const gbp = eve[
"GBP"];
632 auto const jpy = frank[
"JPY"];
636 env(
offer(charlie,
XRP(1'000), aud(1'000)));
637 env(
offer(charlie, aud(1'000), usd(500)));
638 env(
offer(dave,
XRP(1'000), eur(1'000)));
639 env(
offer(dave, eur(1'000), usd(500)));
640 env(
offer(eve,
XRP(1'000), gbp(1'000)));
641 env(
offer(eve, gbp(1'000), usd(500)));
645 env(
offer(frank,
XRP(50), jpy(50)));
646 env(
offer(frank, jpy(50), usd(75)));
659 BEAST_EXPECT(sa ==
XRP(100));
660 BEAST_EXPECT(
equal(da, usd(100)));
673 for (
auto const domainEnabled : {
false,
true})
TestcaseT testcase
Memberspace for declaring test cases.
bool isMember(char const *key) const
Return true if the object has a member named key.
std::vector< STPath >::size_type size() const
virtual beast::Journal getJournal(std::string const &name)=0
An endpoint that consumes resources.
void pathFindConsumeAll(bool const domainEnabled)
void noDirectPathNoIntermediaryNoAlternatives()
void receiveMax(bool const domainEnabled)
void sourceCurrenciesLimit()
void directPathNoIntermediary()
void paymentAutoPathFind()
void sourceCurrencyWithSendMax()
void run() override
Runs the suite.
void convertAllSendMaxRanking()
void alternativePathsConsumeBestTransfer(bool const domainEnabled)
void maxedOutMPTPathfinding()
void pathFind(bool const domainEnabled)
Immutable cryptographic account descriptor.
A transaction testing environment.
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
beast::Journal const journal
void require(Args const &... args)
Check a set of requirements.
std::shared_ptr< OpenView const > current() const
Returns the current ledger.
bool waitFor(std::chrono::duration< Rep, Period > const &relTime)
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
Converts to MPT Issue or STAmount.
xrpl::MPTID const & mpt() const
@ Array
array value (ordered list)
@ Object
object value (collection of name/value pairs).
Charge const kFeeReferenceRpc
static constexpr int kMaxSrcCur
Maximum number of source currencies allowed in a path find request.
static constexpr int kMaxAutoSrcCur
Maximum number of auto source currencies in a path find request.
static constexpr auto kApiVersionIfUnspecified
Status doCommand(rpc::JsonContext &context, json::Value &result)
Execute an RPC command and store the results in a json::Value.
static json::Value rpf(jtx::Account const &src, jtx::Account const &dst, xrpl::test::jtx::MPT const &usd, std::vector< MPTID > const &numSrc)
json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
XrpT const XRP
Converts to XRP Issue or STAmount.
std::tuple< STPathSet, STAmount, STAmount > findPaths(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax, std::optional< PathAsset > const &srcAsset, std::optional< AccountID > const &srcIssuer, std::optional< UInt256 > const &domain)
json::Value findPathsRequest(jtx::Env &env, jtx::Account const &src, jtx::Account const &dst, STAmount const &saDstAmount, std::optional< STAmount > const &saSendMax, std::optional< PathAsset > const &srcAsset, std::optional< AccountID > const &srcIssuer, std::optional< UInt256 > const &domain)
bool equal(STAmount const &sa1, STAmount const &sa2)
json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
UInt256 setupDomain(jtx::Env &env, std::vector< jtx::Account > const &accounts, jtx::Account const &domainOwner, std::string const &credType)
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
BEAST_DEFINE_TESTSUITE(AMMClawback, app, xrpl)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
T get(Section const §ion, std::string const &name, T const &defaultValue=T{})
Retrieve a key/value pair from a section.
BaseUInt< 256 > Domain
Domain is a 256-bit hash representing a specific domain.
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Currency const & xrpCurrency()
XRP currency.
std::string to_string(BaseUInt< Bits, Tag > const &a)
HashSet< PathAsset > accountDestAssets(AccountID const &account, std::shared_ptr< AssetCache > const &lrCache, bool includeXRP)
STAmount amountFromJson(SField const &name, json::Value const &v)
HashSet< PathAsset > accountSourceAssets(AccountID const &account, std::shared_ptr< AssetCache > const &lrCache, bool includeXRP)
MPTID makeMptID(std::uint32_t const sequence, AccountID const &account)
AccountID const & xrpAccount()
Compute AccountID from public key.
std::shared_ptr< JobQueue::Coro > coro