xrpld
Loading...
Searching...
No Matches
PathMPT_test.cpp
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>
12
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/RPCHelpers.h>
18#include <xrpld/rpc/detail/Tuning.h>
19
20#include <xrpl/basics/base_uint.h>
21#include <xrpl/beast/unit_test/suite.h>
22#include <xrpl/core/Job.h>
23#include <xrpl/core/JobQueue.h>
24#include <xrpl/json/json_value.h>
25#include <xrpl/protocol/AccountID.h>
26#include <xrpl/protocol/ApiVersion.h>
27#include <xrpl/protocol/Indexes.h>
28#include <xrpl/protocol/STAmount.h>
29#include <xrpl/protocol/STPathSet.h>
30#include <xrpl/protocol/UintTypes.h>
31#include <xrpl/protocol/jss.h>
32#include <xrpl/resource/Charge.h>
33#include <xrpl/resource/Consumer.h>
34#include <xrpl/resource/Fees.h>
35
36#include <cstdint>
37#include <memory>
38#include <optional>
39#include <tuple>
40#include <vector>
41
42namespace xrpl::test {
43namespace detail {
44
45static json::Value
46rpf(jtx::Account const& src,
47 jtx::Account const& dst,
48 xrpl::test::jtx::MPT const& usd,
49 std::vector<MPTID> const& numSrc)
50{
52 jv[jss::command] = "ripple_path_find";
53 jv[jss::source_account] = toBase58(src);
54
55 if (!numSrc.empty())
56 {
57 auto& sc = (jv[jss::source_currencies] = json::ValueType::Array);
59 for (auto const& id : numSrc)
60 {
61 j[jss::mpt_issuance_id] = to_string(id);
62 sc.append(j);
63 }
64 }
65
66 auto const d = toBase58(dst);
67 jv[jss::destination_account] = d;
68
69 json::Value& j = (jv[jss::destination_amount] = json::ValueType::Object);
70 j[jss::mpt_issuance_id] = to_string(usd.mpt());
71 j[jss::value] = "1";
72
73 return jv;
74}
75
76} // namespace detail
77
78//------------------------------------------------------------------------------
79
81{
84 {
85 // These tests were originally written with search parameters that are
86 // different from the current defaults. This function creates an env
87 // with the search parameters that the tests were written for.
88 using namespace jtx;
89 return Env(*this, envconfig([](std::unique_ptr<Config> cfg) {
90 cfg->pathSearchOld = 7;
91 cfg->pathSearch = 7;
92 cfg->pathSearchMax = 10;
93 return cfg;
94 }));
95 }
96
97public:
98 void
100 {
101 testcase("source currency limits");
102 using namespace std::chrono_literals;
103 using namespace jtx;
104 Env env = pathTestEnv();
105 auto const gw = Account("gateway");
106 auto const alice = Account("alice");
107 auto const bob = Account("bob");
108
109 env.fund(XRP(10'000), "alice", "bob", gw);
110
111 MPT const usd =
112 MPTTester({.env = env, .issuer = gw, .holders = {alice, bob}, .maxAmt = 100});
113
114 auto& app = env.app();
117
118 rpc::JsonContext context{
119 {.j = env.journal,
120 .app = app,
121 .loadType = loadType,
122 .netOps = app.getOPs(),
123 .ledgerMaster = app.getLedgerMaster(),
124 .consumer = c,
125 .role = Role::USER,
126 .coro = {},
127 .infoSub = {},
128 .apiVersion = rpc::kApiVersionIfUnspecified},
129 {},
130 {}};
131 json::Value result;
132 Gate g;
133 // Test rpc::tuning::max_src_cur source currencies.
134 std::vector<MPTID> numSrc;
136 for (std::uint8_t i = 0; i < rpc::tuning::kMaxSrcCur; ++i)
137 numSrc.push_back(makeMptID(i, bob));
138 app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) {
139 context.params = xrpl::test::detail::rpf(alice, bob, usd, numSrc);
140 context.coro = coro;
141 rpc::doCommand(context, result);
142 g.signal();
143 });
144 BEAST_EXPECT(g.waitFor(5s));
145 BEAST_EXPECT(!result.isMember(jss::error));
146
147 // Test more than rpc::tuning::max_src_cur source currencies.
149 app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) {
150 context.params = xrpl::test::detail::rpf(alice, bob, usd, numSrc);
151 context.coro = coro;
152 rpc::doCommand(context, result);
153 g.signal();
154 });
155 BEAST_EXPECT(g.waitFor(5s));
156 BEAST_EXPECT(result.isMember(jss::error));
157
158 // Test rpc::tuning::max_auto_src_cur source currencies.
159 numSrc.clear();
160 for (auto i = 0; i < (rpc::tuning::kMaxAutoSrcCur - 1); ++i)
161 {
162 auto curm = MPTTester({.env = env, .issuer = alice, .holders = {bob}});
163 numSrc.push_back(curm.issuanceID());
164 }
165 app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) {
166 context.params = xrpl::test::detail::rpf(alice, bob, usd, {});
167 context.coro = coro;
168 rpc::doCommand(context, result);
169 g.signal();
170 });
171 BEAST_EXPECT(g.waitFor(5s));
172 BEAST_EXPECT(!result.isMember(jss::error));
173
174 // Test more than rpc::tuning::max_auto_src_cur source currencies.
175 auto curm = MPTTester({.env = env, .issuer = alice, .holders = {bob}});
176 app.getJobQueue().postCoro(JtClient, "RPC-Client", [&](auto const& coro) {
177 context.params = xrpl::test::detail::rpf(alice, bob, usd, {});
178 context.coro = coro;
179 rpc::doCommand(context, result);
180 g.signal();
181 });
182 BEAST_EXPECT(g.waitFor(5s));
183 BEAST_EXPECT(result.isMember(jss::error));
184 }
185
186 void
188 {
189 testcase("no direct path no intermediary no alternatives");
190 using namespace jtx;
191
192 Env env = pathTestEnv();
193
194 env.fund(XRP(10'000), "alice", "bob");
195
196 auto usdm = MPTTester({.env = env, .issuer = "bob"});
197
198 auto const result = findPaths(env, "alice", "bob", usdm(5));
199 BEAST_EXPECT(std::get<0>(result).empty());
200 }
201
202 void
204 {
205 testcase("direct path no intermediary");
206 using namespace jtx;
207 Env env = pathTestEnv();
208 env.fund(XRP(10'000), "alice", "bob");
209
210 MPT const usd = MPTTester({.env = env, .issuer = "alice", .holders = {"bob"}});
211
212 STPathSet st;
213 STAmount sa;
214 std::tie(st, sa, std::ignore) = findPaths(env, "alice", "bob", usd(5));
215 BEAST_EXPECT(st.empty());
216 BEAST_EXPECT(equal(sa, usd(5)));
217 }
218
219 void
221 {
222 testcase("payment auto path find");
223 using namespace jtx;
224 Env env = pathTestEnv();
225 auto const gw = Account("gateway");
226 env.fund(XRP(10'000), "alice", "bob", gw);
227 MPT const usd = MPTTester({.env = env, .issuer = gw, .holders = {"alice", "bob"}});
228 env(pay(gw, "alice", usd(70)));
229 env(pay("alice", "bob", usd(24)));
230 env.require(Balance("alice", usd(46)));
231 env.require(Balance("bob", usd(24)));
232 }
233
234 void
235 pathFind(bool const domainEnabled)
236 {
237 testcase(std::string("path find") + (domainEnabled ? " w/ " : " w/o ") + "domain");
238 using namespace jtx;
239 Env env = pathTestEnv();
240 auto const gw = Account("gateway");
241 env.fund(XRP(10'000), "alice", "bob", gw);
242 MPT const usd = MPTTester({.env = env, .issuer = gw, .holders = {"alice", "bob"}});
243 env(pay(gw, "alice", usd(70)));
244 env(pay(gw, "bob", usd(50)));
245
246 std::optional<uint256> domainID;
247 if (domainEnabled)
248 domainID = setupDomain(env, {"alice", "bob", gw});
249
250 STPathSet st;
251 STAmount sa;
252 STAmount da;
253 std::tie(st, sa, da) = findPaths(
254 env, "alice", "bob", usd(5), std::nullopt, std::nullopt, std::nullopt, domainID);
255 // Note, a direct IOU payment will have "gateway" as alternative path
256 // since IOU supports rippling
257 BEAST_EXPECT(st.empty());
258 BEAST_EXPECT(equal(sa, usd(5)));
259 BEAST_EXPECT(equal(da, usd(5)));
260 }
261
262 void
263 pathFindConsumeAll(bool const domainEnabled)
264 {
265 testcase(
266 std::string("path find consume all") + (domainEnabled ? " w/ " : " w/o ") + "domain");
267 using namespace jtx;
268
269 {
270 Env env = pathTestEnv();
271 auto const gw = Account("gateway");
272 env.fund(XRP(10'000), "alice", "bob", "carol", gw);
273 MPT const usd = MPTTester({.env = env, .issuer = gw, .holders = {"bob", "carol"}});
274 MPT const aud(makeMptID(0, gw));
275 env(pay(gw, "carol", usd(100)));
276 std::optional<uint256> domainID;
277 if (domainEnabled)
278 {
279 domainID = setupDomain(env, {"alice", "bob", "carol", "gateway"});
280 env(offer("carol", XRP(100), usd(100)), Domain(*domainID));
281 }
282 else
283 {
284 env(offer("carol", XRP(100), usd(100)));
285 }
286 env.close();
287
288 STPathSet st;
289 STAmount sa;
290 STAmount da;
291 std::tie(st, sa, da) = findPaths(
292 env,
293 "alice",
294 "bob",
295 aud(-1),
296 std::optional<STAmount>(XRP(100'000'000)),
297 std::nullopt,
298 std::nullopt,
299 domainID);
300 BEAST_EXPECT(st.empty());
301 std::tie(st, sa, da) = findPaths(
302 env,
303 "alice",
304 "bob",
305 usd(-1),
306 std::optional<STAmount>(XRP(100'000'000)),
307 std::nullopt,
308 std::nullopt,
309 domainID);
310 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
311 {
312 auto const& pathElem = st[0][0];
313 BEAST_EXPECT(
314 pathElem.isOffer() && pathElem.getIssuerID() == gw.id() &&
315 pathElem.getMPTID() == usd.issuanceID);
316 }
317 BEAST_EXPECT(sa == XRP(100));
318 BEAST_EXPECT(equal(da, usd(100)));
319
320 // if domain is used, finding path in the open offerbook will return
321 // empty result
322 if (domainEnabled)
323 {
324 std::tie(st, sa, da) = findPaths(
325 env,
326 "alice",
327 "bob",
328 Account("bob")["USD"](-1),
330 std::nullopt,
331 std::nullopt); // not specifying a domain
332 BEAST_EXPECT(st.empty());
333 }
334 }
335 }
336
337 void
338 alternativePathsConsumeBestTransfer(bool const domainEnabled)
339 {
340 testcase(
341 std::string("alternative path consume best transfer") +
342 (domainEnabled ? " w/ " : " w/o ") + "domain");
343 using namespace jtx;
344 Env env = pathTestEnv();
345 auto const gw = Account("gateway");
346 auto const gw2 = Account("gateway2");
347 env.fund(XRP(10'000), "alice", "bob", gw, gw2);
348 MPT const usd = MPTTester({.env = env, .issuer = gw, .holders = {"alice", "bob"}});
349 MPT const gw2Usd = MPTTester(
350 {.env = env, .issuer = gw2, .holders = {"alice", "bob"}, .transferFee = 1'000});
351 std::optional<uint256> domainID;
352 if (domainEnabled)
353 {
354 domainID = setupDomain(env, {"alice", "bob", "gateway", "gateway2"});
355 env(pay(gw, "alice", usd(70)), Domain(*domainID));
356 env(pay(gw2, "alice", gw2Usd(70)), Domain(*domainID));
357 env(pay("alice", "bob", usd(70)), Domain(*domainID));
358 }
359 else
360 {
361 env(pay(gw, "alice", usd(70)));
362 env(pay(gw2, "alice", gw2Usd(70)));
363 env(pay("alice", "bob", usd(70)));
364 }
365 env.require(Balance("alice", usd(0)));
366 env.require(Balance("alice", gw2Usd(70)));
367 env.require(Balance("bob", usd(70)));
368 env.require(Balance("bob", gw2Usd(0)));
369 }
370
371 void
372 receiveMax(bool const domainEnabled)
373 {
374 testcase(std::string("Receive max") + (domainEnabled ? " w/ " : " w/o ") + "domain");
375 using namespace jtx;
376 auto const alice = Account("alice");
377 auto const bob = Account("bob");
378 auto const charlie = Account("charlie");
379 auto const gw = Account("gw");
380 {
381 // XRP -> MPT receive max
382 Env env = pathTestEnv();
383 env.fund(XRP(10'000), alice, bob, charlie, gw);
384 env.close();
385 MPT const usd = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, charlie}});
386 env(pay(gw, charlie, usd(10)));
387 env.close();
388 std::optional<uint256> domainID;
389 if (domainEnabled)
390 {
391 domainID = setupDomain(env, {alice, bob, charlie, gw});
392 env(offer(charlie, XRP(10), usd(10)), Domain(*domainID));
393 }
394 else
395 {
396 env(offer(charlie, XRP(10), usd(10)));
397 }
398 env.close();
399 auto [st, sa, da] = findPaths(
400 env, alice, bob, usd(-1), XRP(100).value(), std::nullopt, std::nullopt, domainID);
401 BEAST_EXPECT(sa == XRP(10));
402 BEAST_EXPECT(equal(da, usd(10)));
403 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
404 {
405 auto const& pathElem = st[0][0];
406 BEAST_EXPECT(
407 pathElem.isOffer() && pathElem.getIssuerID() == gw.id() &&
408 pathElem.getMPTID() == usd.mpt());
409 }
410 }
411 {
412 // MPT -> XRP receive max
413 Env env = pathTestEnv();
414 env.fund(XRP(10'000), alice, bob, charlie, gw);
415 env.close();
416 MPT const usd = MPTTester({.env = env, .issuer = gw, .holders = {alice, bob, charlie}});
417 env(pay(gw, alice, usd(10)));
418 env.close();
419 std::optional<uint256> domainID;
420 if (domainEnabled)
421 {
422 domainID = setupDomain(env, {alice, bob, charlie, gw});
423 env(offer(charlie, usd(10), XRP(10)), Domain(*domainID));
424 }
425 else
426 {
427 env(offer(charlie, usd(10), XRP(10)));
428 }
429 env.close();
430 auto [st, sa, da] = findPaths(
431 env, alice, bob, drops(-1), usd(100).value(), std::nullopt, std::nullopt, domainID);
432 BEAST_EXPECT(sa == usd(10));
433 BEAST_EXPECT(equal(da, XRP(10)));
434 if (BEAST_EXPECT(st.size() == 1 && st[0].size() == 1))
435 {
436 auto const& pathElem = st[0][0];
437 BEAST_EXPECT(
438 pathElem.isOffer() && pathElem.getIssuerID() == xrpAccount() &&
439 pathElem.getCurrency() == xrpCurrency());
440 }
441 }
442 }
443
444 void
445 run() override
446 {
451 for (auto const domainEnabled : {false, true})
452 {
453 pathFind(domainEnabled);
454 pathFindConsumeAll(domainEnabled);
456 receiveMax(domainEnabled);
457 }
458 }
459};
460
462
463} // namespace xrpl::test
A testsuite class.
Definition suite.h:52
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
Represents a JSON value.
Definition json_value.h:117
bool isMember(char const *key) const
Return true if the object has a member named key.
std::vector< STPath >::size_type size() const
Definition STPathSet.h:537
bool empty() const
Definition STPathSet.h:543
A consumption charge.
Definition Charge.h:13
An endpoint that consumes resources.
Definition Consumer.h:20
void pathFindConsumeAll(bool const domainEnabled)
void noDirectPathNoIntermediaryNoAlternatives()
void receiveMax(bool const domainEnabled)
void run() override
Runs the suite.
void alternativePathsConsumeBestTransfer(bool const domainEnabled)
void pathFind(bool const domainEnabled)
Immutable cryptographic account descriptor.
Definition jtx/Account.h:21
A transaction testing environment.
Definition Env.h:161
Application & app()
Definition Env.h:300
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:323
beast::Journal const journal
Definition Env.h:204
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:764
bool waitFor(std::chrono::duration< Rep, Period > const &relTime)
Test helper for creating, mutating, and asserting MPT and confidential MPT ledger state.
Definition mpt.h:447
Converts to MPT Issue or STAmount.
xrpl::MPTID const & mpt() const
T clear(T... args)
T empty(T... args)
@ Array
array value (ordered list)
Definition json_value.h:28
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
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
Definition ApiVersion.h:44
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.
Definition pay.cpp:14
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
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)
uint256 setupDomain(jtx::Env &env, std::vector< jtx::Account > const &accounts, jtx::Account const &domainOwner, std::string const &credType)
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.
Definition offer.cpp:14
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:37
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.
Definition algorithm.h:5
BaseUInt< 256 > Domain
Domain is a 256-bit hash representing a specific domain.
Definition UintTypes.h:59
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
Currency const & xrpCurrency()
XRP currency.
Definition UintTypes.cpp:99
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:651
@ USER
Definition Role.h:27
@ JtClient
Definition Job.h:31
MPTID makeMptID(std::uint32_t const sequence, AccountID const &account)
Definition Indexes.cpp:184
AccountID const & xrpAccount()
Compute AccountID from public key.
T push_back(T... args)
T reserve(T... args)
std::shared_ptr< JobQueue::Coro > coro
Definition Context.h:35
json::Value params
Definition Context.h:51
T tie(T... args)