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