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:50
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:149
Represents a JSON value.
Definition json_value.h:130
bool isMember(char const *key) const
Return true if the object has a member named key.
A consumption charge.
Definition Charge.h:9
An endpoint that consumes resources.
Definition Consumer.h:15
std::vector< STPath >::size_type size() const
Definition STPathSet.h:528
bool empty() const
Definition STPathSet.h:534
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:17
A transaction testing environment.
Definition Env.h:143
Application & app()
Definition Env.h:280
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:296
beast::Journal const journal
Definition Env.h:184
void require(Args const &... args)
Check a set of requirements.
Definition Env.h:605
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:385
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:25
@ Object
object value (collection of name/value pairs).
Definition json_value.h:26
static constexpr int kMaxAutoSrcCur
Maximum number of auto source currencies in a path find request.
static constexpr int kMaxSrcCur
Maximum number of source currencies allowed in a path find request.
static constexpr auto kApiVersionIfUnspecified
Definition ApiVersion.h:43
Status doCommand(RPC::JsonContext &context, json::Value &result)
Execute an RPC command and store the results in a json::Value.
Charge const kFeeReferenceRpc
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:28
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:47
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:93
Currency const & xrpCurrency()
XRP currency.
Definition UintTypes.cpp:99
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
@ USER
Definition Role.h:24
@ JtClient
Definition Job.h:26
AccountID const & xrpAccount()
Compute AccountID from public key.
MPTID makeMptID(std::uint32_t sequence, AccountID const &account)
Definition Indexes.cpp:172
T push_back(T... args)
T reserve(T... args)
std::shared_ptr< JobQueue::Coro > coro
Definition Context.h:27
json::Value params
Definition Context.h:43
T tie(T... args)