rippled
Loading...
Searching...
No Matches
AccountObjects_test.cpp
1#include <test/jtx.h>
2#include <test/jtx/AMM.h>
3#include <test/jtx/xchain_bridge.h>
4
5#include <xrpl/json/json_reader.h>
6#include <xrpl/json/json_value.h>
7#include <xrpl/json/to_string.h>
8#include <xrpl/protocol/jss.h>
9#include <xrpl/protocol/nft.h>
10#include <xrpl/tx/transactors/nft/NFTokenMint.h>
11
12#include <boost/utility/string_ref.hpp>
13
14#include <algorithm>
15
16namespace xrpl {
17namespace test {
18
19static char const* bob_account_objects[] = {
20 R"json({
21 "Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
22 "BookDirectory" : "50AD0A9E54D2B381288D535EB724E4275FFBF41580D28A925D038D7EA4C68000",
23 "BookNode" : "0",
24 "Flags" : 65536,
25 "LedgerEntryType" : "Offer",
26 "OwnerNode" : "0",
27 "Sequence" : 6,
28 "TakerGets" : {
29 "currency" : "USD",
30 "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
31 "value" : "1"
32 },
33 "TakerPays" : "100000000",
34 "index" : "29665262716C19830E26AEEC0916E476FC7D8EF195FF3B4F06829E64F82A3B3E"
35})json",
36 R"json({
37 "Balance" : {
38 "currency" : "USD",
39 "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
40 "value" : "-1000"
41 },
42 "Flags" : 131072,
43 "HighLimit" : {
44 "currency" : "USD",
45 "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
46 "value" : "1000"
47 },
48 "HighNode" : "0",
49 "LedgerEntryType" : "RippleState",
50 "LowLimit" : {
51 "currency" : "USD",
52 "issuer" : "r9cZvwKU3zzuZK9JFovGg1JC5n7QiqNL8L",
53 "value" : "0"
54 },
55 "LowNode" : "0",
56 "index" : "D13183BCFFC9AAC9F96AEBB5F66E4A652AD1F5D10273AEB615478302BEBFD4A4"
57})json",
58 R"json({
59 "Balance" : {
60 "currency" : "USD",
61 "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
62 "value" : "-1000"
63 },
64 "Flags" : 131072,
65 "HighLimit" : {
66 "currency" : "USD",
67 "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
68 "value" : "1000"
69 },
70 "HighNode" : "0",
71 "LedgerEntryType" : "RippleState",
72 "LowLimit" : {
73 "currency" : "USD",
74 "issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW",
75 "value" : "0"
76 },
77 "LowNode" : "0",
78 "index" : "D89BC239086183EB9458C396E643795C1134963E6550E682A190A5F021766D43"
79})json",
80 R"json({
81 "Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
82 "BookDirectory" : "B025997A323F5C3E03DDF1334471F5984ABDE31C59D463525D038D7EA4C68000",
83 "BookNode" : "0",
84 "Flags" : 65536,
85 "LedgerEntryType" : "Offer",
86 "OwnerNode" : "0",
87 "Sequence" : 7,
88 "TakerGets" : {
89 "currency" : "USD",
90 "issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW",
91 "value" : "1"
92 },
93 "TakerPays" : "100000000",
94 "index" : "F03ABE26CB8C5F4AFB31A86590BD25C64C5756FCE5CE9704C27AFE291A4A29A1"
95})json"};
96
98{
99public:
100 void
102 {
103 testcase("error cases");
104
105 using namespace jtx;
106 Env env(*this);
107
108 // test error on no account
109 {
110 Json::Value const params;
111 auto resp = env.rpc("json", "account_objects", to_string(params));
112 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Missing field 'account'.");
113 }
114 // test account non-string
115 {
116 auto testInvalidAccountParam = [&](auto const& param) {
117 Json::Value params;
118 params[jss::account] = param;
119 auto jrr = env.rpc("json", "account_objects", to_string(params))[jss::result];
120 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
121 BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'account'.");
122 };
123
124 testInvalidAccountParam(1);
125 testInvalidAccountParam(1.1);
126 testInvalidAccountParam(true);
127 testInvalidAccountParam(Json::Value(Json::nullValue));
128 testInvalidAccountParam(Json::Value(Json::objectValue));
129 testInvalidAccountParam(Json::Value(Json::arrayValue));
130 }
131 // test error on malformed account string.
132 {
133 Json::Value params;
134 params[jss::account] = "n94JNrQYkDrpt62bbSR7nVEhdyAvcJXRAsjEkFYyqRkh9SUTYEqV";
135 auto resp = env.rpc("json", "account_objects", to_string(params));
136 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Account malformed.");
137 }
138 // test error on account that's not in the ledger.
139 {
140 Json::Value params;
141 params[jss::account] = Account{"bogie"}.human();
142 auto resp = env.rpc("json", "account_objects", to_string(params));
143 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Account not found.");
144 }
145 Account const bob{"bob"};
146 // test error on large ledger_index.
147 {
148 Json::Value params;
149 params[jss::account] = bob.human();
150 params[jss::ledger_index] = 10;
151 auto resp = env.rpc("json", "account_objects", to_string(params));
152 BEAST_EXPECT(resp[jss::result][jss::error_message] == "ledgerNotFound");
153 }
154
155 env.fund(XRP(1000), bob);
156 // test error on type param not a string
157 {
158 Json::Value params;
159 params[jss::account] = bob.human();
160 params[jss::type] = 10;
161 auto resp = env.rpc("json", "account_objects", to_string(params));
162 BEAST_EXPECT(
163 resp[jss::result][jss::error_message] == "Invalid field 'type', not string.");
164 }
165 // test error on type param not a valid type
166 {
167 Json::Value params;
168 params[jss::account] = bob.human();
169 params[jss::type] = "expedited";
170 auto resp = env.rpc("json", "account_objects", to_string(params));
171 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'type'.");
172 }
173 // test error on limit -ve
174 {
175 Json::Value params;
176 params[jss::account] = bob.human();
177 params[jss::limit] = -1;
178 auto resp = env.rpc("json", "account_objects", to_string(params));
179 BEAST_EXPECT(
180 resp[jss::result][jss::error_message] ==
181 "Invalid field 'limit', not unsigned integer.");
182 }
183 // test errors on marker
184 {
185 Account const gw{"G"};
186 env.fund(XRP(1000), gw);
187 auto const USD = gw["USD"];
188 env.trust(USD(1000), bob);
189 env(pay(gw, bob, XRP(1)));
190 env(offer(bob, XRP(100), bob["USD"](1)), txflags(tfPassive));
191
192 Json::Value params;
193 params[jss::account] = bob.human();
194 params[jss::limit] = 1;
195 auto resp = env.rpc("json", "account_objects", to_string(params));
196
197 auto resume_marker = resp[jss::result][jss::marker];
198 std::string mark = to_string(resume_marker);
199 params[jss::marker] = 10;
200 resp = env.rpc("json", "account_objects", to_string(params));
201 BEAST_EXPECT(
202 resp[jss::result][jss::error_message] == "Invalid field 'marker', not string.");
203
204 params[jss::marker] = "This is a string with no comma";
205 resp = env.rpc("json", "account_objects", to_string(params));
206 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
207
208 params[jss::marker] = "This string has a comma, but is not hex";
209 resp = env.rpc("json", "account_objects", to_string(params));
210 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
211
212 params[jss::marker] = std::string(&mark[1U], 64);
213 resp = env.rpc("json", "account_objects", to_string(params));
214 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
215
216 params[jss::marker] = std::string(&mark[1U], 65);
217 resp = env.rpc("json", "account_objects", to_string(params));
218 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
219
220 params[jss::marker] = std::string(&mark[1U], 65) + "not hex";
221 resp = env.rpc("json", "account_objects", to_string(params));
222 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
223
224 // Should this be an error?
225 // A hex digit is absent from the end of marker.
226 // No account objects returned.
227 params[jss::marker] = std::string(&mark[1U], 128);
228 resp = env.rpc("json", "account_objects", to_string(params));
229 BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
230 }
231 }
232
233 void
235 {
236 testcase("unsteppedThenStepped");
237
238 using namespace jtx;
239 Env env(*this);
240
241 Account const gw1{"G1"};
242 Account const gw2{"G2"};
243 Account const bob{"bob"};
244
245 auto const USD1 = gw1["USD"];
246 auto const USD2 = gw2["USD"];
247
248 env.fund(XRP(1000), gw1, gw2, bob);
249 env.trust(USD1(1000), bob);
250 env.trust(USD2(1000), bob);
251
252 env(pay(gw1, bob, USD1(1000)));
253 env(pay(gw2, bob, USD2(1000)));
254
255 env(offer(bob, XRP(100), bob["USD"](1)), txflags(tfPassive));
256 env(offer(bob, XRP(100), USD1(1)), txflags(tfPassive));
257
258 Json::Value bobj[4];
259 for (int i = 0; i < 4; ++i)
261
262 // test 'unstepped'
263 // i.e. request account objects without explicit limit/marker paging
264 {
265 Json::Value params;
266 params[jss::account] = bob.human();
267 auto resp = env.rpc("json", "account_objects", to_string(params));
268 BEAST_EXPECT(!resp.isMember(jss::marker));
269
270 BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 4);
271 for (int i = 0; i < 4; ++i)
272 {
273 auto& aobj = resp[jss::result][jss::account_objects][i];
274 aobj.removeMember("PreviousTxnID");
275 aobj.removeMember("PreviousTxnLgrSeq");
276 BEAST_EXPECT(aobj == bobj[i]);
277 }
278 }
279 // test request with type parameter as filter, unstepped
280 {
281 Json::Value params;
282 params[jss::account] = bob.human();
283 params[jss::type] = jss::state;
284 auto resp = env.rpc("json", "account_objects", to_string(params));
285 BEAST_EXPECT(!resp.isMember(jss::marker));
286
287 BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 2);
288 for (int i = 0; i < 2; ++i)
289 {
290 auto& aobj = resp[jss::result][jss::account_objects][i];
291 aobj.removeMember("PreviousTxnID");
292 aobj.removeMember("PreviousTxnLgrSeq");
293 BEAST_EXPECT(aobj == bobj[i + 1]);
294 }
295 }
296 // test stepped one-at-a-time with limit=1, resume from prev marker
297 {
298 Json::Value params;
299 params[jss::account] = bob.human();
300 params[jss::limit] = 1;
301 for (int i = 0; i < 4; ++i)
302 {
303 auto resp = env.rpc("json", "account_objects", to_string(params));
304 auto& aobjs = resp[jss::result][jss::account_objects];
305 BEAST_EXPECT(aobjs.size() == 1);
306 auto& aobj = aobjs[0U];
307 if (i < 3)
308 {
309 BEAST_EXPECT(resp[jss::result][jss::limit] == 1);
310 }
311 else
312 {
313 BEAST_EXPECT(!resp[jss::result].isMember(jss::limit));
314 }
315
316 aobj.removeMember("PreviousTxnID");
317 aobj.removeMember("PreviousTxnLgrSeq");
318
319 BEAST_EXPECT(aobj == bobj[i]);
320
321 params[jss::marker] = resp[jss::result][jss::marker];
322 }
323 }
324 }
325
326 void
328 {
329 // The preceding test case, unsteppedThenStepped(), found a bug in the
330 // support for NFToken Pages. So we're leaving that test alone when
331 // adding tests to exercise NFTokenPages.
332 testcase("unsteppedThenSteppedWithNFTs");
333
334 using namespace jtx;
335 Env env(*this);
336
337 Account const gw1{"G1"};
338 Account const gw2{"G2"};
339 Account const bob{"bob"};
340
341 auto const USD1 = gw1["USD"];
342 auto const USD2 = gw2["USD"];
343
344 env.fund(XRP(1000), gw1, gw2, bob);
345 env.close();
346
347 // Check behavior if there are no account objects.
348 {
349 // Unpaged
350 Json::Value params;
351 params[jss::account] = bob.human();
352 auto resp = env.rpc("json", "account_objects", to_string(params));
353 BEAST_EXPECT(!resp.isMember(jss::marker));
354 BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
355
356 // Limit == 1
357 params[jss::limit] = 1;
358 resp = env.rpc("json", "account_objects", to_string(params));
359 BEAST_EXPECT(!resp.isMember(jss::marker));
360 BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
361 }
362
363 // Check behavior if there are only NFTokens.
364 env(token::mint(bob, 0u), txflags(tfTransferable));
365 env.close();
366
367 // test 'unstepped'
368 // i.e. request account objects without explicit limit/marker paging
369 Json::Value unpaged;
370 {
371 Json::Value params;
372 params[jss::account] = bob.human();
373 auto resp = env.rpc("json", "account_objects", to_string(params));
374 BEAST_EXPECT(!resp.isMember(jss::marker));
375
376 unpaged = resp[jss::result][jss::account_objects];
377 BEAST_EXPECT(unpaged.size() == 1);
378 }
379 // test request with type parameter as filter, unstepped
380 {
381 Json::Value params;
382 params[jss::account] = bob.human();
383 params[jss::type] = jss::nft_page;
384 auto resp = env.rpc("json", "account_objects", to_string(params));
385 BEAST_EXPECT(!resp.isMember(jss::marker));
386 Json::Value& aobjs = resp[jss::result][jss::account_objects];
387 BEAST_EXPECT(aobjs.size() == 1);
388 BEAST_EXPECT(aobjs[0u][sfLedgerEntryType.jsonName] == jss::NFTokenPage);
389 BEAST_EXPECT(aobjs[0u][sfNFTokens.jsonName].size() == 1);
390 }
391 // test stepped one-at-a-time with limit=1, resume from prev marker
392 {
393 Json::Value params;
394 params[jss::account] = bob.human();
395 params[jss::limit] = 1;
396
397 Json::Value resp = env.rpc("json", "account_objects", to_string(params));
398 Json::Value& aobjs = resp[jss::result][jss::account_objects];
399 BEAST_EXPECT(aobjs.size() == 1);
400 auto& aobj = aobjs[0U];
401 BEAST_EXPECT(!resp[jss::result].isMember(jss::limit));
402 BEAST_EXPECT(!resp[jss::result].isMember(jss::marker));
403
404 BEAST_EXPECT(aobj == unpaged[0u]);
405 }
406
407 // Add more objects in addition to the NFToken Page.
408 env.trust(USD1(1000), bob);
409 env.trust(USD2(1000), bob);
410
411 env(pay(gw1, bob, USD1(1000)));
412 env(pay(gw2, bob, USD2(1000)));
413
414 env(offer(bob, XRP(100), bob["USD"](1)), txflags(tfPassive));
415 env(offer(bob, XRP(100), USD1(1)), txflags(tfPassive));
416 env.close();
417
418 // test 'unstepped'
419 {
420 Json::Value params;
421 params[jss::account] = bob.human();
422 auto resp = env.rpc("json", "account_objects", to_string(params));
423 BEAST_EXPECT(!resp.isMember(jss::marker));
424
425 unpaged = resp[jss::result][jss::account_objects];
426 BEAST_EXPECT(unpaged.size() == 5);
427 }
428 // test request with type parameter as filter, unstepped
429 {
430 Json::Value params;
431 params[jss::account] = bob.human();
432 params[jss::type] = jss::nft_page;
433 auto resp = env.rpc("json", "account_objects", to_string(params));
434 BEAST_EXPECT(!resp.isMember(jss::marker));
435 Json::Value& aobjs = resp[jss::result][jss::account_objects];
436 BEAST_EXPECT(aobjs.size() == 1);
437 BEAST_EXPECT(aobjs[0u][sfLedgerEntryType.jsonName] == jss::NFTokenPage);
438 BEAST_EXPECT(aobjs[0u][sfNFTokens.jsonName].size() == 1);
439 }
440 // test stepped one-at-a-time with limit=1, resume from prev marker
441 {
442 Json::Value params;
443 params[jss::account] = bob.human();
444 params[jss::limit] = 1;
445 for (int i = 0; i < 5; ++i)
446 {
447 Json::Value resp = env.rpc("json", "account_objects", to_string(params));
448 Json::Value& aobjs = resp[jss::result][jss::account_objects];
449 BEAST_EXPECT(aobjs.size() == 1);
450 auto& aobj = aobjs[0U];
451 if (i < 4)
452 {
453 BEAST_EXPECT(resp[jss::result][jss::limit] == 1);
454 BEAST_EXPECT(resp[jss::result].isMember(jss::marker));
455 }
456 else
457 {
458 BEAST_EXPECT(!resp[jss::result].isMember(jss::limit));
459 BEAST_EXPECT(!resp[jss::result].isMember(jss::marker));
460 }
461
462 BEAST_EXPECT(aobj == unpaged[i]);
463
464 params[jss::marker] = resp[jss::result][jss::marker];
465 }
466 }
467
468 // Make sure things still work if there is more than 1 NFT Page.
469 for (int i = 0; i < 32; ++i)
470 {
471 env(token::mint(bob, 0u), txflags(tfTransferable));
472 env.close();
473 }
474 // test 'unstepped'
475 {
476 Json::Value params;
477 params[jss::account] = bob.human();
478 auto resp = env.rpc("json", "account_objects", to_string(params));
479 BEAST_EXPECT(!resp.isMember(jss::marker));
480
481 unpaged = resp[jss::result][jss::account_objects];
482 BEAST_EXPECT(unpaged.size() == 6);
483 }
484 // test request with type parameter as filter, unstepped
485 {
486 Json::Value params;
487 params[jss::account] = bob.human();
488 params[jss::type] = jss::nft_page;
489 auto resp = env.rpc("json", "account_objects", to_string(params));
490 BEAST_EXPECT(!resp.isMember(jss::marker));
491 Json::Value const& aobjs = resp[jss::result][jss::account_objects];
492 BEAST_EXPECT(aobjs.size() == 2);
493 }
494 // test stepped one-at-a-time with limit=1, resume from prev marker
495 {
496 Json::Value params;
497 params[jss::account] = bob.human();
498 params[jss::limit] = 1;
499 for (int i = 0; i < 6; ++i)
500 {
501 Json::Value resp = env.rpc("json", "account_objects", to_string(params));
502 Json::Value& aobjs = resp[jss::result][jss::account_objects];
503 BEAST_EXPECT(aobjs.size() == 1);
504 auto& aobj = aobjs[0U];
505 if (i < 5)
506 {
507 BEAST_EXPECT(resp[jss::result][jss::limit] == 1);
508 BEAST_EXPECT(resp[jss::result].isMember(jss::marker));
509 }
510 else
511 {
512 BEAST_EXPECT(!resp[jss::result].isMember(jss::limit));
513 BEAST_EXPECT(!resp[jss::result].isMember(jss::marker));
514 }
515
516 BEAST_EXPECT(aobj == unpaged[i]);
517
518 params[jss::marker] = resp[jss::result][jss::marker];
519 }
520 }
521 }
522
523 void
525 {
526 testcase("object types");
527
528 // Give gw a bunch of ledger objects and make sure we can retrieve
529 // them by type.
530 using namespace jtx;
531
532 Account const alice{"alice"};
533 Account const gw{"gateway"};
534 auto const USD = gw["USD"];
535
536 auto const features =
537 testable_amendments() | featureXChainBridge | featurePermissionedDomains;
538 Env env(*this, features);
539
540 // Make a lambda we can use to get "account_objects" easily.
541 auto acctObjs = [&env](
542 AccountID const& acct,
546 Json::Value params;
547 params[jss::account] = to_string(acct);
548 if (type)
549 params[jss::type] = *type;
550 if (limit)
551 params[jss::limit] = *limit;
552 if (marker)
553 params[jss::marker] = *marker;
554 params[jss::ledger_index] = "validated";
555 return env.rpc("json", "account_objects", to_string(params));
556 };
557
558 // Make a lambda that easily identifies the size of account objects.
559 auto acctObjsIsSize = [](Json::Value const& resp, unsigned size) {
560 return resp[jss::result][jss::account_objects].isArray() &&
561 (resp[jss::result][jss::account_objects].size() == size);
562 };
563
564 // Make a lambda that checks if the response has error for invalid type
565 auto acctObjsTypeIsInvalid = [](Json::Value const& resp) {
566 return resp[jss::result].isMember(jss::error) &&
567 resp[jss::result][jss::error_message] == "Invalid field \'type\'.";
568 };
569
570 env.fund(XRP(10000), gw, alice);
571 env.close();
572
573 // Since the account is empty now, all account objects should come
574 // back empty.
575 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::account), 0));
576 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::check), 0));
577 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::deposit_preauth), 0));
578 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::escrow), 0));
579 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::nft_page), 0));
580 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::offer), 0));
581 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::payment_channel), 0));
582 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::signer_list), 0));
583 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::state), 0));
584 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::ticket), 0));
585 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::amm), 0));
586 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::did), 0));
587 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::permissioned_domain), 0));
588
589 // we expect invalid field type reported for the following types
590 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::amendments)));
591 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::directory)));
592 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::fee)));
593 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::hashes)));
594 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::NegativeUNL)));
595
596 // gw mints an NFT so we can find it.
597 uint256 const nftID{token::getNextID(env, gw, 0u, tfTransferable)};
598 env(token::mint(gw, 0u), txflags(tfTransferable));
599 env.close();
600 {
601 // Find the NFToken page and make sure it's the right one.
602 Json::Value const resp = acctObjs(gw, jss::nft_page);
603 BEAST_EXPECT(acctObjsIsSize(resp, 1));
604
605 auto const& nftPage = resp[jss::result][jss::account_objects][0u];
606 BEAST_EXPECT(nftPage[sfNFTokens.jsonName].size() == 1);
607 BEAST_EXPECT(
608 nftPage[sfNFTokens.jsonName][0u][sfNFToken.jsonName][sfNFTokenID.jsonName] ==
609 to_string(nftID));
610 }
611
612 // Set up a trust line so we can find it.
613 env.trust(USD(1000), alice);
614 env.close();
615 env(pay(gw, alice, USD(5)));
616 env.close();
617 {
618 // Find the trustline and make sure it's the right one.
619 Json::Value const resp = acctObjs(gw, jss::state);
620 BEAST_EXPECT(acctObjsIsSize(resp, 1));
621
622 auto const& state = resp[jss::result][jss::account_objects][0u];
623 BEAST_EXPECT(state[sfBalance.jsonName][jss::value].asInt() == -5);
624 BEAST_EXPECT(state[sfHighLimit.jsonName][jss::value].asUInt() == 1000);
625 }
626 // gw writes a check for USD(10) to alice.
627 env(check::create(gw, alice, USD(10)));
628 env.close();
629 {
630 // Find the check.
631 Json::Value const resp = acctObjs(gw, jss::check);
632 BEAST_EXPECT(acctObjsIsSize(resp, 1));
633
634 auto const& check = resp[jss::result][jss::account_objects][0u];
635 BEAST_EXPECT(check[sfAccount.jsonName] == gw.human());
636 BEAST_EXPECT(check[sfDestination.jsonName] == alice.human());
637 BEAST_EXPECT(check[sfSendMax.jsonName][jss::value].asUInt() == 10);
638 }
639 // gw preauthorizes payments from alice.
640 env(deposit::auth(gw, alice));
641 env.close();
642 {
643 // Find the pre-authorization.
644 Json::Value const resp = acctObjs(gw, jss::deposit_preauth);
645 BEAST_EXPECT(acctObjsIsSize(resp, 1));
646
647 auto const& preauth = resp[jss::result][jss::account_objects][0u];
648 BEAST_EXPECT(preauth[sfAccount.jsonName] == gw.human());
649 BEAST_EXPECT(preauth[sfAuthorize.jsonName] == alice.human());
650 }
651 {
652 // gw creates an escrow that we can look for in the ledger.
653 Json::Value jvEscrow;
654 jvEscrow[jss::TransactionType] = jss::EscrowCreate;
655 jvEscrow[jss::Account] = gw.human();
656 jvEscrow[jss::Destination] = gw.human();
657 jvEscrow[jss::Amount] = XRP(100).value().getJson(JsonOptions::none);
658 jvEscrow[sfFinishAfter.jsonName] = env.now().time_since_epoch().count() + 1;
659 env(jvEscrow);
660 env.close();
661 }
662 {
663 // Find the escrow.
664 Json::Value const resp = acctObjs(gw, jss::escrow);
665 BEAST_EXPECT(acctObjsIsSize(resp, 1));
666
667 auto const& escrow = resp[jss::result][jss::account_objects][0u];
668 BEAST_EXPECT(escrow[sfAccount.jsonName] == gw.human());
669 BEAST_EXPECT(escrow[sfDestination.jsonName] == gw.human());
670 BEAST_EXPECT(escrow[sfAmount.jsonName].asUInt() == 100'000'000);
671 }
672
673 {
674 std::string const credentialType1 = "credential1";
675 Account issuer("issuer");
676 env.fund(XRP(5000), issuer);
677
678 // gw creates an PermissionedDomain.
679 env(pdomain::setTx(gw, {{issuer, credentialType1}}));
680 env.close();
681
682 // Find the PermissionedDomain.
683 Json::Value const resp = acctObjs(gw, jss::permissioned_domain);
684 BEAST_EXPECT(acctObjsIsSize(resp, 1));
685
686 auto const& permissionedDomain = resp[jss::result][jss::account_objects][0u];
687 BEAST_EXPECT(
688 permissionedDomain.isMember(jss::Owner) &&
689 (permissionedDomain[jss::Owner] == gw.human()));
690 bool const check1 = BEAST_EXPECT(
691 permissionedDomain.isMember(jss::AcceptedCredentials) &&
692 permissionedDomain[jss::AcceptedCredentials].isArray() &&
693 (permissionedDomain[jss::AcceptedCredentials].size() == 1) &&
694 (permissionedDomain[jss::AcceptedCredentials][0u].isMember(jss::Credential)));
695
696 if (check1)
697 {
698 auto const& credential =
699 permissionedDomain[jss::AcceptedCredentials][0u][jss::Credential];
700 BEAST_EXPECT(
701 credential.isMember(sfIssuer.jsonName) &&
702 (credential[sfIssuer.jsonName] == issuer.human()));
703 BEAST_EXPECT(
704 credential.isMember(sfCredentialType.jsonName) &&
705 (credential[sfCredentialType.jsonName] == strHex(credentialType1)));
706 }
707 }
708
709 {
710 // Create a bridge
712 Env scEnv(*this, envconfig(), features);
713 x.createScBridgeObjects(scEnv);
714
715 auto scEnvAcctObjs = [&](Account const& acct, char const* type) {
716 Json::Value params;
717 params[jss::account] = acct.human();
718 params[jss::type] = type;
719 params[jss::ledger_index] = "validated";
720 return scEnv.rpc("json", "account_objects", to_string(params));
721 };
722
723 Json::Value const resp = scEnvAcctObjs(Account::master, jss::bridge);
724
725 BEAST_EXPECT(acctObjsIsSize(resp, 1));
726 auto const& acct_bridge = resp[jss::result][jss::account_objects][0u];
727 BEAST_EXPECT(acct_bridge[sfAccount.jsonName] == Account::master.human());
728 BEAST_EXPECT(acct_bridge[sfLedgerEntryType.getJsonName()] == "Bridge");
729 BEAST_EXPECT(acct_bridge[sfXChainClaimID.getJsonName()].asUInt() == 0);
730 BEAST_EXPECT(acct_bridge[sfXChainAccountClaimCount.getJsonName()].asUInt() == 0);
731 BEAST_EXPECT(acct_bridge[sfXChainAccountCreateCount.getJsonName()].asUInt() == 0);
732 BEAST_EXPECT(acct_bridge[sfMinAccountCreateAmount.getJsonName()].asUInt() == 20000000);
733 BEAST_EXPECT(acct_bridge[sfSignatureReward.getJsonName()].asUInt() == 1000000);
734 BEAST_EXPECT(acct_bridge[sfXChainBridge.getJsonName()] == x.jvb);
735 }
736 {
737 // Alice and Bob create a xchain sequence number that we can look
738 // for in the ledger.
740 Env scEnv(*this, envconfig(), features);
741 x.createScBridgeObjects(scEnv);
742
744 scEnv.close();
745 scEnv(xchain_create_claim_id(x.scBob, x.jvb, x.reward, x.mcBob));
746 scEnv.close();
747
748 auto scEnvAcctObjs = [&](Account const& acct, char const* type) {
749 Json::Value params;
750 params[jss::account] = acct.human();
751 params[jss::type] = type;
752 params[jss::ledger_index] = "validated";
753 return scEnv.rpc("json", "account_objects", to_string(params));
754 };
755
756 {
757 // Find the xchain sequence number for Andrea.
758 Json::Value const resp = scEnvAcctObjs(x.scAlice, jss::xchain_owned_claim_id);
759 BEAST_EXPECT(acctObjsIsSize(resp, 1));
760
761 auto const& xchain_seq = resp[jss::result][jss::account_objects][0u];
762 BEAST_EXPECT(xchain_seq[sfAccount.jsonName] == x.scAlice.human());
763 BEAST_EXPECT(xchain_seq[sfXChainClaimID.getJsonName()].asUInt() == 1);
764 }
765 {
766 // and the one for Bob
767 Json::Value const resp = scEnvAcctObjs(x.scBob, jss::xchain_owned_claim_id);
768 BEAST_EXPECT(acctObjsIsSize(resp, 1));
769
770 auto const& xchain_seq = resp[jss::result][jss::account_objects][0u];
771 BEAST_EXPECT(xchain_seq[sfAccount.jsonName] == x.scBob.human());
772 BEAST_EXPECT(xchain_seq[sfXChainClaimID.getJsonName()].asUInt() == 2);
773 }
774 }
775 {
777 Env scEnv(*this, envconfig(), features);
778 x.createScBridgeObjects(scEnv);
779 auto const amt = XRP(1000);
780
781 // send first batch of account create attestations, so the
782 // xchain_create_account_claim_id should be present on the door
783 // account (Account::master) to collect the signatures until a
784 // quorum is reached
785 scEnv(
787 x.scAttester,
788 x.jvb,
789 x.mcCarol,
790 amt,
791 x.reward,
792 x.payees[0],
793 true,
794 1,
795 x.scuAlice,
796 x.signers[0]));
797 scEnv.close();
798
799 auto scEnvAcctObjs = [&](Account const& acct, char const* type) {
800 Json::Value params;
801 params[jss::account] = acct.human();
802 params[jss::type] = type;
803 params[jss::ledger_index] = "validated";
804 return scEnv.rpc("json", "account_objects", to_string(params));
805 };
806
807 {
808 // Find the xchain_create_account_claim_id
809 Json::Value const resp =
810 scEnvAcctObjs(Account::master, jss::xchain_owned_create_account_claim_id);
811 BEAST_EXPECT(acctObjsIsSize(resp, 1));
812
813 auto const& xchain_create_account_claim_id =
814 resp[jss::result][jss::account_objects][0u];
815 BEAST_EXPECT(
816 xchain_create_account_claim_id[sfAccount.jsonName] == Account::master.human());
817 BEAST_EXPECT(
818 xchain_create_account_claim_id[sfXChainAccountCreateCount.getJsonName()]
819 .asUInt() == 1);
820 }
821 }
822
823 // gw creates an offer that we can look for in the ledger.
824 env(offer(gw, USD(7), XRP(14)));
825 env.close();
826 {
827 // Find the offer.
828 Json::Value const resp = acctObjs(gw, jss::offer);
829 BEAST_EXPECT(acctObjsIsSize(resp, 1));
830
831 auto const& offer = resp[jss::result][jss::account_objects][0u];
832 BEAST_EXPECT(offer[sfAccount.jsonName] == gw.human());
833 BEAST_EXPECT(offer[sfTakerGets.jsonName].asUInt() == 14'000'000);
834 BEAST_EXPECT(offer[sfTakerPays.jsonName][jss::value].asUInt() == 7);
835 }
836 {
837 // Create a payment channel from qw to alice that we can look
838 // for.
839 Json::Value jvPayChan;
840 jvPayChan[jss::TransactionType] = jss::PaymentChannelCreate;
841 jvPayChan[jss::Account] = gw.human();
842 jvPayChan[jss::Destination] = alice.human();
843 jvPayChan[jss::Amount] = XRP(300).value().getJson(JsonOptions::none);
844 jvPayChan[sfSettleDelay.jsonName] = 24 * 60 * 60;
845 jvPayChan[sfPublicKey.jsonName] = strHex(gw.pk().slice());
846 env(jvPayChan);
847 env.close();
848 }
849 {
850 // Find the payment channel.
851 Json::Value const resp = acctObjs(gw, jss::payment_channel);
852 BEAST_EXPECT(acctObjsIsSize(resp, 1));
853
854 auto const& payChan = resp[jss::result][jss::account_objects][0u];
855 BEAST_EXPECT(payChan[sfAccount.jsonName] == gw.human());
856 BEAST_EXPECT(payChan[sfAmount.jsonName].asUInt() == 300'000'000);
857 BEAST_EXPECT(payChan[sfSettleDelay.jsonName].asUInt() == 24 * 60 * 60);
858 }
859
860 {
861 // gw creates a DID that we can look for in the ledger.
862 Json::Value jvDID;
863 jvDID[jss::TransactionType] = jss::DIDSet;
864 jvDID[jss::Account] = gw.human();
865 jvDID[sfURI.jsonName] = strHex(std::string{"uri"});
866 env(jvDID);
867 env.close();
868 }
869 {
870 // Find the DID.
871 Json::Value const resp = acctObjs(gw, jss::did);
872 BEAST_EXPECT(acctObjsIsSize(resp, 1));
873
874 auto const& did = resp[jss::result][jss::account_objects][0u];
875 BEAST_EXPECT(did[sfAccount.jsonName] == gw.human());
876 BEAST_EXPECT(did[sfURI.jsonName] == strHex(std::string{"uri"}));
877 }
878 // Make gw multisigning by adding a signerList.
879 env(jtx::signers(gw, 6, {{alice, 7}}));
880 env.close();
881 {
882 // Find the signer list.
883 Json::Value const resp = acctObjs(gw, jss::signer_list);
884 BEAST_EXPECT(acctObjsIsSize(resp, 1));
885
886 auto const& signerList = resp[jss::result][jss::account_objects][0u];
887 BEAST_EXPECT(signerList[sfSignerQuorum.jsonName] == 6);
888 auto const& entry = signerList[sfSignerEntries.jsonName][0u][sfSignerEntry.jsonName];
889 BEAST_EXPECT(entry[sfAccount.jsonName] == alice.human());
890 BEAST_EXPECT(entry[sfSignerWeight.jsonName].asUInt() == 7);
891 }
892
893 {
894 auto const seq = env.seq(gw);
895 // Create a Ticket for gw.
896 env(ticket::create(gw, 1));
897 env.close();
898
899 // Find the ticket.
900 Json::Value const resp = acctObjs(gw, jss::ticket);
901 BEAST_EXPECT(acctObjsIsSize(resp, 1));
902
903 auto const& ticket = resp[jss::result][jss::account_objects][0u];
904 BEAST_EXPECT(ticket[sfAccount.jsonName] == gw.human());
905 BEAST_EXPECT(ticket[sfLedgerEntryType.jsonName] == jss::Ticket);
906 BEAST_EXPECT(ticket[sfTicketSequence.jsonName].asUInt() == seq + 1);
907 }
908
909 {
910 // See how "deletion_blockers_only" handles gw's directory.
911 Json::Value params;
912 params[jss::account] = gw.human();
913 params[jss::deletion_blockers_only] = true;
914 auto resp = env.rpc("json", "account_objects", to_string(params));
915
916 std::vector<std::string> const expectedLedgerTypes = [] {
918 jss::Escrow.c_str(),
919 jss::Check.c_str(),
920 jss::NFTokenPage.c_str(),
921 jss::RippleState.c_str(),
922 jss::PayChannel.c_str(),
923 jss::PermissionedDomain.c_str()};
924 std::sort(v.begin(), v.end());
925 return v;
926 }();
927
928 std::uint32_t const expectedAccountObjects{
929 static_cast<std::uint32_t>(std::size(expectedLedgerTypes))};
930
931 if (BEAST_EXPECT(acctObjsIsSize(resp, expectedAccountObjects)))
932 {
933 auto const& aobjs = resp[jss::result][jss::account_objects];
934 std::vector<std::string> gotLedgerTypes;
935 gotLedgerTypes.reserve(expectedAccountObjects);
936 for (std::uint32_t i = 0; i < expectedAccountObjects; ++i)
937 {
938 gotLedgerTypes.push_back(aobjs[i]["LedgerEntryType"].asString());
939 }
940 std::sort(gotLedgerTypes.begin(), gotLedgerTypes.end());
941 BEAST_EXPECT(gotLedgerTypes == expectedLedgerTypes);
942 }
943 }
944 {
945 // See how "deletion_blockers_only" with `type` handles gw's
946 // directory.
947 Json::Value params;
948 params[jss::account] = gw.human();
949 params[jss::deletion_blockers_only] = true;
950 params[jss::type] = jss::escrow;
951 auto resp = env.rpc("json", "account_objects", to_string(params));
952
953 if (BEAST_EXPECT(acctObjsIsSize(resp, 1u)))
954 {
955 auto const& aobjs = resp[jss::result][jss::account_objects];
956 BEAST_EXPECT(aobjs[0u]["LedgerEntryType"] == jss::Escrow);
957 }
958 }
959 {
960 // Make a lambda to get the types
961 auto getTypes = [&](Json::Value const& resp, std::vector<std::string>& typesOut) {
962 auto const objs = resp[jss::result][jss::account_objects];
963 for (auto const& obj : resp[jss::result][jss::account_objects])
964 typesOut.push_back(obj[sfLedgerEntryType.fieldName].asString());
965 std::sort(typesOut.begin(), typesOut.end());
966 };
967 // Make a lambda we can use to check the number of fetched
968 // account objects and their ledger type
969 auto expectObjects = [&](Json::Value const& resp,
970 std::vector<std::string> const& types) -> bool {
971 if (!acctObjsIsSize(resp, types.size()))
972 return false;
974 getTypes(resp, typesOut);
975 return types == typesOut;
976 };
977 // Find AMM objects
978 AMM amm(env, gw, XRP(1'000), USD(1'000));
979 amm.deposit(alice, USD(1));
980 // AMM account has 4 objects: AMM object and 3 trustlines
981 auto const lines = getAccountLines(env, amm.ammAccount());
982 BEAST_EXPECT(lines[jss::lines].size() == 3);
983 // request AMM only, doesn't depend on the limit
984 BEAST_EXPECT(acctObjsIsSize(acctObjs(amm.ammAccount(), jss::amm), 1));
985 // request first two objects
986 auto resp = acctObjs(amm.ammAccount(), std::nullopt, 2);
988 getTypes(resp, typesOut);
989 // request next two objects
990 resp = acctObjs(
991 amm.ammAccount(), std::nullopt, 10, resp[jss::result][jss::marker].asString());
992 getTypes(resp, typesOut);
993 BEAST_EXPECT(
994 (typesOut ==
996 jss::AMM.c_str(),
997 jss::RippleState.c_str(),
998 jss::RippleState.c_str(),
999 jss::RippleState.c_str()}));
1000 // filter by state: there are three trustlines
1001 resp = acctObjs(amm.ammAccount(), jss::state, 10);
1002 BEAST_EXPECT(expectObjects(
1003 resp,
1004 {jss::RippleState.c_str(), jss::RippleState.c_str(), jss::RippleState.c_str()}));
1005 // AMM account doesn't own offers
1006 BEAST_EXPECT(acctObjsIsSize(acctObjs(amm.ammAccount(), jss::offer), 0));
1007 // gw account doesn't own AMM object
1008 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::amm), 0));
1009 }
1010
1011 // we still expect invalid field type reported for the following types
1012 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::amendments)));
1013 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::directory)));
1014 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::fee)));
1015 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::hashes)));
1016 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::NegativeUNL)));
1017
1018 // Run up the number of directory entries so gw has two
1019 // directory nodes.
1020 for (int d = 1'000'032; d >= 1'000'000; --d)
1021 {
1022 env(offer(gw, USD(1), drops(d)));
1023 env.close();
1024 }
1025
1026 // Verify that the non-returning types still don't return anything.
1027 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::account), 0));
1028 }
1029
1030 void
1032 {
1033 // there's some bug found in account_nfts method that it did not
1034 // return invalid params when providing unassociated nft marker.
1035 // this test tests both situations when providing valid nft marker
1036 // and unassociated nft marker.
1037 testcase("NFTsMarker");
1038
1039 using namespace jtx;
1040 Env env(*this);
1041
1042 Account const bob{"bob"};
1043 env.fund(XRP(10000), bob);
1044
1045 static constexpr unsigned nftsSize = 10;
1046 for (unsigned i = 0; i < nftsSize; i++)
1047 {
1048 env(token::mint(bob, 0));
1049 }
1050
1051 env.close();
1052
1053 // save the NFTokenIDs to use later
1054 std::vector<Json::Value> tokenIDs;
1055 {
1056 Json::Value params;
1057 params[jss::account] = bob.human();
1058 params[jss::ledger_index] = "validated";
1059 Json::Value const resp = env.rpc("json", "account_nfts", to_string(params));
1060 Json::Value const& nfts = resp[jss::result][jss::account_nfts];
1061 for (Json::Value const& nft : nfts)
1062 tokenIDs.push_back(nft["NFTokenID"]);
1063 }
1064
1065 // this lambda function is used to check if the account_nfts method
1066 // returns the correct token information. lastIndex is used to query the
1067 // last marker.
1068 auto compareNFTs = [&tokenIDs, &env, &bob](unsigned const limit, unsigned const lastIndex) {
1069 Json::Value params;
1070 params[jss::account] = bob.human();
1071 params[jss::limit] = limit;
1072 params[jss::marker] = tokenIDs[lastIndex];
1073 params[jss::ledger_index] = "validated";
1074 Json::Value const resp = env.rpc("json", "account_nfts", to_string(params));
1075
1076 if (resp[jss::result].isMember(jss::error))
1077 return false;
1078
1079 Json::Value const& nfts = resp[jss::result][jss::account_nfts];
1080 unsigned const nftsCount =
1081 tokenIDs.size() - lastIndex - 1 < limit ? tokenIDs.size() - lastIndex - 1 : limit;
1082
1083 if (nfts.size() != nftsCount)
1084 return false;
1085
1086 for (unsigned i = 0; i < nftsCount; i++)
1087 {
1088 if (nfts[i]["NFTokenID"] != tokenIDs[lastIndex + 1 + i])
1089 return false;
1090 }
1091
1092 return true;
1093 };
1094
1095 // test a valid marker which is equal to the third tokenID
1096 BEAST_EXPECT(compareNFTs(4, 2));
1097
1098 // test a valid marker which is equal to the 8th tokenID
1099 BEAST_EXPECT(compareNFTs(4, 7));
1100
1101 // lambda that holds common code for invalid cases.
1102 auto testInvalidMarker = [&env, &bob](auto marker, char const* errorMessage) {
1103 Json::Value params;
1104 params[jss::account] = bob.human();
1105 params[jss::limit] = 4;
1106 params[jss::ledger_index] = jss::validated;
1107 params[jss::marker] = marker;
1108 Json::Value const resp = env.rpc("json", "account_nfts", to_string(params));
1109 return resp[jss::result][jss::error_message] == errorMessage;
1110 };
1111
1112 // test an invalid marker that is not a string
1113 BEAST_EXPECT(testInvalidMarker(17, "Invalid field \'marker\', not string."));
1114
1115 // test an invalid marker that has a non-hex character
1116 BEAST_EXPECT(testInvalidMarker(
1117 "00000000F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B900000000000000000G",
1118 "Invalid field \'marker\'."));
1119
1120 // this lambda function is used to create some fake marker using given
1121 // taxon and sequence because we want to test some unassociated markers
1122 // later
1123 auto createFakeNFTMarker = [](AccountID const& issuer,
1124 std::uint32_t taxon,
1125 std::uint32_t tokenSeq,
1126 std::uint16_t flags = 0,
1127 std::uint16_t fee = 0) {
1128 // the marker has the exact same format as an NFTokenID
1129 return to_string(
1130 NFTokenMint::createNFTokenID(flags, fee, issuer, nft::toTaxon(taxon), tokenSeq));
1131 };
1132
1133 // test an unassociated marker which does not exist in the NFTokenIDs
1134 BEAST_EXPECT(testInvalidMarker(
1135 createFakeNFTMarker(bob.id(), 0x000000000, 0x00000000), "Invalid field \'marker\'."));
1136
1137 // test an unassociated marker which exceeds the maximum value of the
1138 // existing NFTokenID
1139 BEAST_EXPECT(testInvalidMarker(
1140 createFakeNFTMarker(bob.id(), 0xFFFFFFFF, 0xFFFFFFFF), "Invalid field \'marker\'."));
1141 }
1142
1143 void
1145 {
1146 testcase("account_nfts");
1147
1148 using namespace jtx;
1149 Env env(*this);
1150
1151 // test validation
1152 {
1153 auto testInvalidAccountParam = [&](auto const& param) {
1154 Json::Value params;
1155 params[jss::account] = param;
1156 auto jrr = env.rpc("json", "account_nfts", to_string(params))[jss::result];
1157 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
1158 BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'account'.");
1159 };
1160
1161 testInvalidAccountParam(1);
1162 testInvalidAccountParam(1.1);
1163 testInvalidAccountParam(true);
1164 testInvalidAccountParam(Json::Value(Json::nullValue));
1165 testInvalidAccountParam(Json::Value(Json::objectValue));
1166 testInvalidAccountParam(Json::Value(Json::arrayValue));
1167 }
1168 }
1169
1170 void
1172 {
1173 testcase("AccountObjectMarker");
1174
1175 using namespace jtx;
1176 Env env(*this);
1177
1178 Account const alice{"alice"};
1179 Account const bob{"bob"};
1180 Account const carol{"carol"};
1181 env.fund(XRP(10000), alice, bob, carol);
1182
1183 unsigned const accountObjectSize = 30;
1184 for (unsigned i = 0; i < accountObjectSize; i++)
1185 env(check::create(alice, bob, XRP(10)));
1186
1187 for (unsigned i = 0; i < 10; i++)
1188 env(token::mint(carol, 0));
1189
1190 env.close();
1191
1192 unsigned const limit = 11;
1193 Json::Value marker;
1194
1195 // test account_objects with a limit and update marker
1196 {
1197 Json::Value params;
1198 params[jss::account] = bob.human();
1199 params[jss::limit] = limit;
1200 params[jss::ledger_index] = "validated";
1201 auto resp = env.rpc("json", "account_objects", to_string(params));
1202 auto& accountObjects = resp[jss::result][jss::account_objects];
1203 marker = resp[jss::result][jss::marker];
1204 BEAST_EXPECT(!resp[jss::result].isMember(jss::error));
1205 BEAST_EXPECT(accountObjects.size() == limit);
1206 }
1207
1208 // test account_objects with valid marker and update marker
1209 {
1210 Json::Value params;
1211 params[jss::account] = bob.human();
1212 params[jss::limit] = limit;
1213 params[jss::marker] = marker;
1214 params[jss::ledger_index] = "validated";
1215 auto resp = env.rpc("json", "account_objects", to_string(params));
1216 auto& accountObjects = resp[jss::result][jss::account_objects];
1217 marker = resp[jss::result][jss::marker];
1218 BEAST_EXPECT(!resp[jss::result].isMember(jss::error));
1219 BEAST_EXPECT(accountObjects.size() == limit);
1220 }
1221
1222 // this lambda function is used to check invalid marker response.
1223 auto testInvalidMarker = [&](std::string& marker) {
1224 Json::Value params;
1225 params[jss::account] = bob.human();
1226 params[jss::limit] = limit;
1227 params[jss::ledger_index] = jss::validated;
1228 params[jss::marker] = marker;
1229 Json::Value const resp = env.rpc("json", "account_objects", to_string(params));
1230 return resp[jss::result][jss::error_message] == "Invalid field \'marker\'.";
1231 };
1232
1233 auto const markerStr = marker.asString();
1234 auto const& idx = markerStr.find(',');
1235 auto const dirIndex = markerStr.substr(0, idx);
1236 auto const entryIndex = markerStr.substr(idx + 1);
1237
1238 // test account_objects with an invalid marker that contains no ','
1239 {
1240 std::string s = dirIndex + entryIndex;
1241 BEAST_EXPECT(testInvalidMarker(s));
1242 }
1243
1244 // test invalid marker by adding invalid string after the maker:
1245 // "dirIndex,entryIndex,1234"
1246 {
1247 std::string s = markerStr + ",1234";
1248 BEAST_EXPECT(testInvalidMarker(s));
1249 }
1250
1251 // test account_objects with an invalid marker containing invalid
1252 // dirIndex by replacing some characters from the dirIndex.
1253 {
1254 std::string s = markerStr;
1255 s.replace(0, 7, "FFFFFFF");
1256 BEAST_EXPECT(testInvalidMarker(s));
1257 }
1258
1259 // test account_objects with an invalid marker containing invalid
1260 // entryIndex by replacing some characters from the entryIndex.
1261 {
1262 std::string s = entryIndex;
1263 s.replace(0, 7, "FFFFFFF");
1264 s = dirIndex + ',' + s;
1265 BEAST_EXPECT(testInvalidMarker(s));
1266 }
1267
1268 // test account_objects with an invalid marker containing invalid
1269 // dirIndex with marker: ",entryIndex"
1270 {
1271 std::string s = ',' + entryIndex;
1272 BEAST_EXPECT(testInvalidMarker(s));
1273 }
1274
1275 // test account_objects with marker: "0,entryIndex", this is still
1276 // valid, because when dirIndex = 0, we will use root key to find
1277 // dir.
1278 {
1279 std::string const s = "0," + entryIndex;
1280 Json::Value params;
1281 params[jss::account] = bob.human();
1282 params[jss::limit] = limit;
1283 params[jss::marker] = s;
1284 params[jss::ledger_index] = "validated";
1285 auto resp = env.rpc("json", "account_objects", to_string(params));
1286 auto& accountObjects = resp[jss::result][jss::account_objects];
1287 BEAST_EXPECT(!resp[jss::result].isMember(jss::error));
1288 BEAST_EXPECT(accountObjects.size() == limit);
1289 }
1290
1291 // test account_objects with an invalid marker containing invalid
1292 // entryIndex with marker: "dirIndex,"
1293 {
1294 std::string s = dirIndex + ',';
1295 BEAST_EXPECT(testInvalidMarker(s));
1296 }
1297
1298 // test account_objects with an invalid marker containing invalid
1299 // entryIndex with marker: "dirIndex,0"
1300 {
1301 std::string s = dirIndex + ",0";
1302 BEAST_EXPECT(testInvalidMarker(s));
1303 }
1304
1305 // continue getting account_objects with valid marker. This will be the
1306 // last page, so response will not contain any marker.
1307 {
1308 Json::Value params;
1309 params[jss::account] = bob.human();
1310 params[jss::limit] = limit;
1311 params[jss::marker] = marker;
1312 params[jss::ledger_index] = "validated";
1313 auto resp = env.rpc("json", "account_objects", to_string(params));
1314 auto& accountObjects = resp[jss::result][jss::account_objects];
1315 BEAST_EXPECT(!resp[jss::result].isMember(jss::error));
1316 BEAST_EXPECT(accountObjects.size() == accountObjectSize - (limit * 2));
1317 BEAST_EXPECT(!resp[jss::result].isMember(jss::marker));
1318 }
1319
1320 // test account_objects when the account only have nft pages, but
1321 // provided invalid entry index.
1322 {
1323 Json::Value params;
1324 params[jss::account] = carol.human();
1325 params[jss::limit] = 10;
1326 params[jss::marker] = "0," + entryIndex;
1327 params[jss::ledger_index] = "validated";
1328 auto resp = env.rpc("json", "account_objects", to_string(params));
1329 auto& accountObjects = resp[jss::result][jss::account_objects];
1330 BEAST_EXPECT(accountObjects.size() == 0);
1331 }
1332 }
1333
1334 void
1345};
1346
1347BEAST_DEFINE_TESTSUITE(AccountObjects, rpc, xrpl);
1348
1349} // namespace test
1350} // namespace xrpl
T begin(T... args)
Unserialize a JSON document into a Value.
Definition json_reader.h:17
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Represents a JSON value.
Definition json_value.h:130
bool isArray() const
UInt size() const
Number of values in array or object.
UInt asUInt() const
std::string asString() const
Returns the unquoted string value.
bool isMember(char const *key) const
Return true if the object has a member named key.
A testsuite class.
Definition suite.h:51
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:150
static uint256 createNFTokenID(std::uint16_t flags, std::uint16_t fee, AccountID const &issuer, nft::Taxon taxon, std::uint32_t tokenSeq)
void run() override
Runs the suite.
Convenience class to test AMM functionality.
Immutable cryptographic account descriptor.
Definition Account.h:19
std::string const & human() const
Returns the human readable public key.
Definition Account.h:94
static Account const master
The master account.
Definition Account.h:28
A transaction testing environment.
Definition Env.h:122
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:100
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:270
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:249
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:301
Json::Value rpc(unsigned apiVersion, std::unordered_map< std::string, std::string > const &headers, std::string const &cmd, Args &&... args)
Execute an RPC command.
Definition Env.h:847
NetClock::time_point now()
Returns the current network time.
Definition Env.h:282
Set the fee on a JTx.
Definition fee.h:17
Match set account flags.
Definition flags.h:108
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition rpc.h:15
Set the flags on a JTx.
Definition txflags.h:11
T end(T... args)
T find(T... args)
T is_same_v
@ nullValue
'null' value
Definition json_value.h:19
@ arrayValue
array value (ordered list)
Definition json_value.h:25
@ objectValue
object value (collection of name/value pairs).
Definition json_value.h:26
Taxon toTaxon(std::uint32_t i)
Definition nft.h:22
Json::Value create(A const &account, A const &dest, STAmount const &sendMax)
Create a check.
Json::Value auth(Account const &account, Account const &auth)
Preauthorize for deposit.
Definition deposit.cpp:13
Json::Value setTx(AccountID const &account, Credentials const &credentials, std::optional< uint256 > domain)
Json::Value create(Account const &account, std::uint32_t count)
Create one of more tickets.
Definition ticket.cpp:12
Json::Value mint(jtx::Account const &account, std::uint32_t nfTokenTaxon)
Mint an NFToken.
Definition token.cpp:14
uint256 getNextID(jtx::Env const &env, jtx::Account const &issuer, std::uint32_t nfTokenTaxon, std::uint16_t flags, std::uint16_t xferFee)
Get the next NFTokenID that will be issued.
Definition token.cpp:48
Json::Value signers(Account const &account, std::uint32_t quorum, std::vector< signer > const &v)
Definition multisign.cpp:15
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:95
Json::Value pay(AccountID const &account, AccountID const &to, AnyAmount amount)
Create a payment.
Definition pay.cpp:11
Json::Value xchain_create_claim_id(Account const &acc, Json::Value const &bridge, STAmount const &reward, Account const &otherChainSource)
FeatureBitset testable_amendments()
Definition Env.h:78
Json::Value create_account_attestation(jtx::Account const &submittingAccount, Json::Value const &jvBridge, jtx::Account const &sendingAccount, jtx::AnyAmount const &sendingAmount, jtx::AnyAmount const &rewardAmount, jtx::Account const &rewardAccount, bool wasLockingChainSend, std::uint64_t createCount, jtx::Account const &dst, jtx::signer const &signer)
std::unique_ptr< Config > envconfig()
creates and initializes a default configuration for jtx::Env
Definition envconfig.h:34
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value getAccountLines(Env &env, AccountID const &acctId)
Json::Value offer(Account const &account, STAmount const &takerPays, STAmount const &takerGets, std::uint32_t flags)
Create an offer.
Definition offer.cpp:10
static char const * bob_account_objects[]
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:602
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:10
T push_back(T... args)
T replace(T... args)
T reserve(T... args)
T size(T... args)
T sort(T... args)
std::vector< signer > const signers
std::vector< Account > const payees
Set the sequence number on a JTx.
Definition seq.h:14
T time_since_epoch(T... args)