rippled
Loading...
Searching...
No Matches
AccountLines_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2016 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21
22#include <xrpl/beast/unit_test.h>
23#include <xrpl/protocol/ErrorCodes.h>
24#include <xrpl/protocol/TxFlags.h>
25#include <xrpl/protocol/jss.h>
26
27namespace ripple {
28namespace RPC {
29
31{
32public:
33 void
35 {
36 testcase("account_lines");
37
38 using namespace test::jtx;
39 Env env(*this);
40 {
41 // account_lines with no account.
42 auto const lines = env.rpc("json", "account_lines", "{ }");
43 BEAST_EXPECT(
44 lines[jss::result][jss::error_message] ==
45 RPC::missing_field_error(jss::account)[jss::error_message]);
46 }
47 {
48 // account_lines with a malformed account.
49 Json::Value params;
50 params[jss::account] =
51 "n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj";
52 auto const lines =
53 env.rpc("json", "account_lines", to_string(params));
54 BEAST_EXPECT(
55 lines[jss::result][jss::error_message] ==
56 RPC::make_error(rpcACT_MALFORMED)[jss::error_message]);
57 }
58 {
59 // test account non-string
60 auto testInvalidAccountParam = [&](auto const& param) {
61 Json::Value params;
62 params[jss::account] = param;
63 auto jrr = env.rpc(
64 "json", "account_lines", to_string(params))[jss::result];
65 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
66 BEAST_EXPECT(
67 jrr[jss::error_message] == "Invalid field 'account'.");
68 };
69
70 testInvalidAccountParam(1);
71 testInvalidAccountParam(1.1);
72 testInvalidAccountParam(true);
73 testInvalidAccountParam(Json::Value(Json::nullValue));
74 testInvalidAccountParam(Json::Value(Json::objectValue));
75 testInvalidAccountParam(Json::Value(Json::arrayValue));
76 }
77 Account const alice{"alice"};
78 {
79 // account_lines on an unfunded account.
80 Json::Value params;
81 params[jss::account] = alice.human();
82 auto const lines =
83 env.rpc("json", "account_lines", to_string(params));
84 BEAST_EXPECT(
85 lines[jss::result][jss::error_message] ==
86 RPC::make_error(rpcACT_NOT_FOUND)[jss::error_message]);
87 }
88 env.fund(XRP(10000), alice);
89 env.close();
90 LedgerInfo const ledger3Info = env.closed()->info();
91 BEAST_EXPECT(ledger3Info.seq == 3);
92
93 {
94 // alice is funded but has no lines. An empty array is returned.
95 Json::Value params;
96 params[jss::account] = alice.human();
97 auto const lines =
98 env.rpc("json", "account_lines", to_string(params));
99 BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
100 BEAST_EXPECT(lines[jss::result][jss::lines].size() == 0);
101 }
102 {
103 // Specify a ledger that doesn't exist.
104 Json::Value params;
105 params[jss::account] = alice.human();
106 params[jss::ledger_index] = "nonsense";
107 auto const lines =
108 env.rpc("json", "account_lines", to_string(params));
109 BEAST_EXPECT(
110 lines[jss::result][jss::error_message] ==
111 "ledgerIndexMalformed");
112 }
113 {
114 // Specify a different ledger that doesn't exist.
115 Json::Value params;
116 params[jss::account] = alice.human();
117 params[jss::ledger_index] = 50000;
118 auto const lines =
119 env.rpc("json", "account_lines", to_string(params));
120 BEAST_EXPECT(
121 lines[jss::result][jss::error_message] == "ledgerNotFound");
122 }
123 // Create trust lines to share with alice.
124 Account const gw1{"gw1"};
125 env.fund(XRP(10000), gw1);
126 std::vector<IOU> gw1Currencies;
127
128 for (char c = 0; c <= ('Z' - 'A'); ++c)
129 {
130 // gw1 currencies have names "YAA" -> "YAZ".
131 gw1Currencies.push_back(
132 gw1[std::string("YA") + static_cast<char>('A' + c)]);
133 IOU const& gw1Currency = gw1Currencies.back();
134
135 // Establish trust lines.
136 env(trust(alice, gw1Currency(100 + c)));
137 env(pay(gw1, alice, gw1Currency(50 + c)));
138 }
139 env.close();
140 LedgerInfo const ledger4Info = env.closed()->info();
141 BEAST_EXPECT(ledger4Info.seq == 4);
142
143 // Add another set of trust lines in another ledger so we can see
144 // differences in historic ledgers.
145 Account const gw2{"gw2"};
146 env.fund(XRP(10000), gw2);
147
148 // gw2 requires authorization.
149 env(fset(gw2, asfRequireAuth));
150 env.close();
151 std::vector<IOU> gw2Currencies;
152
153 for (char c = 0; c <= ('Z' - 'A'); ++c)
154 {
155 // gw2 currencies have names "ZAA" -> "ZAZ".
156 gw2Currencies.push_back(
157 gw2[std::string("ZA") + static_cast<char>('A' + c)]);
158 IOU const& gw2Currency = gw2Currencies.back();
159
160 // Establish trust lines.
161 env(trust(alice, gw2Currency(200 + c)));
162 env(trust(gw2, gw2Currency(0), alice, tfSetfAuth));
163 env.close();
164 env(pay(gw2, alice, gw2Currency(100 + c)));
165 env.close();
166
167 // Set flags on gw2 trust lines so we can look for them.
168 env(trust(
169 alice,
170 gw2Currency(0),
171 gw2,
173 }
174 env.close();
175 LedgerInfo const ledger58Info = env.closed()->info();
176 BEAST_EXPECT(ledger58Info.seq == 58);
177
178 // A re-usable test for historic ledgers.
179 auto testAccountLinesHistory = [this, &env](
180 Account const& account,
181 LedgerInfo const& info,
182 int count) {
183 // Get account_lines by ledger index.
184 Json::Value paramsSeq;
185 paramsSeq[jss::account] = account.human();
186 paramsSeq[jss::ledger_index] = info.seq;
187 auto const linesSeq =
188 env.rpc("json", "account_lines", to_string(paramsSeq));
189 BEAST_EXPECT(linesSeq[jss::result][jss::lines].isArray());
190 BEAST_EXPECT(linesSeq[jss::result][jss::lines].size() == count);
191
192 // Get account_lines by ledger hash.
193 Json::Value paramsHash;
194 paramsHash[jss::account] = account.human();
195 paramsHash[jss::ledger_hash] = to_string(info.hash);
196 auto const linesHash =
197 env.rpc("json", "account_lines", to_string(paramsHash));
198 BEAST_EXPECT(linesHash[jss::result][jss::lines].isArray());
199 BEAST_EXPECT(linesHash[jss::result][jss::lines].size() == count);
200 };
201
202 // Alice should have no trust lines in ledger 3.
203 testAccountLinesHistory(alice, ledger3Info, 0);
204
205 // Alice should have 26 trust lines in ledger 4.
206 testAccountLinesHistory(alice, ledger4Info, 26);
207
208 // Alice should have 52 trust lines in ledger 58.
209 testAccountLinesHistory(alice, ledger58Info, 52);
210
211 {
212 // Surprisingly, it's valid to specify both index and hash, in
213 // which case the hash wins.
214 Json::Value params;
215 params[jss::account] = alice.human();
216 params[jss::ledger_hash] = to_string(ledger4Info.hash);
217 params[jss::ledger_index] = ledger58Info.seq;
218 auto const lines =
219 env.rpc("json", "account_lines", to_string(params));
220 BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
221 BEAST_EXPECT(lines[jss::result][jss::lines].size() == 26);
222 }
223 {
224 // alice should have 52 trust lines in the current ledger.
225 Json::Value params;
226 params[jss::account] = alice.human();
227 auto const lines =
228 env.rpc("json", "account_lines", to_string(params));
229 BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
230 BEAST_EXPECT(lines[jss::result][jss::lines].size() == 52);
231 }
232 {
233 // alice should have 26 trust lines with gw1.
234 Json::Value params;
235 params[jss::account] = alice.human();
236 params[jss::peer] = gw1.human();
237 auto const lines =
238 env.rpc("json", "account_lines", to_string(params));
239 BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
240 BEAST_EXPECT(lines[jss::result][jss::lines].size() == 26);
241
242 // Check no ripple is not set for trustlines between alice and gw1
243 auto const& line = lines[jss::result][jss::lines][0u];
244 BEAST_EXPECT(!line[jss::no_ripple].isMember(jss::no_ripple));
245 }
246 {
247 // Use a malformed peer.
248 Json::Value params;
249 params[jss::account] = alice.human();
250 params[jss::peer] =
251 "n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj";
252 auto const lines =
253 env.rpc("json", "account_lines", to_string(params));
254 BEAST_EXPECT(
255 lines[jss::result][jss::error_message] ==
256 RPC::make_error(rpcACT_MALFORMED)[jss::error_message]);
257 }
258 {
259 // A negative limit should fail.
260 Json::Value params;
261 params[jss::account] = alice.human();
262 params[jss::limit] = -1;
263 auto const lines =
264 env.rpc("json", "account_lines", to_string(params));
265 BEAST_EXPECT(
266 lines[jss::result][jss::error_message] ==
267 RPC::expected_field_message(jss::limit, "unsigned integer"));
268 }
269 {
270 // Limit the response to 1 trust line.
271 Json::Value paramsA;
272 paramsA[jss::account] = alice.human();
273 paramsA[jss::limit] = 1;
274 auto const linesA =
275 env.rpc("json", "account_lines", to_string(paramsA));
276 BEAST_EXPECT(linesA[jss::result][jss::lines].isArray());
277 BEAST_EXPECT(linesA[jss::result][jss::lines].size() == 1);
278
279 // Pick up from where the marker left off. We should get 51.
280 auto marker = linesA[jss::result][jss::marker].asString();
281 Json::Value paramsB;
282 paramsB[jss::account] = alice.human();
283 paramsB[jss::marker] = marker;
284 auto const linesB =
285 env.rpc("json", "account_lines", to_string(paramsB));
286 BEAST_EXPECT(linesB[jss::result][jss::lines].isArray());
287 BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 51);
288
289 // Go again from where the marker left off, but set a limit of 3.
290 Json::Value paramsC;
291 paramsC[jss::account] = alice.human();
292 paramsC[jss::limit] = 3;
293 paramsC[jss::marker] = marker;
294 auto const linesC =
295 env.rpc("json", "account_lines", to_string(paramsC));
296 BEAST_EXPECT(linesC[jss::result][jss::lines].isArray());
297 BEAST_EXPECT(linesC[jss::result][jss::lines].size() == 3);
298
299 // Mess with the marker so it becomes bad and check for the error.
300 marker[5] = marker[5] == '7' ? '8' : '7';
301 Json::Value paramsD;
302 paramsD[jss::account] = alice.human();
303 paramsD[jss::marker] = marker;
304 auto const linesD =
305 env.rpc("json", "account_lines", to_string(paramsD));
306 BEAST_EXPECT(
307 linesD[jss::result][jss::error_message] ==
308 RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
309 }
310 {
311 // A non-string marker should also fail.
312 Json::Value params;
313 params[jss::account] = alice.human();
314 params[jss::marker] = true;
315 auto const lines =
316 env.rpc("json", "account_lines", to_string(params));
317 BEAST_EXPECT(
318 lines[jss::result][jss::error_message] ==
319 RPC::expected_field_message(jss::marker, "string"));
320 }
321 {
322 // Check that the flags we expect from alice to gw2 are present.
323 Json::Value params;
324 params[jss::account] = alice.human();
325 params[jss::limit] = 10;
326 params[jss::peer] = gw2.human();
327 auto const lines =
328 env.rpc("json", "account_lines", to_string(params));
329 auto const& line = lines[jss::result][jss::lines][0u];
330 BEAST_EXPECT(line[jss::freeze].asBool() == true);
331 BEAST_EXPECT(line[jss::deep_freeze].asBool() == true);
332 BEAST_EXPECT(line[jss::no_ripple].asBool() == true);
333 BEAST_EXPECT(line[jss::peer_authorized].asBool() == true);
334 }
335 {
336 // Check that the flags we expect from gw2 to alice are present.
337 Json::Value paramsA;
338 paramsA[jss::account] = gw2.human();
339 paramsA[jss::limit] = 1;
340 paramsA[jss::peer] = alice.human();
341 auto const linesA =
342 env.rpc("json", "account_lines", to_string(paramsA));
343 auto const& lineA = linesA[jss::result][jss::lines][0u];
344 BEAST_EXPECT(lineA[jss::freeze_peer].asBool() == true);
345 BEAST_EXPECT(lineA[jss::deep_freeze_peer].asBool() == true);
346 BEAST_EXPECT(lineA[jss::no_ripple_peer].asBool() == true);
347 BEAST_EXPECT(lineA[jss::authorized].asBool() == true);
348
349 // Continue from the returned marker to make sure that works.
350 BEAST_EXPECT(linesA[jss::result].isMember(jss::marker));
351 auto const marker = linesA[jss::result][jss::marker].asString();
352 Json::Value paramsB;
353 paramsB[jss::account] = gw2.human();
354 paramsB[jss::limit] = 25;
355 paramsB[jss::marker] = marker;
356 paramsB[jss::peer] = alice.human();
357 auto const linesB =
358 env.rpc("json", "account_lines", to_string(paramsB));
359 BEAST_EXPECT(linesB[jss::result][jss::lines].isArray());
360 BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 25);
361 BEAST_EXPECT(!linesB[jss::result].isMember(jss::marker));
362 }
363 }
364
365 void
367 {
368 testcase("Entry pointed to by marker is not owned by account");
369 using namespace test::jtx;
370 Env env(*this);
371
372 // The goal of this test is observe account_lines RPC calls return an
373 // error message when the SLE pointed to by the marker is not owned by
374 // the Account being traversed.
375 //
376 // To start, we'll create an environment with some trust lines, offers
377 // and a signers list.
378 Account const alice{"alice"};
379 Account const becky{"becky"};
380 Account const gw1{"gw1"};
381 env.fund(XRP(10000), alice, becky, gw1);
382 env.close();
383
384 // Give alice a SignerList.
385 Account const bogie{"bogie"};
386 env(signers(alice, 2, {{bogie, 3}}));
387 env.close();
388
389 auto const EUR = gw1["EUR"];
390 env(trust(alice, EUR(200)));
391 env(trust(becky, EUR(200)));
392 env.close();
393
394 // Get all account objects for alice and verify that her
395 // signerlist is first. This is only a (reliable) coincidence of
396 // object naming. So if any of alice's objects are renamed this
397 // may fail.
398 Json::Value aliceObjectsParams;
399 aliceObjectsParams[jss::account] = alice.human();
400 aliceObjectsParams[jss::limit] = 10;
401 Json::Value const aliceObjects =
402 env.rpc("json", "account_objects", to_string(aliceObjectsParams));
403 Json::Value const& aliceSignerList =
404 aliceObjects[jss::result][jss::account_objects][0u];
405 if (!(aliceSignerList[sfLedgerEntryType.jsonName] == jss::SignerList))
406 {
407 fail(
408 "alice's account objects are misordered. "
409 "Please reorder the objects so the SignerList is first.",
410 __FILE__,
411 __LINE__);
412 return;
413 }
414
415 // Get account_lines for alice. Limit at 1, so we get a marker
416 // pointing to her SignerList.
417 Json::Value aliceLines1Params;
418 aliceLines1Params[jss::account] = alice.human();
419 aliceLines1Params[jss::limit] = 1;
420 auto const aliceLines1 =
421 env.rpc("json", "account_lines", to_string(aliceLines1Params));
422 BEAST_EXPECT(aliceLines1[jss::result].isMember(jss::marker));
423
424 // Verify that the marker points at the signer list.
425 std::string const aliceMarker =
426 aliceLines1[jss::result][jss::marker].asString();
427 std::string const markerIndex =
428 aliceMarker.substr(0, aliceMarker.find(','));
429 BEAST_EXPECT(markerIndex == aliceSignerList[jss::index].asString());
430
431 // When we fetch Alice's remaining lines we should find one and no more.
432 Json::Value aliceLines2Params;
433 aliceLines2Params[jss::account] = alice.human();
434 aliceLines2Params[jss::marker] = aliceMarker;
435 auto const aliceLines2 =
436 env.rpc("json", "account_lines", to_string(aliceLines2Params));
437 BEAST_EXPECT(aliceLines2[jss::result][jss::lines].size() == 1);
438 BEAST_EXPECT(!aliceLines2[jss::result].isMember(jss::marker));
439
440 // Get account lines for beckys account, using alices SignerList as a
441 // marker. This should cause an error.
442 Json::Value beckyLinesParams;
443 beckyLinesParams[jss::account] = becky.human();
444 beckyLinesParams[jss::marker] = aliceMarker;
445 auto const beckyLines =
446 env.rpc("json", "account_lines", to_string(beckyLinesParams));
447 BEAST_EXPECT(beckyLines[jss::result].isMember(jss::error_message));
448 }
449
450 void
452 {
453 testcase("Entry pointed to by marker is removed");
454 using namespace test::jtx;
455 Env env(*this);
456
457 // The goal here is to observe account_lines marker behavior if the
458 // entry pointed at by a returned marker is removed from the ledger.
459 //
460 // It isn't easy to explicitly delete a trust line, so we do so in a
461 // round-about fashion. It takes 4 actors:
462 // o Gateway gw1 issues USD
463 // o alice offers to buy 100 USD for 100 XRP.
464 // o becky offers to sell 100 USD for 100 XRP.
465 // There will now be an inferred trustline between alice and gw1.
466 // o alice pays her 100 USD to cheri.
467 // alice should now have no USD and no trustline to gw1.
468 Account const alice{"alice"};
469 Account const becky{"becky"};
470 Account const cheri{"cheri"};
471 Account const gw1{"gw1"};
472 Account const gw2{"gw2"};
473 env.fund(XRP(10000), alice, becky, cheri, gw1, gw2);
474 env.close();
475
476 auto const USD = gw1["USD"];
477 auto const AUD = gw1["AUD"];
478 auto const EUR = gw2["EUR"];
479 env(trust(alice, USD(200)));
480 env(trust(alice, AUD(200)));
481 env(trust(becky, EUR(200)));
482 env(trust(cheri, EUR(200)));
483 env.close();
484
485 // becky gets 100 USD from gw1.
486 env(pay(gw2, becky, EUR(100)));
487 env.close();
488
489 // alice offers to buy 100 EUR for 100 XRP.
490 env(offer(alice, EUR(100), XRP(100)));
491 env.close();
492
493 // becky offers to buy 100 XRP for 100 EUR.
494 env(offer(becky, XRP(100), EUR(100)));
495 env.close();
496
497 // Get account_lines for alice. Limit at 1, so we get a marker.
498 Json::Value linesBegParams;
499 linesBegParams[jss::account] = alice.human();
500 linesBegParams[jss::limit] = 2;
501 auto const linesBeg =
502 env.rpc("json", "account_lines", to_string(linesBegParams));
503 BEAST_EXPECT(
504 linesBeg[jss::result][jss::lines][0u][jss::currency] == "USD");
505 BEAST_EXPECT(linesBeg[jss::result].isMember(jss::marker));
506
507 // alice pays 100 EUR to cheri.
508 env(pay(alice, cheri, EUR(100)));
509 env.close();
510
511 // Since alice paid all her EUR to cheri, alice should no longer
512 // have a trust line to gw1. So the old marker should now be invalid.
513 Json::Value linesEndParams;
514 linesEndParams[jss::account] = alice.human();
515 linesEndParams[jss::marker] = linesBeg[jss::result][jss::marker];
516 auto const linesEnd =
517 env.rpc("json", "account_lines", to_string(linesEndParams));
518 BEAST_EXPECT(
519 linesEnd[jss::result][jss::error_message] ==
520 RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
521 }
522
523 void
525 {
526 testcase("Marker can point to any appropriate ledger entry type");
527 using namespace test::jtx;
528 using namespace std::chrono_literals;
529 Env env(*this);
530
531 // The goal of this test is observe account_lines RPC calls return an
532 // error message when the SLE pointed to by the marker is not owned by
533 // the Account being traversed.
534 //
535 // To start, we'll create an environment with some trust lines, offers
536 // and a signers list.
537 Account const alice{"alice"};
538 Account const becky{"becky"};
539 Account const gw1{"gw1"};
540 env.fund(XRP(10000), alice, becky, gw1);
541 env.close();
542
543 auto payChan = [](Account const& account,
544 Account const& to,
545 STAmount const& amount,
546 NetClock::duration const& settleDelay,
547 PublicKey const& pk) {
548 Json::Value jv;
549 jv[jss::TransactionType] = jss::PaymentChannelCreate;
550 jv[jss::Account] = account.human();
551 jv[jss::Destination] = to.human();
552 jv[jss::Amount] = amount.getJson(JsonOptions::none);
553 jv["SettleDelay"] = settleDelay.count();
554 jv["PublicKey"] = strHex(pk.slice());
555 return jv;
556 };
557
558 // Test all available object types. Not all of these objects will be
559 // included in the search, nor found by `account_objects`. If that ever
560 // changes for any reason, this test will help catch that.
561 //
562 // SignerList, for alice
563 Account const bogie{"bogie"};
564 env(signers(alice, 2, {{bogie, 3}}));
565 env.close();
566
567 // SignerList, includes alice
568 env(signers(becky, 2, {{alice, 3}}));
569 env.close();
570
571 // Trust lines
572 auto const EUR = gw1["EUR"];
573 env(trust(alice, EUR(200)));
574 env(trust(becky, EUR(200)));
575 env.close();
576
577 // Escrow, in each direction
578 env(escrow::create(alice, becky, XRP(1000)),
579 escrow::finish_time(env.now() + 1s));
580 env(escrow::create(becky, alice, XRP(1000)),
581 escrow::finish_time(env.now() + 1s));
582
583 // Pay channels, in each direction
584 env(payChan(alice, becky, XRP(1000), 100s, alice.pk()));
585 env(payChan(becky, alice, XRP(1000), 100s, becky.pk()));
586
587 // Mint NFTs, for each account
588 uint256 const aliceNFtokenID =
589 token::getNextID(env, alice, 0, tfTransferable);
590 env(token::mint(alice, 0), txflags(tfTransferable));
591
592 uint256 const beckyNFtokenID =
593 token::getNextID(env, becky, 0, tfTransferable);
594 env(token::mint(becky, 0), txflags(tfTransferable));
595
596 // NFT Offers, for each other's NFTs
597 env(token::createOffer(alice, beckyNFtokenID, drops(1)),
598 token::owner(becky));
599 env(token::createOffer(becky, aliceNFtokenID, drops(1)),
600 token::owner(alice));
601
602 env(token::createOffer(becky, beckyNFtokenID, drops(1)),
603 txflags(tfSellNFToken),
604 token::destination(alice));
605 env(token::createOffer(alice, aliceNFtokenID, drops(1)),
606 txflags(tfSellNFToken),
607 token::destination(becky));
608
609 env(token::createOffer(gw1, beckyNFtokenID, drops(1)),
610 token::owner(becky),
611 token::destination(alice));
612 env(token::createOffer(gw1, aliceNFtokenID, drops(1)),
613 token::owner(alice),
614 token::destination(becky));
615
616 env(token::createOffer(becky, beckyNFtokenID, drops(1)),
617 txflags(tfSellNFToken));
618 env(token::createOffer(alice, aliceNFtokenID, drops(1)),
619 txflags(tfSellNFToken));
620
621 // Checks, in each direction
622 env(check::create(alice, becky, XRP(50)));
623 env(check::create(becky, alice, XRP(50)));
624
625 // Deposit preauth, in each direction
626 env(deposit::auth(alice, becky));
627 env(deposit::auth(becky, alice));
628
629 // Offers, one where alice is the owner, and one where alice is the
630 // issuer
631 auto const USDalice = alice["USD"];
632 env(offer(alice, EUR(10), XRP(100)));
633 env(offer(becky, USDalice(10), XRP(100)));
634
635 // Tickets
636 env(ticket::create(alice, 2));
637
638 // Add another trustline for good measure
639 auto const BTCbecky = becky["BTC"];
640 env(trust(alice, BTCbecky(200)));
641
642 env.close();
643
644 {
645 // Now make repeated calls to `account_lines` with a limit of 1.
646 // That should iterate all of alice's relevant objects, even though
647 // the list will be empty for most calls.
648 auto getNextLine = [](Env& env,
649 Account const& alice,
650 std::optional<std::string> const marker) {
652 params[jss::account] = alice.human();
653 params[jss::limit] = 1;
654 if (marker)
655 params[jss::marker] = *marker;
656
657 return env.rpc("json", "account_lines", to_string(params));
658 };
659
660 auto aliceLines = getNextLine(env, alice, std::nullopt);
661 constexpr std::size_t expectedIterations = 16;
662 constexpr std::size_t expectedLines = 2;
663 constexpr std::size_t expectedNFTs = 1;
664 std::size_t foundLines = 0;
665
666 auto hasMarker = [](auto const& aliceLines) {
667 return aliceLines[jss::result].isMember(jss::marker);
668 };
669 auto marker = [](auto const& aliceLines) {
670 return aliceLines[jss::result][jss::marker].asString();
671 };
672 auto checkLines = [](auto const& aliceLines) {
673 return aliceLines.isMember(jss::result) &&
674 !aliceLines[jss::result].isMember(jss::error_message) &&
675 aliceLines[jss::result].isMember(jss::lines) &&
676 aliceLines[jss::result][jss::lines].isArray() &&
677 aliceLines[jss::result][jss::lines].size() <= 1;
678 };
679
680 BEAST_EXPECT(hasMarker(aliceLines));
681 BEAST_EXPECT(checkLines(aliceLines));
682 BEAST_EXPECT(aliceLines[jss::result][jss::lines].size() == 0);
683
684 int iterations = 1;
685
686 while (hasMarker(aliceLines))
687 {
688 // Iterate through the markers
689 aliceLines = getNextLine(env, alice, marker(aliceLines));
690 BEAST_EXPECT(checkLines(aliceLines));
691 foundLines += aliceLines[jss::result][jss::lines].size();
692 ++iterations;
693 }
694 BEAST_EXPECT(expectedLines == foundLines);
695
696 Json::Value aliceObjectsParams2;
697 aliceObjectsParams2[jss::account] = alice.human();
698 aliceObjectsParams2[jss::limit] = 200;
699 Json::Value const aliceObjects = env.rpc(
700 "json", "account_objects", to_string(aliceObjectsParams2));
701 BEAST_EXPECT(aliceObjects.isMember(jss::result));
702 BEAST_EXPECT(
703 !aliceObjects[jss::result].isMember(jss::error_message));
704 BEAST_EXPECT(
705 aliceObjects[jss::result].isMember(jss::account_objects));
706 BEAST_EXPECT(
707 aliceObjects[jss::result][jss::account_objects].isArray());
708 // account_objects does not currently return NFTPages. If
709 // that ever changes, without also changing account_lines,
710 // this test will need to be updated.
711 BEAST_EXPECT(
712 aliceObjects[jss::result][jss::account_objects].size() ==
713 iterations + expectedNFTs);
714 // If ledger object association ever changes, for whatever
715 // reason, this test will need to be updated.
716 BEAST_EXPECTS(
717 iterations == expectedIterations, std::to_string(iterations));
718
719 // Get becky's objects just to confirm that they're symmetrical
720 Json::Value beckyObjectsParams;
721 beckyObjectsParams[jss::account] = becky.human();
722 beckyObjectsParams[jss::limit] = 200;
723 Json::Value const beckyObjects = env.rpc(
724 "json", "account_objects", to_string(beckyObjectsParams));
725 BEAST_EXPECT(beckyObjects.isMember(jss::result));
726 BEAST_EXPECT(
727 !beckyObjects[jss::result].isMember(jss::error_message));
728 BEAST_EXPECT(
729 beckyObjects[jss::result].isMember(jss::account_objects));
730 BEAST_EXPECT(
731 beckyObjects[jss::result][jss::account_objects].isArray());
732 // becky should have the same number of objects as alice, except the
733 // 2 tickets that only alice created.
734 BEAST_EXPECT(
735 beckyObjects[jss::result][jss::account_objects].size() ==
736 aliceObjects[jss::result][jss::account_objects].size() - 2);
737 }
738 }
739
740 // test API V2
741 void
743 {
744 testcase("V2: account_lines");
745
746 using namespace test::jtx;
747 Env env(*this);
748 {
749 // account_lines with mal-formed json2 (missing id field).
750 Json::Value request;
751 request[jss::method] = "account_lines";
752 request[jss::jsonrpc] = "2.0";
753 request[jss::ripplerpc] = "2.0";
754 auto const lines = env.rpc("json2", to_string(request));
755 BEAST_EXPECT(
756 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
757 BEAST_EXPECT(
758 lines.isMember(jss::ripplerpc) &&
759 lines[jss::ripplerpc] == "2.0");
760 }
761 {
762 // account_lines with no account.
763 Json::Value request;
764 request[jss::method] = "account_lines";
765 request[jss::jsonrpc] = "2.0";
766 request[jss::ripplerpc] = "2.0";
767 request[jss::id] = 5;
768 auto const lines = env.rpc("json2", to_string(request));
769 BEAST_EXPECT(
770 lines[jss::error][jss::message] ==
771 RPC::missing_field_error(jss::account)[jss::error_message]);
772 BEAST_EXPECT(
773 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
774 BEAST_EXPECT(
775 lines.isMember(jss::ripplerpc) &&
776 lines[jss::ripplerpc] == "2.0");
777 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
778 }
779 {
780 // account_lines with a malformed account.
781 Json::Value params;
782 params[jss::account] =
783 "n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj";
784 Json::Value request;
785 request[jss::method] = "account_lines";
786 request[jss::jsonrpc] = "2.0";
787 request[jss::ripplerpc] = "2.0";
788 request[jss::id] = 5;
789 request[jss::params] = params;
790 auto const lines = env.rpc("json2", to_string(request));
791 BEAST_EXPECT(
792 lines[jss::error][jss::message] ==
793 RPC::make_error(rpcACT_MALFORMED)[jss::error_message]);
794 BEAST_EXPECT(
795 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
796 BEAST_EXPECT(
797 lines.isMember(jss::ripplerpc) &&
798 lines[jss::ripplerpc] == "2.0");
799 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
800 }
801 Account const alice{"alice"};
802 {
803 // account_lines on an unfunded account.
804 Json::Value params;
805 params[jss::account] = alice.human();
806 Json::Value request;
807 request[jss::method] = "account_lines";
808 request[jss::jsonrpc] = "2.0";
809 request[jss::ripplerpc] = "2.0";
810 request[jss::id] = 5;
811 request[jss::params] = params;
812 auto const lines = env.rpc("json2", to_string(request));
813 BEAST_EXPECT(
814 lines[jss::error][jss::message] ==
815 RPC::make_error(rpcACT_NOT_FOUND)[jss::error_message]);
816 BEAST_EXPECT(
817 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
818 BEAST_EXPECT(
819 lines.isMember(jss::ripplerpc) &&
820 lines[jss::ripplerpc] == "2.0");
821 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
822 }
823 env.fund(XRP(10000), alice);
824 env.close();
825 LedgerInfo const ledger3Info = env.closed()->info();
826 BEAST_EXPECT(ledger3Info.seq == 3);
827
828 {
829 // alice is funded but has no lines. An empty array is returned.
830 Json::Value params;
831 params[jss::account] = alice.human();
832 Json::Value request;
833 request[jss::method] = "account_lines";
834 request[jss::jsonrpc] = "2.0";
835 request[jss::ripplerpc] = "2.0";
836 request[jss::id] = 5;
837 request[jss::params] = params;
838 auto const lines = env.rpc("json2", to_string(request));
839 BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
840 BEAST_EXPECT(lines[jss::result][jss::lines].size() == 0);
841 BEAST_EXPECT(
842 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
843 BEAST_EXPECT(
844 lines.isMember(jss::ripplerpc) &&
845 lines[jss::ripplerpc] == "2.0");
846 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
847 }
848 {
849 // Specify a ledger that doesn't exist.
850 Json::Value params;
851 params[jss::account] = alice.human();
852 params[jss::ledger_index] = "nonsense";
853 Json::Value request;
854 request[jss::method] = "account_lines";
855 request[jss::jsonrpc] = "2.0";
856 request[jss::ripplerpc] = "2.0";
857 request[jss::id] = 5;
858 request[jss::params] = params;
859 auto const lines = env.rpc("json2", to_string(request));
860 BEAST_EXPECT(
861 lines[jss::error][jss::message] == "ledgerIndexMalformed");
862 BEAST_EXPECT(
863 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
864 BEAST_EXPECT(
865 lines.isMember(jss::ripplerpc) &&
866 lines[jss::ripplerpc] == "2.0");
867 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
868 }
869 {
870 // Specify a different ledger that doesn't exist.
871 Json::Value params;
872 params[jss::account] = alice.human();
873 params[jss::ledger_index] = 50000;
874 Json::Value request;
875 request[jss::method] = "account_lines";
876 request[jss::jsonrpc] = "2.0";
877 request[jss::ripplerpc] = "2.0";
878 request[jss::id] = 5;
879 request[jss::params] = params;
880 auto const lines = env.rpc("json2", to_string(request));
881 BEAST_EXPECT(lines[jss::error][jss::message] == "ledgerNotFound");
882 BEAST_EXPECT(
883 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
884 BEAST_EXPECT(
885 lines.isMember(jss::ripplerpc) &&
886 lines[jss::ripplerpc] == "2.0");
887 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
888 }
889 // Create trust lines to share with alice.
890 Account const gw1{"gw1"};
891 env.fund(XRP(10000), gw1);
892 std::vector<IOU> gw1Currencies;
893
894 for (char c = 0; c <= ('Z' - 'A'); ++c)
895 {
896 // gw1 currencies have names "YAA" -> "YAZ".
897 gw1Currencies.push_back(
898 gw1[std::string("YA") + static_cast<char>('A' + c)]);
899 IOU const& gw1Currency = gw1Currencies.back();
900
901 // Establish trust lines.
902 env(trust(alice, gw1Currency(100 + c)));
903 env(pay(gw1, alice, gw1Currency(50 + c)));
904 }
905 env.close();
906 LedgerInfo const ledger4Info = env.closed()->info();
907 BEAST_EXPECT(ledger4Info.seq == 4);
908
909 // Add another set of trust lines in another ledger so we can see
910 // differences in historic ledgers.
911 Account const gw2{"gw2"};
912 env.fund(XRP(10000), gw2);
913
914 // gw2 requires authorization.
915 env(fset(gw2, asfRequireAuth));
916 env.close();
917 std::vector<IOU> gw2Currencies;
918
919 for (char c = 0; c <= ('Z' - 'A'); ++c)
920 {
921 // gw2 currencies have names "ZAA" -> "ZAZ".
922 gw2Currencies.push_back(
923 gw2[std::string("ZA") + static_cast<char>('A' + c)]);
924 IOU const& gw2Currency = gw2Currencies.back();
925
926 // Establish trust lines.
927 env(trust(alice, gw2Currency(200 + c)));
928 env(trust(gw2, gw2Currency(0), alice, tfSetfAuth));
929 env.close();
930 env(pay(gw2, alice, gw2Currency(100 + c)));
931 env.close();
932
933 // Set flags on gw2 trust lines so we can look for them.
934 env(trust(
935 alice,
936 gw2Currency(0),
937 gw2,
939 }
940 env.close();
941 LedgerInfo const ledger58Info = env.closed()->info();
942 BEAST_EXPECT(ledger58Info.seq == 58);
943
944 // A re-usable test for historic ledgers.
945 auto testAccountLinesHistory = [this, &env](
946 Account const& account,
947 LedgerInfo const& info,
948 int count) {
949 // Get account_lines by ledger index.
950 Json::Value paramsSeq;
951 paramsSeq[jss::account] = account.human();
952 paramsSeq[jss::ledger_index] = info.seq;
953 Json::Value requestSeq;
954 requestSeq[jss::method] = "account_lines";
955 requestSeq[jss::jsonrpc] = "2.0";
956 requestSeq[jss::ripplerpc] = "2.0";
957 requestSeq[jss::id] = 5;
958 requestSeq[jss::params] = paramsSeq;
959 auto const linesSeq = env.rpc("json2", to_string(requestSeq));
960 BEAST_EXPECT(linesSeq[jss::result][jss::lines].isArray());
961 BEAST_EXPECT(linesSeq[jss::result][jss::lines].size() == count);
962 BEAST_EXPECT(
963 linesSeq.isMember(jss::jsonrpc) &&
964 linesSeq[jss::jsonrpc] == "2.0");
965 BEAST_EXPECT(
966 linesSeq.isMember(jss::ripplerpc) &&
967 linesSeq[jss::ripplerpc] == "2.0");
968 BEAST_EXPECT(linesSeq.isMember(jss::id) && linesSeq[jss::id] == 5);
969
970 // Get account_lines by ledger hash.
971 Json::Value paramsHash;
972 paramsHash[jss::account] = account.human();
973 paramsHash[jss::ledger_hash] = to_string(info.hash);
974 Json::Value requestHash;
975 requestHash[jss::method] = "account_lines";
976 requestHash[jss::jsonrpc] = "2.0";
977 requestHash[jss::ripplerpc] = "2.0";
978 requestHash[jss::id] = 5;
979 requestHash[jss::params] = paramsHash;
980 auto const linesHash = env.rpc("json2", to_string(requestHash));
981 BEAST_EXPECT(linesHash[jss::result][jss::lines].isArray());
982 BEAST_EXPECT(linesHash[jss::result][jss::lines].size() == count);
983 BEAST_EXPECT(
984 linesHash.isMember(jss::jsonrpc) &&
985 linesHash[jss::jsonrpc] == "2.0");
986 BEAST_EXPECT(
987 linesHash.isMember(jss::ripplerpc) &&
988 linesHash[jss::ripplerpc] == "2.0");
989 BEAST_EXPECT(
990 linesHash.isMember(jss::id) && linesHash[jss::id] == 5);
991 };
992
993 // Alice should have no trust lines in ledger 3.
994 testAccountLinesHistory(alice, ledger3Info, 0);
995
996 // Alice should have 26 trust lines in ledger 4.
997 testAccountLinesHistory(alice, ledger4Info, 26);
998
999 // Alice should have 52 trust lines in ledger 58.
1000 testAccountLinesHistory(alice, ledger58Info, 52);
1001
1002 {
1003 // Surprisingly, it's valid to specify both index and hash, in
1004 // which case the hash wins.
1005 Json::Value params;
1006 params[jss::account] = alice.human();
1007 params[jss::ledger_hash] = to_string(ledger4Info.hash);
1008 params[jss::ledger_index] = ledger58Info.seq;
1009 Json::Value request;
1010 request[jss::method] = "account_lines";
1011 request[jss::jsonrpc] = "2.0";
1012 request[jss::ripplerpc] = "2.0";
1013 request[jss::id] = 5;
1014 request[jss::params] = params;
1015 auto const lines = env.rpc("json2", to_string(request));
1016 BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
1017 BEAST_EXPECT(lines[jss::result][jss::lines].size() == 26);
1018 BEAST_EXPECT(
1019 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
1020 BEAST_EXPECT(
1021 lines.isMember(jss::ripplerpc) &&
1022 lines[jss::ripplerpc] == "2.0");
1023 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
1024 }
1025 {
1026 // alice should have 52 trust lines in the current ledger.
1027 Json::Value params;
1028 params[jss::account] = alice.human();
1029 Json::Value request;
1030 request[jss::method] = "account_lines";
1031 request[jss::jsonrpc] = "2.0";
1032 request[jss::ripplerpc] = "2.0";
1033 request[jss::id] = 5;
1034 request[jss::params] = params;
1035 auto const lines = env.rpc("json2", to_string(request));
1036 BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
1037 BEAST_EXPECT(lines[jss::result][jss::lines].size() == 52);
1038 BEAST_EXPECT(
1039 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
1040 BEAST_EXPECT(
1041 lines.isMember(jss::ripplerpc) &&
1042 lines[jss::ripplerpc] == "2.0");
1043 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
1044 }
1045 {
1046 // alice should have 26 trust lines with gw1.
1047 Json::Value params;
1048 params[jss::account] = alice.human();
1049 params[jss::peer] = gw1.human();
1050 Json::Value request;
1051 request[jss::method] = "account_lines";
1052 request[jss::jsonrpc] = "2.0";
1053 request[jss::ripplerpc] = "2.0";
1054 request[jss::id] = 5;
1055 request[jss::params] = params;
1056 auto const lines = env.rpc("json2", to_string(request));
1057 BEAST_EXPECT(lines[jss::result][jss::lines].isArray());
1058 BEAST_EXPECT(lines[jss::result][jss::lines].size() == 26);
1059 BEAST_EXPECT(
1060 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
1061 BEAST_EXPECT(
1062 lines.isMember(jss::ripplerpc) &&
1063 lines[jss::ripplerpc] == "2.0");
1064 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
1065 }
1066 {
1067 // Use a malformed peer.
1068 Json::Value params;
1069 params[jss::account] = alice.human();
1070 params[jss::peer] =
1071 "n9MJkEKHDhy5eTLuHUQeAAjo382frHNbFK4C8hcwN4nwM2SrLdBj";
1072 Json::Value request;
1073 request[jss::method] = "account_lines";
1074 request[jss::jsonrpc] = "2.0";
1075 request[jss::ripplerpc] = "2.0";
1076 request[jss::id] = 5;
1077 request[jss::params] = params;
1078 auto const lines = env.rpc("json2", to_string(request));
1079 BEAST_EXPECT(
1080 lines[jss::error][jss::message] ==
1081 RPC::make_error(rpcACT_MALFORMED)[jss::error_message]);
1082 BEAST_EXPECT(
1083 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
1084 BEAST_EXPECT(
1085 lines.isMember(jss::ripplerpc) &&
1086 lines[jss::ripplerpc] == "2.0");
1087 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
1088 }
1089 {
1090 // A negative limit should fail.
1091 Json::Value params;
1092 params[jss::account] = alice.human();
1093 params[jss::limit] = -1;
1094 Json::Value request;
1095 request[jss::method] = "account_lines";
1096 request[jss::jsonrpc] = "2.0";
1097 request[jss::ripplerpc] = "2.0";
1098 request[jss::id] = 5;
1099 request[jss::params] = params;
1100 auto const lines = env.rpc("json2", to_string(request));
1101 BEAST_EXPECT(
1102 lines[jss::error][jss::message] ==
1103 RPC::expected_field_message(jss::limit, "unsigned integer"));
1104 BEAST_EXPECT(
1105 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
1106 BEAST_EXPECT(
1107 lines.isMember(jss::ripplerpc) &&
1108 lines[jss::ripplerpc] == "2.0");
1109 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
1110 }
1111 {
1112 // Limit the response to 1 trust line.
1113 Json::Value paramsA;
1114 paramsA[jss::account] = alice.human();
1115 paramsA[jss::limit] = 1;
1116 Json::Value requestA;
1117 requestA[jss::method] = "account_lines";
1118 requestA[jss::jsonrpc] = "2.0";
1119 requestA[jss::ripplerpc] = "2.0";
1120 requestA[jss::id] = 5;
1121 requestA[jss::params] = paramsA;
1122 auto const linesA = env.rpc("json2", to_string(requestA));
1123 BEAST_EXPECT(linesA[jss::result][jss::lines].isArray());
1124 BEAST_EXPECT(linesA[jss::result][jss::lines].size() == 1);
1125 BEAST_EXPECT(
1126 linesA.isMember(jss::jsonrpc) && linesA[jss::jsonrpc] == "2.0");
1127 BEAST_EXPECT(
1128 linesA.isMember(jss::ripplerpc) &&
1129 linesA[jss::ripplerpc] == "2.0");
1130 BEAST_EXPECT(linesA.isMember(jss::id) && linesA[jss::id] == 5);
1131
1132 // Pick up from where the marker left off. We should get 51.
1133 auto marker = linesA[jss::result][jss::marker].asString();
1134 Json::Value paramsB;
1135 paramsB[jss::account] = alice.human();
1136 paramsB[jss::marker] = marker;
1137 Json::Value requestB;
1138 requestB[jss::method] = "account_lines";
1139 requestB[jss::jsonrpc] = "2.0";
1140 requestB[jss::ripplerpc] = "2.0";
1141 requestB[jss::id] = 5;
1142 requestB[jss::params] = paramsB;
1143 auto const linesB = env.rpc("json2", to_string(requestB));
1144 BEAST_EXPECT(linesB[jss::result][jss::lines].isArray());
1145 BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 51);
1146 BEAST_EXPECT(
1147 linesB.isMember(jss::jsonrpc) && linesB[jss::jsonrpc] == "2.0");
1148 BEAST_EXPECT(
1149 linesB.isMember(jss::ripplerpc) &&
1150 linesB[jss::ripplerpc] == "2.0");
1151 BEAST_EXPECT(linesB.isMember(jss::id) && linesB[jss::id] == 5);
1152
1153 // Go again from where the marker left off, but set a limit of 3.
1154 Json::Value paramsC;
1155 paramsC[jss::account] = alice.human();
1156 paramsC[jss::limit] = 3;
1157 paramsC[jss::marker] = marker;
1158 Json::Value requestC;
1159 requestC[jss::method] = "account_lines";
1160 requestC[jss::jsonrpc] = "2.0";
1161 requestC[jss::ripplerpc] = "2.0";
1162 requestC[jss::id] = 5;
1163 requestC[jss::params] = paramsC;
1164 auto const linesC = env.rpc("json2", to_string(requestC));
1165 BEAST_EXPECT(linesC[jss::result][jss::lines].isArray());
1166 BEAST_EXPECT(linesC[jss::result][jss::lines].size() == 3);
1167 BEAST_EXPECT(
1168 linesC.isMember(jss::jsonrpc) && linesC[jss::jsonrpc] == "2.0");
1169 BEAST_EXPECT(
1170 linesC.isMember(jss::ripplerpc) &&
1171 linesC[jss::ripplerpc] == "2.0");
1172 BEAST_EXPECT(linesC.isMember(jss::id) && linesC[jss::id] == 5);
1173
1174 // Mess with the marker so it becomes bad and check for the error.
1175 marker[5] = marker[5] == '7' ? '8' : '7';
1176 Json::Value paramsD;
1177 paramsD[jss::account] = alice.human();
1178 paramsD[jss::marker] = marker;
1179 Json::Value requestD;
1180 requestD[jss::method] = "account_lines";
1181 requestD[jss::jsonrpc] = "2.0";
1182 requestD[jss::ripplerpc] = "2.0";
1183 requestD[jss::id] = 5;
1184 requestD[jss::params] = paramsD;
1185 auto const linesD = env.rpc("json2", to_string(requestD));
1186 BEAST_EXPECT(
1187 linesD[jss::error][jss::message] ==
1188 RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
1189 BEAST_EXPECT(
1190 linesD.isMember(jss::jsonrpc) && linesD[jss::jsonrpc] == "2.0");
1191 BEAST_EXPECT(
1192 linesD.isMember(jss::ripplerpc) &&
1193 linesD[jss::ripplerpc] == "2.0");
1194 BEAST_EXPECT(linesD.isMember(jss::id) && linesD[jss::id] == 5);
1195 }
1196 {
1197 // A non-string marker should also fail.
1198 Json::Value params;
1199 params[jss::account] = alice.human();
1200 params[jss::marker] = true;
1201 Json::Value request;
1202 request[jss::method] = "account_lines";
1203 request[jss::jsonrpc] = "2.0";
1204 request[jss::ripplerpc] = "2.0";
1205 request[jss::id] = 5;
1206 request[jss::params] = params;
1207 auto const lines = env.rpc("json2", to_string(request));
1208 BEAST_EXPECT(
1209 lines[jss::error][jss::message] ==
1210 RPC::expected_field_message(jss::marker, "string"));
1211 BEAST_EXPECT(
1212 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
1213 BEAST_EXPECT(
1214 lines.isMember(jss::ripplerpc) &&
1215 lines[jss::ripplerpc] == "2.0");
1216 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
1217 }
1218 {
1219 // Check that the flags we expect from alice to gw2 are present.
1220 Json::Value params;
1221 params[jss::account] = alice.human();
1222 params[jss::limit] = 10;
1223 params[jss::peer] = gw2.human();
1224 Json::Value request;
1225 request[jss::method] = "account_lines";
1226 request[jss::jsonrpc] = "2.0";
1227 request[jss::ripplerpc] = "2.0";
1228 request[jss::id] = 5;
1229 request[jss::params] = params;
1230 auto const lines = env.rpc("json2", to_string(request));
1231 auto const& line = lines[jss::result][jss::lines][0u];
1232 BEAST_EXPECT(line[jss::freeze].asBool() == true);
1233 BEAST_EXPECT(line[jss::deep_freeze].asBool() == true);
1234 BEAST_EXPECT(line[jss::no_ripple].asBool() == true);
1235 BEAST_EXPECT(line[jss::peer_authorized].asBool() == true);
1236 BEAST_EXPECT(
1237 lines.isMember(jss::jsonrpc) && lines[jss::jsonrpc] == "2.0");
1238 BEAST_EXPECT(
1239 lines.isMember(jss::ripplerpc) &&
1240 lines[jss::ripplerpc] == "2.0");
1241 BEAST_EXPECT(lines.isMember(jss::id) && lines[jss::id] == 5);
1242 }
1243 {
1244 // Check that the flags we expect from gw2 to alice are present.
1245 Json::Value paramsA;
1246 paramsA[jss::account] = gw2.human();
1247 paramsA[jss::limit] = 1;
1248 paramsA[jss::peer] = alice.human();
1249 Json::Value requestA;
1250 requestA[jss::method] = "account_lines";
1251 requestA[jss::jsonrpc] = "2.0";
1252 requestA[jss::ripplerpc] = "2.0";
1253 requestA[jss::id] = 5;
1254 requestA[jss::params] = paramsA;
1255 auto const linesA = env.rpc("json2", to_string(requestA));
1256 auto const& lineA = linesA[jss::result][jss::lines][0u];
1257 BEAST_EXPECT(lineA[jss::freeze_peer].asBool() == true);
1258 BEAST_EXPECT(lineA[jss::deep_freeze_peer].asBool() == true);
1259 BEAST_EXPECT(lineA[jss::no_ripple_peer].asBool() == true);
1260 BEAST_EXPECT(lineA[jss::authorized].asBool() == true);
1261 BEAST_EXPECT(
1262 linesA.isMember(jss::jsonrpc) && linesA[jss::jsonrpc] == "2.0");
1263 BEAST_EXPECT(
1264 linesA.isMember(jss::ripplerpc) &&
1265 linesA[jss::ripplerpc] == "2.0");
1266 BEAST_EXPECT(linesA.isMember(jss::id) && linesA[jss::id] == 5);
1267
1268 // Continue from the returned marker to make sure that works.
1269 BEAST_EXPECT(linesA[jss::result].isMember(jss::marker));
1270 auto const marker = linesA[jss::result][jss::marker].asString();
1271 Json::Value paramsB;
1272 paramsB[jss::account] = gw2.human();
1273 paramsB[jss::limit] = 25;
1274 paramsB[jss::marker] = marker;
1275 paramsB[jss::peer] = alice.human();
1276 Json::Value requestB;
1277 requestB[jss::method] = "account_lines";
1278 requestB[jss::jsonrpc] = "2.0";
1279 requestB[jss::ripplerpc] = "2.0";
1280 requestB[jss::id] = 5;
1281 requestB[jss::params] = paramsB;
1282 auto const linesB = env.rpc("json2", to_string(requestB));
1283 BEAST_EXPECT(linesB[jss::result][jss::lines].isArray());
1284 BEAST_EXPECT(linesB[jss::result][jss::lines].size() == 25);
1285 BEAST_EXPECT(!linesB[jss::result].isMember(jss::marker));
1286 BEAST_EXPECT(
1287 linesB.isMember(jss::jsonrpc) && linesB[jss::jsonrpc] == "2.0");
1288 BEAST_EXPECT(
1289 linesB.isMember(jss::ripplerpc) &&
1290 linesB[jss::ripplerpc] == "2.0");
1291 BEAST_EXPECT(linesB.isMember(jss::id) && linesB[jss::id] == 5);
1292 }
1293 }
1294
1295 // test API V2
1296 void
1298 {
1299 testcase("V2: account_lines with removed marker");
1300
1301 using namespace test::jtx;
1302 Env env(*this);
1303
1304 // The goal here is to observe account_lines marker behavior if the
1305 // entry pointed at by a returned marker is removed from the ledger.
1306 //
1307 // It isn't easy to explicitly delete a trust line, so we do so in a
1308 // round-about fashion. It takes 4 actors:
1309 // o Gateway gw1 issues EUR
1310 // o alice offers to buy 100 EUR for 100 XRP.
1311 // o becky offers to sell 100 EUR for 100 XRP.
1312 // There will now be an inferred trustline between alice and gw2.
1313 // o alice pays her 100 EUR to cheri.
1314 // alice should now have no EUR and no trustline to gw2.
1315 Account const alice{"alice"};
1316 Account const becky{"becky"};
1317 Account const cheri{"cheri"};
1318 Account const gw1{"gw1"};
1319 Account const gw2{"gw2"};
1320 env.fund(XRP(10000), alice, becky, cheri, gw1, gw2);
1321 env.close();
1322
1323 auto const USD = gw1["USD"];
1324 auto const AUD = gw1["AUD"];
1325 auto const EUR = gw2["EUR"];
1326 env(trust(alice, USD(200)));
1327 env(trust(alice, AUD(200)));
1328 env(trust(becky, EUR(200)));
1329 env(trust(cheri, EUR(200)));
1330 env.close();
1331
1332 // becky gets 100 EUR from gw1.
1333 env(pay(gw2, becky, EUR(100)));
1334 env.close();
1335
1336 // alice offers to buy 100 EUR for 100 XRP.
1337 env(offer(alice, EUR(100), XRP(100)));
1338 env.close();
1339
1340 // becky offers to buy 100 XRP for 100 EUR.
1341 env(offer(becky, XRP(100), EUR(100)));
1342 env.close();
1343
1344 // Get account_lines for alice. Limit at 1, so we get a marker.
1345 Json::Value linesBegParams;
1346 linesBegParams[jss::account] = alice.human();
1347 linesBegParams[jss::limit] = 2;
1348 Json::Value linesBegRequest;
1349 linesBegRequest[jss::method] = "account_lines";
1350 linesBegRequest[jss::jsonrpc] = "2.0";
1351 linesBegRequest[jss::ripplerpc] = "2.0";
1352 linesBegRequest[jss::id] = 5;
1353 linesBegRequest[jss::params] = linesBegParams;
1354 auto const linesBeg = env.rpc("json2", to_string(linesBegRequest));
1355 BEAST_EXPECT(
1356 linesBeg[jss::result][jss::lines][0u][jss::currency] == "USD");
1357 BEAST_EXPECT(linesBeg[jss::result].isMember(jss::marker));
1358 BEAST_EXPECT(
1359 linesBeg.isMember(jss::jsonrpc) && linesBeg[jss::jsonrpc] == "2.0");
1360 BEAST_EXPECT(
1361 linesBeg.isMember(jss::ripplerpc) &&
1362 linesBeg[jss::ripplerpc] == "2.0");
1363 BEAST_EXPECT(linesBeg.isMember(jss::id) && linesBeg[jss::id] == 5);
1364
1365 // alice pays 100 USD to cheri.
1366 env(pay(alice, cheri, EUR(100)));
1367 env.close();
1368
1369 // Since alice paid all her EUR to cheri, alice should no longer
1370 // have a trust line to gw1. So the old marker should now be invalid.
1371 Json::Value linesEndParams;
1372 linesEndParams[jss::account] = alice.human();
1373 linesEndParams[jss::marker] = linesBeg[jss::result][jss::marker];
1374 Json::Value linesEndRequest;
1375 linesEndRequest[jss::method] = "account_lines";
1376 linesEndRequest[jss::jsonrpc] = "2.0";
1377 linesEndRequest[jss::ripplerpc] = "2.0";
1378 linesEndRequest[jss::id] = 5;
1379 linesEndRequest[jss::params] = linesEndParams;
1380 auto const linesEnd = env.rpc("json2", to_string(linesEndRequest));
1381 BEAST_EXPECT(
1382 linesEnd[jss::error][jss::message] ==
1383 RPC::make_error(rpcINVALID_PARAMS)[jss::error_message]);
1384 BEAST_EXPECT(
1385 linesEnd.isMember(jss::jsonrpc) && linesEnd[jss::jsonrpc] == "2.0");
1386 BEAST_EXPECT(
1387 linesEnd.isMember(jss::ripplerpc) &&
1388 linesEnd[jss::ripplerpc] == "2.0");
1389 BEAST_EXPECT(linesEnd.isMember(jss::id) && linesEnd[jss::id] == 5);
1390 }
1391
1392 void
1402};
1403
1404BEAST_DEFINE_TESTSUITE(AccountLines, rpc, ripple);
1405
1406} // namespace RPC
1407} // namespace ripple
T back(T... args)
Represents a JSON value.
Definition json_value.h:149
bool isMember(char const *key) const
Return true if the object has a member named key.
A testsuite class.
Definition suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:155
void fail(String const &reason, char const *file, int line)
Record a failure.
Definition suite.h:533
A public key.
Definition PublicKey.h:62
void run() override
Runs the suite.
T find(T... args)
T is_same_v
@ nullValue
'null' value
Definition json_value.h:38
@ arrayValue
array value (ordered list)
Definition json_value.h:44
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:45
Json::Value make_error(error_code_i code)
Returns a new json object that reflects the error code.
std::string expected_field_message(std::string const &name, std::string const &type)
Definition ErrorCodes.h:337
Json::Value missing_field_error(std::string const &name)
Definition ErrorCodes.h:283
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
constexpr std::uint32_t tfSetDeepFreeze
Definition TxFlags.h:120
@ rpcACT_NOT_FOUND
Definition ErrorCodes.h:70
@ rpcACT_MALFORMED
Definition ErrorCodes.h:90
@ rpcINVALID_PARAMS
Definition ErrorCodes.h:84
constexpr std::uint32_t const tfSellNFToken
Definition TxFlags.h:230
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:30
constexpr std::uint32_t tfSetfAuth
Definition TxFlags.h:115
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
constexpr std::uint32_t asfRequireAuth
Definition TxFlags.h:78
constexpr std::uint32_t tfSetFreeze
Definition TxFlags.h:118
constexpr std::uint32_t tfSetNoRipple
Definition TxFlags.h:116
constexpr std::uint32_t const tfTransferable
Definition TxFlags.h:142
T push_back(T... args)
Information about the notional ledger backing the view.
T substr(T... args)
T to_string(T... args)