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/proposal.h>
13#include <test/jtx/sig.h>
14#include <test/jtx/sponsor.h>
15#include <test/jtx/ticket.h>
16#include <test/jtx/token.h>
17#include <test/jtx/trust.h>
18#include <test/jtx/txflags.h>
19#include <test/jtx/xchain_bridge.h>
20
21#include <xrpl/basics/base_uint.h>
22#include <xrpl/basics/strHex.h>
23#include <xrpl/beast/unit_test/suite.h>
24#include <xrpl/json/json_reader.h>
25#include <xrpl/json/json_value.h>
26#include <xrpl/json/to_string.h>
27#include <xrpl/protocol/AccountID.h>
28#include <xrpl/protocol/Feature.h>
29#include <xrpl/protocol/Indexes.h>
30#include <xrpl/protocol/SField.h>
31#include <xrpl/protocol/Seed.h>
32#include <xrpl/protocol/SeqProxy.h>
33#include <xrpl/protocol/TxFlags.h>
34#include <xrpl/protocol/jss.h>
35#include <xrpl/protocol/nft.h>
36#include <xrpl/tx/transactors/nft/NFTokenMint.h>
37
38#include <algorithm>
39#include <chrono> // IWYU pragma: keep
40#include <cstdint>
41#include <iterator>
42#include <optional>
43#include <ranges>
44#include <vector>
45
46namespace xrpl::test {
47
48static char const* gBobAccountObjects[] = {
49 R"json({
50 "Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
51 "BookDirectory" : "50AD0A9E54D2B381288D535EB724E4275FFBF41580D28A925D038D7EA4C68000",
52 "BookNode" : "0",
53 "Flags" : 65536,
54 "LedgerEntryType" : "Offer",
55 "OwnerNode" : "0",
56 "Sequence" : 6,
57 "TakerGets" : {
58 "currency" : "USD",
59 "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
60 "value" : "1"
61 },
62 "TakerPays" : "100000000",
63 "index" : "29665262716C19830E26AEEC0916E476FC7D8EF195FF3B4F06829E64F82A3B3E"
64})json",
65 R"json({
66 "Balance" : {
67 "currency" : "USD",
68 "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
69 "value" : "-1000"
70 },
71 "Flags" : 131072,
72 "HighLimit" : {
73 "currency" : "USD",
74 "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
75 "value" : "1000"
76 },
77 "HighNode" : "0",
78 "LedgerEntryType" : "RippleState",
79 "LowLimit" : {
80 "currency" : "USD",
81 "issuer" : "r9cZvwKU3zzuZK9JFovGg1JC5n7QiqNL8L",
82 "value" : "0"
83 },
84 "LowNode" : "0",
85 "index" : "D13183BCFFC9AAC9F96AEBB5F66E4A652AD1F5D10273AEB615478302BEBFD4A4"
86})json",
87 R"json({
88 "Balance" : {
89 "currency" : "USD",
90 "issuer" : "rrrrrrrrrrrrrrrrrrrrBZbvji",
91 "value" : "-1000"
92 },
93 "Flags" : 131072,
94 "HighLimit" : {
95 "currency" : "USD",
96 "issuer" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
97 "value" : "1000"
98 },
99 "HighNode" : "0",
100 "LedgerEntryType" : "RippleState",
101 "LowLimit" : {
102 "currency" : "USD",
103 "issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW",
104 "value" : "0"
105 },
106 "LowNode" : "0",
107 "index" : "D89BC239086183EB9458C396E643795C1134963E6550E682A190A5F021766D43"
108})json",
109 R"json({
110 "Account" : "rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK",
111 "BookDirectory" : "B025997A323F5C3E03DDF1334471F5984ABDE31C59D463525D038D7EA4C68000",
112 "BookNode" : "0",
113 "Flags" : 65536,
114 "LedgerEntryType" : "Offer",
115 "OwnerNode" : "0",
116 "Sequence" : 7,
117 "TakerGets" : {
118 "currency" : "USD",
119 "issuer" : "r32rQHyesiTtdWFU7UJVtff4nCR5SHCbJW",
120 "value" : "1"
121 },
122 "TakerPays" : "100000000",
123 "index" : "F03ABE26CB8C5F4AFB31A86590BD25C64C5756FCE5CE9704C27AFE291A4A29A1"
124})json"};
125
127{
128public:
129 void
131 {
132 testcase("error cases");
133
134 using namespace jtx;
135 Env env(*this);
136
137 // test error on no account
138 {
139 json::Value const params;
140 auto resp = env.rpc("json", "account_objects", to_string(params));
141 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Missing field 'account'.");
142 }
143 // test account non-string
144 {
145 auto testInvalidAccountParam = [&](auto const& param) {
146 json::Value params;
147 params[jss::account] = param;
148 auto jrr = env.rpc("json", "account_objects", to_string(params))[jss::result];
149 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
150 BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'account'.");
151 };
152
153 testInvalidAccountParam(1);
154 testInvalidAccountParam(1.1);
155 testInvalidAccountParam(true);
156 testInvalidAccountParam(json::Value(json::ValueType::Null));
157 testInvalidAccountParam(json::Value(json::ValueType::Object));
158 testInvalidAccountParam(json::Value(json::ValueType::Array));
159 }
160 // test error on malformed account string.
161 {
162 json::Value params;
163 params[jss::account] = "n94JNrQYkDrpt62bbSR7nVEhdyAvcJXRAsjEkFYyqRkh9SUTYEqV";
164 auto resp = env.rpc("json", "account_objects", to_string(params));
165 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Account malformed.");
166 }
167 // test error on account that's not in the ledger.
168 {
169 json::Value params;
170 params[jss::account] = Account{"bogie"}.human();
171 auto resp = env.rpc("json", "account_objects", to_string(params));
172 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Account not found.");
173 }
174 Account const bob{"bob"};
175 // test error on large ledger_index.
176 {
177 json::Value params;
178 params[jss::account] = bob.human();
179 params[jss::ledger_index] = 10;
180 auto resp = env.rpc("json", "account_objects", to_string(params));
181 BEAST_EXPECT(resp[jss::result][jss::error_message] == "ledgerNotFound");
182 }
183
184 env.fund(XRP(1000), bob);
185 // test error on type param not a string
186 {
187 json::Value params;
188 params[jss::account] = bob.human();
189 params[jss::type] = 10;
190 auto resp = env.rpc("json", "account_objects", to_string(params));
191 BEAST_EXPECT(
192 resp[jss::result][jss::error_message] == "Invalid field 'type', not string.");
193 }
194 // test error on type param not a valid type
195 {
196 json::Value params;
197 params[jss::account] = bob.human();
198 params[jss::type] = "expedited";
199 auto resp = env.rpc("json", "account_objects", to_string(params));
200 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'type'.");
201 }
202 // test error on limit -ve
203 {
204 json::Value params;
205 params[jss::account] = bob.human();
206 params[jss::limit] = -1;
207 auto resp = env.rpc("json", "account_objects", to_string(params));
208 BEAST_EXPECT(
209 resp[jss::result][jss::error_message] ==
210 "Invalid field 'limit', not unsigned integer.");
211 }
212 // test error on sponsored param not a boolean
213 {
214 json::Value params;
215 params[jss::account] = bob.human();
216 params[jss::sponsored] = "true";
217 auto resp = env.rpc("json", "account_objects", to_string(params));
218 BEAST_EXPECT(
219 resp[jss::result][jss::error_message] == "Invalid field 'sponsored', not boolean.");
220 }
221 // test errors on marker
222 {
223 Account const gw{"G"};
224 env.fund(XRP(1000), gw);
225 auto const usd = gw["USD"];
226 env.trust(usd(1000), bob);
227 env(pay(gw, bob, XRP(1)));
228 env(offer(bob, XRP(100), bob["USD"](1)), Txflags(tfPassive));
229
230 json::Value params;
231 params[jss::account] = bob.human();
232 params[jss::limit] = 1;
233 auto resp = env.rpc("json", "account_objects", to_string(params));
234
235 auto resumeMarker = resp[jss::result][jss::marker];
236 std::string mark = to_string(resumeMarker);
237 params[jss::marker] = 10;
238 resp = env.rpc("json", "account_objects", to_string(params));
239 BEAST_EXPECT(
240 resp[jss::result][jss::error_message] == "Invalid field 'marker', not string.");
241
242 params[jss::marker] = "This is a string with no comma";
243 resp = env.rpc("json", "account_objects", to_string(params));
244 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
245
246 params[jss::marker] = "This string has a comma, but is not hex";
247 resp = env.rpc("json", "account_objects", to_string(params));
248 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
249
250 params[jss::marker] = std::string(&mark[1U], 64);
251 resp = env.rpc("json", "account_objects", to_string(params));
252 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
253
254 params[jss::marker] = std::string(&mark[1U], 65);
255 resp = env.rpc("json", "account_objects", to_string(params));
256 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
257
258 params[jss::marker] = std::string(&mark[1U], 65) + "not hex";
259 resp = env.rpc("json", "account_objects", to_string(params));
260 BEAST_EXPECT(resp[jss::result][jss::error_message] == "Invalid field 'marker'.");
261
262 // Should this be an error?
263 // A hex digit is absent from the end of marker.
264 // No account objects returned.
265 params[jss::marker] = std::string(&mark[1U], 128);
266 resp = env.rpc("json", "account_objects", to_string(params));
267 BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
268 }
269 }
270
271 void
273 {
274 testcase("unsteppedThenStepped");
275
276 using namespace jtx;
277 Env env(*this);
278
279 Account const gw1{"G1"};
280 Account const gw2{"G2"};
281 Account const bob{"bob"};
282
283 auto const usD1 = gw1["USD"];
284 auto const usD2 = gw2["USD"];
285
286 env.fund(XRP(1000), gw1, gw2, bob);
287 env.trust(usD1(1000), bob);
288 env.trust(usD2(1000), bob);
289
290 env(pay(gw1, bob, usD1(1000)));
291 env(pay(gw2, bob, usD2(1000)));
292
293 env(offer(bob, XRP(100), bob["USD"](1)), Txflags(tfPassive));
294 env(offer(bob, XRP(100), usD1(1)), Txflags(tfPassive));
295
296 json::Value bobj[4];
297 for (int i = 0; i < 4; ++i)
299
300 // test 'unstepped'
301 // i.e. request account objects without explicit limit/marker paging
302 {
303 json::Value params;
304 params[jss::account] = bob.human();
305 auto resp = env.rpc("json", "account_objects", to_string(params));
306 BEAST_EXPECT(!resp.isMember(jss::marker));
307
308 BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 4);
309 for (int i = 0; i < 4; ++i)
310 {
311 auto& aobj = resp[jss::result][jss::account_objects][i];
312 aobj.removeMember("PreviousTxnID");
313 aobj.removeMember("PreviousTxnLgrSeq");
314 BEAST_EXPECT(aobj == bobj[i]);
315 }
316 }
317 // test request with type parameter as filter, unstepped
318 {
319 json::Value params;
320 params[jss::account] = bob.human();
321 params[jss::type] = jss::state;
322 auto resp = env.rpc("json", "account_objects", to_string(params));
323 BEAST_EXPECT(!resp.isMember(jss::marker));
324
325 BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 2);
326 for (int i = 0; i < 2; ++i)
327 {
328 auto& aobj = resp[jss::result][jss::account_objects][i];
329 aobj.removeMember("PreviousTxnID");
330 aobj.removeMember("PreviousTxnLgrSeq");
331 BEAST_EXPECT(aobj == bobj[i + 1]);
332 }
333 }
334 // test stepped one-at-a-time with limit=1, resume from prev marker
335 {
336 json::Value params;
337 params[jss::account] = bob.human();
338 params[jss::limit] = 1;
339 for (int i = 0; i < 4; ++i)
340 {
341 auto resp = env.rpc("json", "account_objects", to_string(params));
342 auto& aobjs = resp[jss::result][jss::account_objects];
343 BEAST_EXPECT(aobjs.size() == 1);
344 auto& aobj = aobjs[0U];
345 if (i < 3)
346 {
347 BEAST_EXPECT(resp[jss::result][jss::limit] == 1);
348 }
349 else
350 {
351 BEAST_EXPECT(!resp[jss::result].isMember(jss::limit));
352 }
353
354 aobj.removeMember("PreviousTxnID");
355 aobj.removeMember("PreviousTxnLgrSeq");
356
357 BEAST_EXPECT(aobj == bobj[i]);
358
359 params[jss::marker] = resp[jss::result][jss::marker];
360 }
361 }
362 }
363
364 void
366 {
367 // The preceding test case, unsteppedThenStepped(), found a bug in the
368 // support for NFToken Pages. So we're leaving that test alone when
369 // adding tests to exercise NFTokenPages.
370 testcase("unsteppedThenSteppedWithNFTs");
371
372 using namespace jtx;
373 Env env(*this);
374
375 Account const gw1{"G1"};
376 Account const gw2{"G2"};
377 Account const bob{"bob"};
378
379 auto const usD1 = gw1["USD"];
380 auto const usD2 = gw2["USD"];
381
382 env.fund(XRP(1000), gw1, gw2, bob);
383 env.close();
384
385 // Check behavior if there are no account objects.
386 {
387 // Unpaged
388 json::Value params;
389 params[jss::account] = bob.human();
390 auto resp = env.rpc("json", "account_objects", to_string(params));
391 BEAST_EXPECT(!resp.isMember(jss::marker));
392 BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
393
394 // Limit == 1
395 params[jss::limit] = 1;
396 resp = env.rpc("json", "account_objects", to_string(params));
397 BEAST_EXPECT(!resp.isMember(jss::marker));
398 BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
399 }
400
401 // Check behavior if there are only NFTokens.
402 env(token::mint(bob, 0u), Txflags(tfTransferable));
403 env.close();
404
405 // test 'unstepped'
406 // i.e. request account objects without explicit limit/marker paging
407 json::Value unpaged;
408 {
409 json::Value params;
410 params[jss::account] = bob.human();
411 auto resp = env.rpc("json", "account_objects", to_string(params));
412 BEAST_EXPECT(!resp.isMember(jss::marker));
413
414 unpaged = resp[jss::result][jss::account_objects];
415 BEAST_EXPECT(unpaged.size() == 1);
416 }
417 // test request with type parameter as filter, unstepped
418 {
419 json::Value params;
420 params[jss::account] = bob.human();
421 params[jss::type] = jss::nft_page;
422 auto resp = env.rpc("json", "account_objects", to_string(params));
423 BEAST_EXPECT(!resp.isMember(jss::marker));
424 json::Value& aobjs = resp[jss::result][jss::account_objects];
425 BEAST_EXPECT(aobjs.size() == 1);
426 BEAST_EXPECT(aobjs[0u][sfLedgerEntryType.jsonName] == jss::NFTokenPage);
427 BEAST_EXPECT(aobjs[0u][sfNFTokens.jsonName].size() == 1);
428 }
429 // test stepped one-at-a-time with limit=1, resume from prev marker
430 {
431 json::Value params;
432 params[jss::account] = bob.human();
433 params[jss::limit] = 1;
434
435 json::Value resp = env.rpc("json", "account_objects", to_string(params));
436 json::Value& aobjs = resp[jss::result][jss::account_objects];
437 BEAST_EXPECT(aobjs.size() == 1);
438 auto& aobj = aobjs[0U];
439 BEAST_EXPECT(!resp[jss::result].isMember(jss::limit));
440 BEAST_EXPECT(!resp[jss::result].isMember(jss::marker));
441
442 BEAST_EXPECT(aobj == unpaged[0u]);
443 }
444
445 // Add more objects in addition to the NFToken Page.
446 env.trust(usD1(1000), bob);
447 env.trust(usD2(1000), bob);
448
449 env(pay(gw1, bob, usD1(1000)));
450 env(pay(gw2, bob, usD2(1000)));
451
452 env(offer(bob, XRP(100), bob["USD"](1)), Txflags(tfPassive));
453 env(offer(bob, XRP(100), usD1(1)), Txflags(tfPassive));
454 env.close();
455
456 // test 'unstepped'
457 {
458 json::Value params;
459 params[jss::account] = bob.human();
460 auto resp = env.rpc("json", "account_objects", to_string(params));
461 BEAST_EXPECT(!resp.isMember(jss::marker));
462
463 unpaged = resp[jss::result][jss::account_objects];
464 BEAST_EXPECT(unpaged.size() == 5);
465 }
466 // test request with type parameter as filter, unstepped
467 {
468 json::Value params;
469 params[jss::account] = bob.human();
470 params[jss::type] = jss::nft_page;
471 auto resp = env.rpc("json", "account_objects", to_string(params));
472 BEAST_EXPECT(!resp.isMember(jss::marker));
473 json::Value& aobjs = resp[jss::result][jss::account_objects];
474 BEAST_EXPECT(aobjs.size() == 1);
475 BEAST_EXPECT(aobjs[0u][sfLedgerEntryType.jsonName] == jss::NFTokenPage);
476 BEAST_EXPECT(aobjs[0u][sfNFTokens.jsonName].size() == 1);
477 }
478 // test stepped one-at-a-time with limit=1, resume from prev marker
479 {
480 json::Value params;
481 params[jss::account] = bob.human();
482 params[jss::limit] = 1;
483 for (int i = 0; i < 5; ++i)
484 {
485 json::Value resp = env.rpc("json", "account_objects", to_string(params));
486 json::Value& aobjs = resp[jss::result][jss::account_objects];
487 BEAST_EXPECT(aobjs.size() == 1);
488 auto& aobj = aobjs[0U];
489 if (i < 4)
490 {
491 BEAST_EXPECT(resp[jss::result][jss::limit] == 1);
492 BEAST_EXPECT(resp[jss::result].isMember(jss::marker));
493 }
494 else
495 {
496 BEAST_EXPECT(!resp[jss::result].isMember(jss::limit));
497 BEAST_EXPECT(!resp[jss::result].isMember(jss::marker));
498 }
499
500 BEAST_EXPECT(aobj == unpaged[i]);
501
502 params[jss::marker] = resp[jss::result][jss::marker];
503 }
504 }
505
506 // Make sure things still work if there is more than 1 NFT Page.
507 for (int i = 0; i < 32; ++i)
508 {
509 env(token::mint(bob, 0u), Txflags(tfTransferable));
510 env.close();
511 }
512 // test 'unstepped'
513 {
514 json::Value params;
515 params[jss::account] = bob.human();
516 auto resp = env.rpc("json", "account_objects", to_string(params));
517 BEAST_EXPECT(!resp.isMember(jss::marker));
518
519 unpaged = resp[jss::result][jss::account_objects];
520 BEAST_EXPECT(unpaged.size() == 6);
521 }
522 // test request with type parameter as filter, unstepped
523 {
524 json::Value params;
525 params[jss::account] = bob.human();
526 params[jss::type] = jss::nft_page;
527 auto resp = env.rpc("json", "account_objects", to_string(params));
528 BEAST_EXPECT(!resp.isMember(jss::marker));
529 json::Value const& aobjs = resp[jss::result][jss::account_objects];
530 BEAST_EXPECT(aobjs.size() == 2);
531 }
532 // test stepped one-at-a-time with limit=1, resume from prev marker
533 {
534 json::Value params;
535 params[jss::account] = bob.human();
536 params[jss::limit] = 1;
537 for (int i = 0; i < 6; ++i)
538 {
539 json::Value resp = env.rpc("json", "account_objects", to_string(params));
540 json::Value& aobjs = resp[jss::result][jss::account_objects];
541 BEAST_EXPECT(aobjs.size() == 1);
542 auto& aobj = aobjs[0U];
543 if (i < 5)
544 {
545 BEAST_EXPECT(resp[jss::result][jss::limit] == 1);
546 BEAST_EXPECT(resp[jss::result].isMember(jss::marker));
547 }
548 else
549 {
550 BEAST_EXPECT(!resp[jss::result].isMember(jss::limit));
551 BEAST_EXPECT(!resp[jss::result].isMember(jss::marker));
552 }
553
554 BEAST_EXPECT(aobj == unpaged[i]);
555
556 params[jss::marker] = resp[jss::result][jss::marker];
557 }
558 }
559 }
560
561 void
563 {
564 testcase("object types");
565
566 // Give gw a bunch of ledger objects and make sure we can retrieve
567 // them by type.
568 using namespace jtx;
569
570 Account const alice{"alice"};
571 Account const gw{"gateway"};
572 auto const usd = gw["USD"];
573
574 auto const features =
575 testableAmendments() | featureXChainBridge | featurePermissionedDomains;
576 Env env(*this, features);
577
578 // Make a lambda we can use to get "account_objects" easily.
579 auto acctObjs = [&env](
580 AccountID const& acct,
582 std::optional<std::uint16_t> limit = std::nullopt,
583 std::optional<std::string> marker = std::nullopt) {
584 json::Value params;
585 params[jss::account] = to_string(acct);
586 if (type)
587 params[jss::type] = *type;
588 if (limit)
589 params[jss::limit] = *limit;
590 if (marker)
591 params[jss::marker] = *marker;
592 params[jss::ledger_index] = "validated";
593 return env.rpc("json", "account_objects", to_string(params));
594 };
595
596 // Make a lambda that easily identifies the size of account objects.
597 auto acctObjsIsSize = [](json::Value const& resp, unsigned size) {
598 return resp[jss::result][jss::account_objects].isArray() &&
599 (resp[jss::result][jss::account_objects].size() == size);
600 };
601
602 // Make a lambda that checks if the response has error for invalid type
603 auto acctObjsTypeIsInvalid = [](json::Value const& resp) {
604 return resp[jss::result].isMember(jss::error) &&
605 resp[jss::result][jss::error_message] == "Invalid field \'type\'.";
606 };
607
608 env.fund(XRP(10000), gw, alice);
609 env.close();
610
611 // Since the account is empty now, all account objects should come
612 // back empty.
613 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::account), 0));
614 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::check), 0));
615 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::deposit_preauth), 0));
616 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::escrow), 0));
617 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::nft_page), 0));
618 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::offer), 0));
619 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::payment_channel), 0));
620 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::signer_list), 0));
621 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::state), 0));
622 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::ticket), 0));
623 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::amm), 0));
624 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::did), 0));
625 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::permissioned_domain), 0));
626 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::sponsorship), 0));
627
628 // we expect invalid field type reported for the following types
629 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::amendments)));
630 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::directory)));
631 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::fee)));
632 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::hashes)));
633 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::NegativeUNL)));
634
635 // gw mints an NFT so we can find it.
636 UInt256 const nftID{token::getNextID(env, gw, 0u, tfTransferable)};
637 env(token::mint(gw, 0u), Txflags(tfTransferable));
638 env.close();
639 {
640 // Find the NFToken page and make sure it's the right one.
641 json::Value const resp = acctObjs(gw, jss::nft_page);
642 BEAST_EXPECT(acctObjsIsSize(resp, 1));
643
644 auto const& nftPage = resp[jss::result][jss::account_objects][0u];
645 BEAST_EXPECT(nftPage[sfNFTokens.jsonName].size() == 1);
646 BEAST_EXPECT(
647 nftPage[sfNFTokens.jsonName][0u][sfNFToken.jsonName][sfNFTokenID.jsonName] ==
648 to_string(nftID));
649 }
650
651 // Set up a trust line so we can find it.
652 env.trust(usd(1000), alice);
653 env.close();
654 env(pay(gw, alice, usd(5)));
655 env.close();
656 {
657 // Find the trustline and make sure it's the right one.
658 json::Value const resp = acctObjs(gw, jss::state);
659 BEAST_EXPECT(acctObjsIsSize(resp, 1));
660
661 auto const& state = resp[jss::result][jss::account_objects][0u];
662 BEAST_EXPECT(state[sfBalance.jsonName][jss::value].asInt() == -5);
663 BEAST_EXPECT(state[sfHighLimit.jsonName][jss::value].asUInt() == 1000);
664 }
665 // gw writes a check for USD(10) to alice.
666 env(check::create(gw, alice, usd(10)));
667 env.close();
668 {
669 // Find the check.
670 json::Value const resp = acctObjs(gw, jss::check);
671 BEAST_EXPECT(acctObjsIsSize(resp, 1));
672
673 auto const& check = resp[jss::result][jss::account_objects][0u];
674 BEAST_EXPECT(check[sfAccount.jsonName] == gw.human());
675 BEAST_EXPECT(check[sfDestination.jsonName] == alice.human());
676 BEAST_EXPECT(check[sfSendMax.jsonName][jss::value].asUInt() == 10);
677 }
678 // gw preauthorizes payments from alice.
679 env(deposit::auth(gw, alice));
680 env.close();
681 {
682 // Find the pre-authorization.
683 json::Value const resp = acctObjs(gw, jss::deposit_preauth);
684 BEAST_EXPECT(acctObjsIsSize(resp, 1));
685
686 auto const& preauth = resp[jss::result][jss::account_objects][0u];
687 BEAST_EXPECT(preauth[sfAccount.jsonName] == gw.human());
688 BEAST_EXPECT(preauth[sfAuthorize.jsonName] == alice.human());
689 }
690 {
691 // gw creates an escrow that we can look for in the ledger.
692 json::Value jvEscrow;
693 jvEscrow[jss::TransactionType] = jss::EscrowCreate;
694 jvEscrow[jss::Account] = gw.human();
695 jvEscrow[jss::Destination] = gw.human();
696 jvEscrow[jss::Amount] = XRP(100).value().getJson(JsonOptions::Values::None);
697 jvEscrow[sfFinishAfter.jsonName] = env.now().time_since_epoch().count() + 1;
698 env(jvEscrow);
699 env.close();
700 }
701 {
702 // Find the escrow.
703 json::Value const resp = acctObjs(gw, jss::escrow);
704 BEAST_EXPECT(acctObjsIsSize(resp, 1));
705
706 auto const& escrow = resp[jss::result][jss::account_objects][0u];
707 BEAST_EXPECT(escrow[sfAccount.jsonName] == gw.human());
708 BEAST_EXPECT(escrow[sfDestination.jsonName] == gw.human());
709 BEAST_EXPECT(escrow[sfAmount.jsonName].asUInt() == 100'000'000);
710 }
711
712 {
713 std::string const credentialType1 = "credential1";
714 Account const issuer("issuer");
715 env.fund(XRP(5000), issuer);
716
717 // gw creates an PermissionedDomain.
718 env(pdomain::setTx(gw, {{.issuer = issuer, .credType = credentialType1}}));
719 env.close();
720
721 // Find the PermissionedDomain.
722 json::Value const resp = acctObjs(gw, jss::permissioned_domain);
723 BEAST_EXPECT(acctObjsIsSize(resp, 1));
724
725 auto const& permissionedDomain = resp[jss::result][jss::account_objects][0u];
726 BEAST_EXPECT(
727 permissionedDomain.isMember(jss::Owner) &&
728 (permissionedDomain[jss::Owner] == gw.human()));
729 bool const check1 = BEAST_EXPECT(
730 permissionedDomain.isMember(jss::AcceptedCredentials) &&
731 permissionedDomain[jss::AcceptedCredentials].isArray() &&
732 (permissionedDomain[jss::AcceptedCredentials].size() == 1) &&
733 (permissionedDomain[jss::AcceptedCredentials][0u].isMember(jss::Credential)));
734
735 if (check1)
736 {
737 auto const& credential =
738 permissionedDomain[jss::AcceptedCredentials][0u][jss::Credential];
739 BEAST_EXPECT(
740 credential.isMember(sfIssuer.jsonName) &&
741 (credential[sfIssuer.jsonName] == issuer.human()));
742 BEAST_EXPECT(
743 credential.isMember(sfCredentialType.jsonName) &&
744 (credential[sfCredentialType.jsonName] == strHex(credentialType1)));
745 }
746 }
747
748 {
749 // Create a bridge
751 Env scEnv(*this, envconfig(), features);
752 x.createScBridgeObjects(scEnv);
753
754 auto scEnvAcctObjs = [&](Account const& acct, char const* type) {
755 json::Value params;
756 params[jss::account] = acct.human();
757 params[jss::type] = type;
758 params[jss::ledger_index] = "validated";
759 return scEnv.rpc("json", "account_objects", to_string(params));
760 };
761
762 json::Value const resp = scEnvAcctObjs(Account::kMaster, jss::bridge);
763
764 BEAST_EXPECT(acctObjsIsSize(resp, 1));
765 auto const& acctBridge = resp[jss::result][jss::account_objects][0u];
766 BEAST_EXPECT(acctBridge[sfAccount.jsonName] == Account::kMaster.human());
767 BEAST_EXPECT(acctBridge[sfLedgerEntryType.getJsonName()] == "Bridge");
768 BEAST_EXPECT(acctBridge[sfXChainClaimID.getJsonName()].asUInt() == 0);
769 BEAST_EXPECT(acctBridge[sfXChainAccountClaimCount.getJsonName()].asUInt() == 0);
770 BEAST_EXPECT(acctBridge[sfXChainAccountCreateCount.getJsonName()].asUInt() == 0);
771 BEAST_EXPECT(acctBridge[sfMinAccountCreateAmount.getJsonName()].asUInt() == 20000000);
772 BEAST_EXPECT(acctBridge[sfSignatureReward.getJsonName()].asUInt() == 1000000);
773 BEAST_EXPECT(acctBridge[sfXChainBridge.getJsonName()] == x.jvb);
774 }
775 {
776 // Alice and Bob create a xchain sequence number that we can look
777 // for in the ledger.
779 Env scEnv(*this, envconfig(), features);
780 x.createScBridgeObjects(scEnv);
781
782 scEnv(xchainCreateClaimId(x.scAlice, x.jvb, x.reward, x.mcAlice));
783 scEnv.close();
784 scEnv(xchainCreateClaimId(x.scBob, x.jvb, x.reward, x.mcBob));
785 scEnv.close();
786
787 auto scEnvAcctObjs = [&](Account const& acct, char const* type) {
788 json::Value params;
789 params[jss::account] = acct.human();
790 params[jss::type] = type;
791 params[jss::ledger_index] = "validated";
792 return scEnv.rpc("json", "account_objects", to_string(params));
793 };
794
795 {
796 // Find the xchain sequence number for Andrea.
797 json::Value const resp = scEnvAcctObjs(x.scAlice, jss::xchain_owned_claim_id);
798 BEAST_EXPECT(acctObjsIsSize(resp, 1));
799
800 auto const& xchainSeq = resp[jss::result][jss::account_objects][0u];
801 BEAST_EXPECT(xchainSeq[sfAccount.jsonName] == x.scAlice.human());
802 BEAST_EXPECT(xchainSeq[sfXChainClaimID.getJsonName()].asUInt() == 1);
803 }
804 {
805 // and the one for Bob
806 json::Value const resp = scEnvAcctObjs(x.scBob, jss::xchain_owned_claim_id);
807 BEAST_EXPECT(acctObjsIsSize(resp, 1));
808
809 auto const& xchainSeq = resp[jss::result][jss::account_objects][0u];
810 BEAST_EXPECT(xchainSeq[sfAccount.jsonName] == x.scBob.human());
811 BEAST_EXPECT(xchainSeq[sfXChainClaimID.getJsonName()].asUInt() == 2);
812 }
813 }
814 {
816 Env scEnv(*this, envconfig(), features);
817 x.createScBridgeObjects(scEnv);
818 auto const amt = XRP(1000);
819
820 // send first batch of account create attestations, so the
821 // xchain_create_account_claim_id_ should be present on the door
822 // account (Account::kMaster) to collect the signatures until a
823 // quorum is reached
824 scEnv(
826 x.scAttester,
827 x.jvb,
828 x.mcCarol,
829 amt,
830 x.reward,
831 x.payees[0],
832 true,
833 1,
834 x.scuAlice,
835 x.signers[0]));
836 scEnv.close();
837
838 auto scEnvAcctObjs = [&](Account const& acct, char const* type) {
839 json::Value params;
840 params[jss::account] = acct.human();
841 params[jss::type] = type;
842 params[jss::ledger_index] = "validated";
843 return scEnv.rpc("json", "account_objects", to_string(params));
844 };
845
846 {
847 // Find the xchain_create_account_claim_id_
848 json::Value const resp =
849 scEnvAcctObjs(Account::kMaster, jss::xchain_owned_create_account_claim_id);
850 BEAST_EXPECT(acctObjsIsSize(resp, 1));
851
852 auto const& xchainCreateAccountClaimId =
853 resp[jss::result][jss::account_objects][0u];
854 BEAST_EXPECT(
855 xchainCreateAccountClaimId[sfAccount.jsonName] == Account::kMaster.human());
856 BEAST_EXPECT(
857 xchainCreateAccountClaimId[sfXChainAccountCreateCount.getJsonName()].asUInt() ==
858 1);
859 }
860 }
861
862 // gw creates an offer that we can look for in the ledger.
863 env(offer(gw, usd(7), XRP(14)));
864 env.close();
865 {
866 // Find the offer.
867 json::Value const resp = acctObjs(gw, jss::offer);
868 BEAST_EXPECT(acctObjsIsSize(resp, 1));
869
870 auto const& offer = resp[jss::result][jss::account_objects][0u];
871 BEAST_EXPECT(offer[sfAccount.jsonName] == gw.human());
872 BEAST_EXPECT(offer[sfTakerGets.jsonName].asUInt() == 14'000'000);
873 BEAST_EXPECT(offer[sfTakerPays.jsonName][jss::value].asUInt() == 7);
874 }
875 {
876 // Create a payment channel from qw to alice that we can look
877 // for.
878 json::Value jvPayChan;
879 jvPayChan[jss::TransactionType] = jss::PaymentChannelCreate;
880 jvPayChan[jss::Account] = gw.human();
881 jvPayChan[jss::Destination] = alice.human();
882 jvPayChan[jss::Amount] = XRP(300).value().getJson(JsonOptions::Values::None);
883 jvPayChan[sfSettleDelay.jsonName] = 24 * 60 * 60;
884 jvPayChan[sfPublicKey.jsonName] = strHex(gw.pk().slice());
885 env(jvPayChan);
886 env.close();
887 }
888 {
889 // Find the payment channel.
890 json::Value const resp = acctObjs(gw, jss::payment_channel);
891 BEAST_EXPECT(acctObjsIsSize(resp, 1));
892
893 auto const& payChan = resp[jss::result][jss::account_objects][0u];
894 BEAST_EXPECT(payChan[sfAccount.jsonName] == gw.human());
895 BEAST_EXPECT(payChan[sfAmount.jsonName].asUInt() == 300'000'000);
896 BEAST_EXPECT(payChan[sfSettleDelay.jsonName].asUInt() == 24 * 60 * 60);
897 }
898
899 {
900 // gw creates a DID that we can look for in the ledger.
901 json::Value jvDID;
902 jvDID[jss::TransactionType] = jss::DIDSet;
903 jvDID[jss::Account] = gw.human();
904 jvDID[sfURI.jsonName] = strHex(std::string{"uri"});
905 env(jvDID);
906 env.close();
907 }
908 {
909 // Find the DID.
910 json::Value const resp = acctObjs(gw, jss::did);
911 BEAST_EXPECT(acctObjsIsSize(resp, 1));
912
913 auto const& did = resp[jss::result][jss::account_objects][0u];
914 BEAST_EXPECT(did[sfAccount.jsonName] == gw.human());
915 BEAST_EXPECT(did[sfURI.jsonName] == strHex(std::string{"uri"}));
916 }
917 // Make gw multisigning by adding a signerList.
918 env(jtx::signers(gw, 6, {{alice, 7}}));
919 env.close();
920 {
921 // Find the signer list.
922 json::Value const resp = acctObjs(gw, jss::signer_list);
923 BEAST_EXPECT(acctObjsIsSize(resp, 1));
924
925 auto const& signerList = resp[jss::result][jss::account_objects][0u];
926 BEAST_EXPECT(signerList[sfSignerQuorum.jsonName] == 6);
927 auto const& entry = signerList[sfSignerEntries.jsonName][0u][sfSignerEntry.jsonName];
928 BEAST_EXPECT(entry[sfAccount.jsonName] == alice.human());
929 BEAST_EXPECT(entry[sfSignerWeight.jsonName].asUInt() == 7);
930 }
931
932 {
933 auto const seq = env.seq(gw);
934 // Create a Ticket for gw.
935 env(ticket::create(gw, 1));
936 env.close();
937
938 // Find the ticket.
939 json::Value const resp = acctObjs(gw, jss::ticket);
940 BEAST_EXPECT(acctObjsIsSize(resp, 1));
941
942 auto const& ticket = resp[jss::result][jss::account_objects][0u];
943 BEAST_EXPECT(ticket[sfAccount.jsonName] == gw.human());
944 BEAST_EXPECT(ticket[sfLedgerEntryType.jsonName] == jss::Ticket);
945 BEAST_EXPECT(ticket[sfTicketSequence.jsonName].asUInt() == seq + 1);
946 }
947
948 {
949 // Create a sponsorship
950 env(sponsor::set(alice, tfSponsorshipSetRequireSignForFee, 200, XRP(100), drops(10)),
952 env.close();
953
954 // Find the sponsorship.
955 for (auto const& acct : {alice, gw})
956 {
957 json::Value const resp = acctObjs(acct, jss::sponsorship);
958 BEAST_EXPECT(acctObjsIsSize(resp, 1));
959
960 auto const& sponsorship = resp[jss::result][jss::account_objects][0u];
961
962 BEAST_EXPECT(sponsorship[sfOwner.jsonName] == alice.human());
963 BEAST_EXPECT(sponsorship[sfSponsee.jsonName] == gw.human());
964 BEAST_EXPECT(
965 sponsorship[sfFlags.jsonName].asUInt() == tfSponsorshipSetRequireSignForFee);
966 BEAST_EXPECT(sponsorship[sfRemainingOwnerCount.jsonName].asUInt() == 200);
967 BEAST_EXPECT(sponsorship[sfFeeAmount.jsonName].asUInt() == 100000000);
968 BEAST_EXPECT(sponsorship[sfMaxFee.jsonName].asUInt() == 10);
969 }
970 }
971
972 {
973 // See how "deletion_blockers_only" handles gw's directory.
974 json::Value params;
975 params[jss::account] = gw.human();
976 params[jss::deletion_blockers_only] = true;
977 auto resp = env.rpc("json", "account_objects", to_string(params));
978
979 std::vector<std::string> const expectedLedgerTypes = [] {
981 jss::Escrow.cStr(),
982 jss::Check.cStr(),
983 jss::NFTokenPage.cStr(),
984 jss::RippleState.cStr(),
985 jss::PayChannel.cStr(),
986 jss::PermissionedDomain.cStr(),
987 jss::Sponsorship.cStr()};
989 return v;
990 }();
991
992 std::uint32_t const expectedAccountObjects{
993 static_cast<std::uint32_t>(std::size(expectedLedgerTypes))};
994
995 if (BEAST_EXPECT(acctObjsIsSize(resp, expectedAccountObjects)))
996 {
997 auto const& aobjs = resp[jss::result][jss::account_objects];
998 std::vector<std::string> gotLedgerTypes;
999 gotLedgerTypes.reserve(expectedAccountObjects);
1000 for (std::uint32_t i = 0; i < expectedAccountObjects; ++i)
1001 {
1002 gotLedgerTypes.push_back(aobjs[i]["LedgerEntryType"].asString());
1003 }
1004 std::ranges::sort(gotLedgerTypes);
1005 BEAST_EXPECT(gotLedgerTypes == expectedLedgerTypes);
1006 }
1007 }
1008 {
1009 // See how "deletion_blockers_only" with `type` handles gw's
1010 // directory.
1011 json::Value params;
1012 params[jss::account] = gw.human();
1013 params[jss::deletion_blockers_only] = true;
1014 params[jss::type] = jss::escrow;
1015 auto resp = env.rpc("json", "account_objects", to_string(params));
1016
1017 if (BEAST_EXPECT(acctObjsIsSize(resp, 1u)))
1018 {
1019 auto const& aobjs = resp[jss::result][jss::account_objects];
1020 BEAST_EXPECT(aobjs[0u]["LedgerEntryType"] == jss::Escrow);
1021 }
1022 }
1023 {
1024 // Make a lambda to get the types
1025 auto getTypes = [&](json::Value const& resp, std::vector<std::string>& typesOut) {
1026 auto const objs = resp[jss::result][jss::account_objects];
1027 for (auto const& obj : resp[jss::result][jss::account_objects])
1028 typesOut.push_back(obj[sfLedgerEntryType.fieldName].asString());
1029 std::ranges::sort(typesOut);
1030 };
1031 // Make a lambda we can use to check the number of fetched
1032 // account objects and their ledger type
1033 auto expectObjects = [&](json::Value const& resp,
1034 std::vector<std::string> const& types) -> bool {
1035 if (!acctObjsIsSize(resp, types.size()))
1036 return false;
1037 std::vector<std::string> typesOut;
1038 getTypes(resp, typesOut);
1039 return types == typesOut;
1040 };
1041 // Find AMM objects
1042 AMM amm(env, gw, XRP(1'000), usd(1'000));
1043 amm.deposit(alice, usd(1));
1044 // AMM account has 4 objects: AMM object and 3 trustlines
1045 auto const lines = getAccountLines(env, amm.ammAccount());
1046 BEAST_EXPECT(lines[jss::lines].size() == 3);
1047 // request AMM only, doesn't depend on the limit
1048 BEAST_EXPECT(acctObjsIsSize(acctObjs(amm.ammAccount(), jss::amm), 1));
1049 // request first two objects
1050 auto resp = acctObjs(amm.ammAccount(), std::nullopt, 2);
1051 std::vector<std::string> typesOut;
1052 getTypes(resp, typesOut);
1053 // request next two objects
1054 resp = acctObjs(
1055 amm.ammAccount(), std::nullopt, 10, resp[jss::result][jss::marker].asString());
1056 getTypes(resp, typesOut);
1057 BEAST_EXPECT(
1058 (typesOut ==
1060 jss::AMM.cStr(),
1061 jss::RippleState.cStr(),
1062 jss::RippleState.cStr(),
1063 jss::RippleState.cStr()}));
1064 // filter by state: there are three trustlines
1065 resp = acctObjs(amm.ammAccount(), jss::state, 10);
1066 BEAST_EXPECT(expectObjects(
1067 resp, {jss::RippleState.cStr(), jss::RippleState.cStr(), jss::RippleState.cStr()}));
1068 // AMM account doesn't own offers
1069 BEAST_EXPECT(acctObjsIsSize(acctObjs(amm.ammAccount(), jss::offer), 0));
1070 // gw account doesn't own AMM object
1071 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::amm), 0));
1072 }
1073
1074 // we still expect invalid field type reported for the following types
1075 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::amendments)));
1076 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::directory)));
1077 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::fee)));
1078 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::hashes)));
1079 BEAST_EXPECT(acctObjsTypeIsInvalid(acctObjs(gw, jss::NegativeUNL)));
1080
1081 // Run up the number of directory entries so gw has two
1082 // directory nodes.
1083 for (int d = 1'000'032; d >= 1'000'000; --d)
1084 {
1085 env(offer(gw, usd(1), drops(d)));
1086 env.close();
1087 }
1088
1089 // Verify that the non-returning types still don't return anything.
1090 BEAST_EXPECT(acctObjsIsSize(acctObjs(gw, jss::account), 0));
1091 }
1092
1093 void
1095 {
1096 // there's some bug found in account_nfts method that it did not
1097 // return invalid params when providing unassociated nft marker.
1098 // this test tests both situations when providing valid nft marker
1099 // and unassociated nft marker.
1100 testcase("NFTsMarker");
1101
1102 using namespace jtx;
1103 Env env(*this);
1104
1105 Account const bob{"bob"};
1106 env.fund(XRP(10000), bob);
1107
1108 static constexpr unsigned kNftsSize = 10;
1109 for (unsigned i = 0; i < kNftsSize; i++)
1110 {
1111 env(token::mint(bob, 0));
1112 }
1113
1114 env.close();
1115
1116 // save the NFTokenIDs to use later
1117 std::vector<json::Value> tokenIDs;
1118 {
1119 json::Value params;
1120 params[jss::account] = bob.human();
1121 params[jss::ledger_index] = "validated";
1122 json::Value const resp = env.rpc("json", "account_nfts", to_string(params));
1123 json::Value const& nfts = resp[jss::result][jss::account_nfts];
1124 for (json::Value const& nft : nfts)
1125 tokenIDs.push_back(nft["NFTokenID"]);
1126 }
1127
1128 // this lambda function is used to check if the account_nfts method
1129 // returns the correct token information. lastIndex is used to query the
1130 // last marker.
1131 auto compareNFTs = [&tokenIDs, &env, &bob](unsigned const limit, unsigned const lastIndex) {
1132 json::Value params;
1133 params[jss::account] = bob.human();
1134 params[jss::limit] = limit;
1135 params[jss::marker] = tokenIDs[lastIndex];
1136 params[jss::ledger_index] = "validated";
1137 json::Value const resp = env.rpc("json", "account_nfts", to_string(params));
1138
1139 if (resp[jss::result].isMember(jss::error))
1140 return false;
1141
1142 json::Value const& nfts = resp[jss::result][jss::account_nfts];
1143 unsigned const nftsCount =
1144 tokenIDs.size() - lastIndex - 1 < limit ? tokenIDs.size() - lastIndex - 1 : limit;
1145
1146 if (nfts.size() != nftsCount)
1147 return false;
1148
1149 for (unsigned i = 0; i < nftsCount; i++)
1150 {
1151 if (nfts[i]["NFTokenID"] != tokenIDs[lastIndex + 1 + i])
1152 return false;
1153 }
1154
1155 return true;
1156 };
1157
1158 // test a valid marker which is equal to the third tokenID
1159 BEAST_EXPECT(compareNFTs(4, 2));
1160
1161 // test a valid marker which is equal to the 8th tokenID
1162 BEAST_EXPECT(compareNFTs(4, 7));
1163
1164 // lambda that holds common code for invalid cases.
1165 auto testInvalidMarker = [&env, &bob](auto marker, char const* errorMessage) {
1166 json::Value params;
1167 params[jss::account] = bob.human();
1168 params[jss::limit] = 4;
1169 params[jss::ledger_index] = jss::validated;
1170 params[jss::marker] = marker;
1171 json::Value const resp = env.rpc("json", "account_nfts", to_string(params));
1172 return resp[jss::result][jss::error_message] == errorMessage;
1173 };
1174
1175 // test an invalid marker that is not a string
1176 BEAST_EXPECT(testInvalidMarker(17, "Invalid field \'marker\', not string."));
1177
1178 // test an invalid marker that has a non-hex character
1179 BEAST_EXPECT(testInvalidMarker(
1180 "00000000F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B900000000000000000G",
1181 "Invalid field \'marker\'."));
1182
1183 // this lambda function is used to create some fake marker using given
1184 // taxon and sequence because we want to test some unassociated markers
1185 // later
1186 auto createFakeNFTMarker = [](AccountID const& issuer,
1187 std::uint32_t taxon,
1188 std::uint32_t tokenSeq,
1189 std::uint16_t flags = 0,
1190 std::uint16_t fee = 0) {
1191 // the marker has the exact same format as an NFTokenID
1192 return to_string(
1193 NFTokenMint::createNFTokenID(flags, fee, issuer, nft::toTaxon(taxon), tokenSeq));
1194 };
1195
1196 // test an unassociated marker which does not exist in the NFTokenIDs
1197 BEAST_EXPECT(testInvalidMarker(
1198 createFakeNFTMarker(bob.id(), 0x000000000, 0x00000000), "Invalid field \'marker\'."));
1199
1200 // test an unassociated marker which exceeds the maximum value of the
1201 // existing NFTokenID
1202 BEAST_EXPECT(testInvalidMarker(
1203 createFakeNFTMarker(bob.id(), 0xFFFFFFFF, 0xFFFFFFFF), "Invalid field \'marker\'."));
1204 }
1205
1206 void
1208 {
1209 testcase("account_nfts");
1210
1211 using namespace jtx;
1212 Env env(*this);
1213
1214 // test validation
1215 {
1216 auto testInvalidAccountParam = [&](auto const& param) {
1217 json::Value params;
1218 params[jss::account] = param;
1219 auto jrr = env.rpc("json", "account_nfts", to_string(params))[jss::result];
1220 BEAST_EXPECT(jrr[jss::error] == "invalidParams");
1221 BEAST_EXPECT(jrr[jss::error_message] == "Invalid field 'account'.");
1222 };
1223
1224 testInvalidAccountParam(1);
1225 testInvalidAccountParam(1.1);
1226 testInvalidAccountParam(true);
1227 testInvalidAccountParam(json::Value(json::ValueType::Null));
1228 testInvalidAccountParam(json::Value(json::ValueType::Object));
1229 testInvalidAccountParam(json::Value(json::ValueType::Array));
1230 }
1231 }
1232
1233 void
1235 {
1236 testcase("AccountObjectMarker");
1237
1238 using namespace jtx;
1239 Env env(*this);
1240
1241 Account const alice{"alice"};
1242 Account const bob{"bob"};
1243 Account const carol{"carol"};
1244 env.fund(XRP(10000), alice, bob, carol);
1245
1246 unsigned const accountObjectSize = 30;
1247 for (unsigned i = 0; i < accountObjectSize; i++)
1248 env(check::create(alice, bob, XRP(10)));
1249
1250 for (unsigned i = 0; i < 10; i++)
1251 env(token::mint(carol, 0));
1252
1253 env.close();
1254
1255 unsigned const limit = 11;
1256 json::Value marker;
1257
1258 // test account_objects with a limit and update marker
1259 {
1260 json::Value params;
1261 params[jss::account] = bob.human();
1262 params[jss::limit] = limit;
1263 params[jss::ledger_index] = "validated";
1264 auto resp = env.rpc("json", "account_objects", to_string(params));
1265 auto& accountObjects = resp[jss::result][jss::account_objects];
1266 marker = resp[jss::result][jss::marker];
1267 BEAST_EXPECT(!resp[jss::result].isMember(jss::error));
1268 BEAST_EXPECT(accountObjects.size() == limit);
1269 }
1270
1271 // test account_objects with valid marker and update marker
1272 {
1273 json::Value params;
1274 params[jss::account] = bob.human();
1275 params[jss::limit] = limit;
1276 params[jss::marker] = marker;
1277 params[jss::ledger_index] = "validated";
1278 auto resp = env.rpc("json", "account_objects", to_string(params));
1279 auto& accountObjects = resp[jss::result][jss::account_objects];
1280 marker = resp[jss::result][jss::marker];
1281 BEAST_EXPECT(!resp[jss::result].isMember(jss::error));
1282 BEAST_EXPECT(accountObjects.size() == limit);
1283 }
1284
1285 // this lambda function is used to check invalid marker response.
1286 auto testInvalidMarker = [&](std::string& marker) {
1287 json::Value params;
1288 params[jss::account] = bob.human();
1289 params[jss::limit] = limit;
1290 params[jss::ledger_index] = jss::validated;
1291 params[jss::marker] = marker;
1292 json::Value const resp = env.rpc("json", "account_objects", to_string(params));
1293 return resp[jss::result][jss::error_message] == "Invalid field \'marker\'.";
1294 };
1295
1296 auto const markerStr = marker.asString();
1297 auto const& idx = markerStr.find(',');
1298 auto const dirIndex = markerStr.substr(0, idx);
1299 auto const entryIndex = markerStr.substr(idx + 1);
1300
1301 // test account_objects with an invalid marker that contains no ','
1302 {
1303 std::string s = dirIndex + entryIndex;
1304 BEAST_EXPECT(testInvalidMarker(s));
1305 }
1306
1307 // test invalid marker by adding invalid string after the maker:
1308 // "dirIndex,entryIndex,1234"
1309 {
1310 std::string s = markerStr + ",1234";
1311 BEAST_EXPECT(testInvalidMarker(s));
1312 }
1313
1314 // test account_objects with an invalid marker containing invalid
1315 // dirIndex by replacing some characters from the dirIndex.
1316 {
1317 std::string s = markerStr;
1318 s.replace(0, 7, "FFFFFFF");
1319 BEAST_EXPECT(testInvalidMarker(s));
1320 }
1321
1322 // test account_objects with an invalid marker containing invalid
1323 // entryIndex by replacing some characters from the entryIndex.
1324 {
1325 std::string s = entryIndex;
1326 s.replace(0, 7, "FFFFFFF");
1327 s = dirIndex + ',' + s;
1328 BEAST_EXPECT(testInvalidMarker(s));
1329 }
1330
1331 // test account_objects with an invalid marker containing invalid
1332 // dirIndex with marker: ",entryIndex"
1333 {
1334 std::string s = ',' + entryIndex;
1335 BEAST_EXPECT(testInvalidMarker(s));
1336 }
1337
1338 // test account_objects with marker: "0,entryIndex", this is still
1339 // valid, because when dirIndex = 0, we will use root key to find
1340 // dir.
1341 {
1342 std::string const s = "0," + entryIndex;
1343 json::Value params;
1344 params[jss::account] = bob.human();
1345 params[jss::limit] = limit;
1346 params[jss::marker] = s;
1347 params[jss::ledger_index] = "validated";
1348 auto resp = env.rpc("json", "account_objects", to_string(params));
1349 auto& accountObjects = resp[jss::result][jss::account_objects];
1350 BEAST_EXPECT(!resp[jss::result].isMember(jss::error));
1351 BEAST_EXPECT(accountObjects.size() == limit);
1352 }
1353
1354 // test account_objects with an invalid marker containing invalid
1355 // entryIndex with marker: "dirIndex,"
1356 {
1357 std::string s = dirIndex + ',';
1358 BEAST_EXPECT(testInvalidMarker(s));
1359 }
1360
1361 // test account_objects with an invalid marker containing invalid
1362 // entryIndex with marker: "dirIndex,0"
1363 {
1364 std::string s = dirIndex + ",0";
1365 BEAST_EXPECT(testInvalidMarker(s));
1366 }
1367
1368 // continue getting account_objects with valid marker. This will be the
1369 // last page, so response will not contain any marker.
1370 {
1371 json::Value params;
1372 params[jss::account] = bob.human();
1373 params[jss::limit] = limit;
1374 params[jss::marker] = marker;
1375 params[jss::ledger_index] = "validated";
1376 auto resp = env.rpc("json", "account_objects", to_string(params));
1377 auto& accountObjects = resp[jss::result][jss::account_objects];
1378 BEAST_EXPECT(!resp[jss::result].isMember(jss::error));
1379 BEAST_EXPECT(accountObjects.size() == accountObjectSize - (limit * 2));
1380 BEAST_EXPECT(!resp[jss::result].isMember(jss::marker));
1381 }
1382
1383 // test account_objects when the account only have nft pages, but
1384 // provided invalid entry index.
1385 {
1386 json::Value params;
1387 params[jss::account] = carol.human();
1388 params[jss::limit] = 10;
1389 params[jss::marker] = "0," + entryIndex;
1390 params[jss::ledger_index] = "validated";
1391 auto resp = env.rpc("json", "account_objects", to_string(params));
1392 auto& accountObjects = resp[jss::result][jss::account_objects];
1393 BEAST_EXPECT(accountObjects.size() == 0);
1394 }
1395 }
1396
1397 void
1399 {
1400 testcase("SponsoredFilter");
1401 using namespace jtx;
1402
1403 Env env(*this, testableAmendments());
1404 Account const alice("alice");
1405 Account const bob("bob");
1406 Account const sponsor1("sponsor1");
1407 Account const gw("gw");
1408 auto const usd = gw["USD"];
1409
1410 env.fund(XRP(10000), alice, bob, sponsor1, gw);
1411 env.close();
1412
1413 // Helper to call account_objects with sponsored filter
1414 auto acctObjsSponsored = [](Env& testEnv,
1415 AccountID const& acct,
1416 bool sponsored,
1417 std::optional<json::StaticString> const& type = std::nullopt) {
1418 json::Value params;
1419 params[jss::account] = to_string(acct);
1420 params[jss::sponsored] = sponsored;
1421 if (type)
1422 params[jss::type] = *type;
1423 params[jss::ledger_index] = "validated";
1424 return testEnv.rpc("json", "account_objects", to_string(params));
1425 };
1426
1427 // Create a trust line for bob (not sponsored)
1428 env(trust(bob, usd(1000)));
1429 env.close();
1430
1431 // sponsored=true should not find any objects for bob (doesn't have any sponsored objects)
1432 {
1433 auto const resp = acctObjsSponsored(env, bob.id(), true);
1434 auto const& objs = resp[jss::result][jss::account_objects];
1435 BEAST_EXPECT(objs.size() == 0);
1436 }
1437
1438 // Now sponsor bob's trust line
1439 auto const trustId = keylet::trustLine(bob, gw, usd.currency);
1440 if (!BEAST_EXPECT(env.le(trustId)))
1441 return;
1442
1443 env(sponsor::transfer(bob, tfSponsorshipCreate, trustId.key),
1444 sponsor::As(sponsor1, spfSponsorReserve),
1445 Sig(sfSponsorSignature, sponsor1));
1446 env.close();
1447
1448 // Verify trust line has sponsor field
1449 {
1450 auto const sle = env.le(trustId);
1451 if (!BEAST_EXPECT(sle))
1452 return;
1453 BEAST_EXPECT(sle->isFieldPresent(sfHighSponsor) || sle->isFieldPresent(sfLowSponsor));
1454 }
1455
1456 // sponsored=true on bob should include the sponsored trust line
1457 {
1458 auto const resp = acctObjsSponsored(env, bob.id(), true);
1459 auto const& objs = resp[jss::result][jss::account_objects];
1460 if (!BEAST_EXPECT(objs.size() == 1))
1461 return;
1462
1463 auto const& obj = objs[0u];
1464 BEAST_EXPECT(obj[sfLedgerEntryType.jsonName] == jss::RippleState);
1465 BEAST_EXPECT(
1466 obj.isMember(sfHighSponsor.jsonName) || obj.isMember(sfLowSponsor.jsonName));
1467 }
1468
1469 // sponsored=false on bob should NOT include the sponsored trust line
1470 {
1471 auto const resp = acctObjsSponsored(env, bob.id(), false);
1472 BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
1473 }
1474
1475 // A trust line sponsored on either side is classified as sponsored
1476 // for both parties.
1477 {
1478 Env env(*this, testableAmendments());
1479 Account const issuer("issuer");
1480 Account const user("user");
1481 Account const sponsor("sponsor");
1482 auto const usd = issuer["USD"];
1483
1484 env.fund(XRP(10000), issuer, user, sponsor);
1485 env.close();
1486
1487 env(trust(issuer, user["USD"](100)));
1488 env.close();
1489
1490 env(trust(user, usd(100)));
1491 env.close();
1492
1493 auto const trustId = keylet::trustLine(user, issuer, usd.currency);
1494 if (!BEAST_EXPECT(env.le(trustId)))
1495 return;
1496
1497 env(sponsor::transfer(user, tfSponsorshipCreate, trustId.key),
1499 Sig(sfSponsorSignature, sponsor));
1500 env.close();
1501
1502 auto const line = env.le(trustId);
1503 if (!BEAST_EXPECT(line))
1504 return;
1505
1506 auto const userIsHigh = line->getFieldAmount(sfHighLimit).getIssuer() == user.id();
1507 auto const& userSponsorField = userIsHigh ? sfHighSponsor : sfLowSponsor;
1508 auto const& issuerSponsorField = userIsHigh ? sfLowSponsor : sfHighSponsor;
1509
1510 BEAST_EXPECT(line->isFieldPresent(userSponsorField));
1511 BEAST_EXPECT(!line->isFieldPresent(issuerSponsorField));
1512
1513 {
1514 auto const resp = acctObjsSponsored(env, user.id(), true, jss::state);
1515 auto const& objs = resp[jss::result][jss::account_objects];
1516 if (BEAST_EXPECT(objs.size() == 1))
1517 BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::RippleState);
1518 }
1519 {
1520 auto const resp = acctObjsSponsored(env, user.id(), false, jss::state);
1521 auto const& objs = resp[jss::result][jss::account_objects];
1522 BEAST_EXPECT(objs.size() == 0);
1523 }
1524 {
1525 auto const resp = acctObjsSponsored(env, issuer.id(), true, jss::state);
1526 auto const& objs = resp[jss::result][jss::account_objects];
1527 if (BEAST_EXPECT(objs.size() == 1))
1528 BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::RippleState);
1529 }
1530 {
1531 auto const resp = acctObjsSponsored(env, issuer.id(), false, jss::state);
1532 auto const& objs = resp[jss::result][jss::account_objects];
1533 BEAST_EXPECT(objs.size() == 0);
1534 }
1535 }
1536
1537 // A sponsored Check is classified as sponsored in both the writer's
1538 // and the destination's results.
1539 {
1540 Env env(*this, testableAmendments());
1541 Account const owner("owner");
1542 Account const dest("dest");
1543 Account const sponsor("sponsor");
1544
1545 env.fund(XRP(10000), owner, dest, sponsor);
1546 env.close();
1547
1548 auto const checkSeq = env.seq(owner);
1549 env(check::create(owner, dest, XRP(1)));
1550 env.close();
1551
1552 auto const checkId = keylet::check(owner, SeqProxy::rawSequence(checkSeq));
1553 if (!BEAST_EXPECT(env.le(checkId)))
1554 return;
1555
1556 env(sponsor::transfer(owner, tfSponsorshipCreate, checkId.key),
1558 Sig(sfSponsorSignature, sponsor));
1559 env.close();
1560
1561 {
1562 auto const sle = env.le(checkId);
1563 if (!BEAST_EXPECT(sle))
1564 return;
1565 BEAST_EXPECT(sle->isFieldPresent(sfSponsor));
1566 }
1567
1568 for (auto const& acct : {owner.id(), dest.id()})
1569 {
1570 {
1571 auto const resp = acctObjsSponsored(env, acct, true, jss::check);
1572 auto const& objs = resp[jss::result][jss::account_objects];
1573 if (BEAST_EXPECT(objs.size() == 1))
1574 BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::Check);
1575 }
1576 {
1577 auto const resp = acctObjsSponsored(env, acct, false, jss::check);
1578 BEAST_EXPECT(resp[jss::result][jss::account_objects].size() == 0);
1579 }
1580 }
1581 }
1582
1583 // A Sponsorship object is visible to both sides.
1584 {
1585 Env env(*this, testableAmendments());
1586 Account const owner("owner");
1587 Account const sponsee("sponsee");
1588
1589 env.fund(XRP(10000), owner, sponsee);
1590 env.close();
1591
1592 env(sponsor::set(owner, 0, 100, XRP(100)), sponsor::SponseeAcc(sponsee));
1593 env.close();
1594
1595 auto const sponsorshipKeylet = keylet::sponsorship(owner, sponsee);
1596 if (!BEAST_EXPECT(env.le(sponsorshipKeylet)))
1597 return;
1598
1599 {
1600 auto const resp = acctObjsSponsored(env, owner.id(), false, jss::sponsorship);
1601 auto const& objs = resp[jss::result][jss::account_objects];
1602 if (BEAST_EXPECT(objs.size() == 1))
1603 BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::Sponsorship);
1604 }
1605 {
1606 auto const resp = acctObjsSponsored(env, sponsee.id(), false, jss::sponsorship);
1607 auto const& objs = resp[jss::result][jss::account_objects];
1608 if (BEAST_EXPECT(objs.size() == 1))
1609 BEAST_EXPECT(objs[0u][sfLedgerEntryType.jsonName] == jss::Sponsorship);
1610 }
1611 {
1612 auto const resp = acctObjsSponsored(env, owner.id(), true, jss::sponsorship);
1613 auto const& objs = resp[jss::result][jss::account_objects];
1614 BEAST_EXPECT(objs.size() == 0);
1615 }
1616 {
1617 auto const resp = acctObjsSponsored(env, sponsee.id(), true, jss::sponsorship);
1618 auto const& objs = resp[jss::result][jss::account_objects];
1619 BEAST_EXPECT(objs.size() == 0);
1620 }
1621 }
1622 }
1623
1624 void
1626 {
1627 testcase("AccountObjectDoesntShowCancelledOffers");
1628
1629 using namespace jtx;
1630 Env env(*this);
1631
1632 Account const alice{"alice"};
1633 Account const bob{"bob"};
1634 auto const eur = bob["EUR"];
1635 env.fund(XRP(10000), alice, bob);
1636 env.close();
1637
1638 auto const rpcAccountObjects = [&](std::optional<uint32_t> limit = std::nullopt) {
1639 json::Value params;
1640 params[jss::account] = alice.human();
1641 if (limit.has_value())
1642 {
1643 params[jss::limit] = *limit;
1644 }
1645 return env.rpc("json", "account_objects", to_string(params));
1646 };
1647
1648 auto const numEntries = 33;
1650 seqs.reserve(numEntries);
1651 for ([[maybe_unused]] auto _ : std::ranges::iota_view{0, numEntries})
1652 {
1653 json::Value params;
1654 params[jss::secret] = toBase58(generateSeed("alice"));
1655 params[jss::tx_json] = offer(alice, eur(1), XRP(2));
1656 auto const res = env.rpc("json", "submit", to_string(params))[jss::result];
1657 BEAST_EXPECT(res[jss::engine_result].asString() == "tesSUCCESS");
1658 seqs.push_back(env.seq(alice));
1659 }
1660
1661 auto res = rpcAccountObjects();
1662 BEAST_EXPECT(res[jss::result][jss::account_objects].size() == numEntries);
1663 BEAST_EXPECT(not res[jss::result].isMember(jss::limit));
1664 BEAST_EXPECT(not res[jss::result].isMember(jss::marker));
1665
1666 for (auto const s : std::views::all(seqs) | std::views::take(numEntries - 1))
1667 {
1668 json::Value params;
1669 params[jss::secret] = toBase58(generateSeed("alice"));
1670 params[jss::tx_json] = offerCancel(alice, s - 1);
1671 auto const res = env.rpc("json", "submit", to_string(params))[jss::result];
1672 BEAST_EXPECT(res[jss::engine_result].asString() == "tesSUCCESS");
1673 }
1674
1675 res = rpcAccountObjects();
1676 BEAST_EXPECT(res[jss::result][jss::account_objects].size() == 1);
1677 BEAST_EXPECT(not res[jss::result].isMember(jss::limit));
1678 BEAST_EXPECT(not res[jss::result].isMember(jss::marker));
1679
1680 {
1681 json::Value params;
1682 params[jss::secret] = toBase58(generateSeed("alice"));
1683 json::Value txJson;
1684 txJson[jss::TransactionType] = jss::NFTokenMint;
1685 txJson[jss::Account] = to_string(alice.id());
1686 txJson["NFTokenTaxon"] = 1;
1687 params[jss::tx_json] = txJson;
1688 auto const res = env.rpc("json", "submit", to_string(params))[jss::result];
1689 BEAST_EXPECT(res[jss::engine_result].asString() == "tesSUCCESS");
1690 }
1691 env.close();
1692
1693 res = rpcAccountObjects();
1694 BEAST_EXPECT(res[jss::result][jss::account_objects].size() == 2);
1695 BEAST_EXPECT(not res[jss::result].isMember(jss::limit));
1696 BEAST_EXPECT(not res[jss::result].isMember(jss::marker));
1697
1698 res = rpcAccountObjects(1);
1699 BEAST_EXPECT(res[jss::result][jss::account_objects].size() == 1);
1700 BEAST_EXPECT(res[jss::result][jss::limit].asUInt() == 1);
1701 BEAST_EXPECT(res[jss::result].isMember(jss::marker));
1702 }
1703
1704 // A TransactionProposal blocks AccountDelete (it is not in
1705 // nonObligationDeleter) but was omitted from kDeletionBlockers, so
1706 // deletion_blockers_only reported a clean directory. Tickets on the
1707 // same account are auto-removed at delete time and must stay omitted.
1708 void
1710 {
1711 testcase("deletion_blockers_only includes TransactionProposal");
1712
1713 using namespace jtx;
1714 using namespace std::chrono_literals;
1715
1716 Env env{*this, testableAmendments()};
1717
1718 Account const alice{"alice"};
1719 Account const bob{"bob"};
1720 env.fund(XRP(10000), alice, bob);
1721 env.close();
1722
1723 std::uint32_t const ticketSeq = proposal::createTicket(env, alice);
1724 env(proposal::create(
1725 alice,
1726 proposal::unsignedPayload(env, pay(alice, bob, XRP(1)), ticketSeq),
1727 proposal::expiration(env, 100s)),
1729 env.close();
1730
1731 json::Value params;
1732 params[jss::account] = alice.human();
1733 params[jss::deletion_blockers_only] = true;
1734 params[jss::ledger_index] = "validated";
1735
1736 {
1737 auto const resp = env.rpc("json", "account_objects", to_string(params));
1738 auto const& aobjs = resp[jss::result][jss::account_objects];
1739 if (BEAST_EXPECT(aobjs.isArray() && aobjs.size() == 1))
1740 {
1741 BEAST_EXPECT(aobjs[0u][sfLedgerEntryType.jsonName] == jss::TransactionProposal);
1742 BEAST_EXPECT(aobjs[0u][sfOwner.jsonName] == alice.human());
1743 }
1744 }
1745
1746 {
1747 params[jss::type] = jss::transaction_proposal;
1748 auto const resp = env.rpc("json", "account_objects", to_string(params));
1749 auto const& aobjs = resp[jss::result][jss::account_objects];
1750 if (BEAST_EXPECT(aobjs.isArray() && aobjs.size() == 1))
1751 BEAST_EXPECT(aobjs[0u][sfLedgerEntryType.jsonName] == jss::TransactionProposal);
1752 }
1753
1754 {
1755 params[jss::type] = jss::check;
1756 auto const resp = env.rpc("json", "account_objects", to_string(params));
1757 auto const& aobjs = resp[jss::result][jss::account_objects];
1758 BEAST_EXPECT(aobjs.isArray() && aobjs.size() == 0);
1759 }
1760 }
1761
1762 void
1776};
1777
1779
1780} // namespace xrpl::test
A testsuite class.
Definition suite.h:52
TestcaseT testcase
Memberspace for declaring test cases.
Definition suite.h:155
Unserialize a JSON document into a Value.
Definition json_reader.h:20
bool parse(std::string const &document, Value &root)
Read a Value from a JSON document.
Represents a JSON value.
Definition json_value.h:117
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:115
static constexpr SeqProxy rawSequence(std::uint32_t v)
Factory function to return a sequence-based SeqProxy.
Definition SeqProxy.h:62
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:21
std::string const & human() const
Returns the human readable public key.
PublicKey const & pk() const
Return the public key.
Definition jtx/Account.h:84
AccountID id() const
Returns the Account ID.
A transaction testing environment.
Definition Env.h:161
bool close(NetClock::time_point closeTime, std::optional< std::chrono::milliseconds > consensusDelay=std::nullopt)
Close and advance the ledger.
Definition Env.cpp:133
SLE::const_pointer le(Account const &account) const
Return an account root.
Definition Env.cpp:311
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:323
std::uint32_t seq(Account const &account) const
Returns the next sequence number on account.
Definition Env.cpp:302
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:1058
void trust(STAmount const &amount, Account const &account)
Establish trust lines.
Definition Env.cpp:354
NetClock::time_point now()
Returns the current network time.
Definition Env.h:326
Set the regular signature on a JTx.
Definition sig.h:19
Set the flags on a JTx.
Definition txflags.h:14
T find(T... args)
@ Array
array value (ordered list)
Definition json_value.h:28
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
@ Null
'null' value
Definition json_value.h:22
Keylet check(AccountID const &id, SeqProxy const &seq) noexcept
A Check.
Definition Indexes.cpp:360
Keylet sponsorship(AccountID const &sponsor, AccountID const &sponsee) noexcept
A Sponsorship.
Definition Indexes.cpp:354
Keylet trustLine(AccountID const &id0, AccountID const &id1, Currency const &currency) noexcept
The index of a trust line for a given currency.
Definition Indexes.cpp:275
Taxon toTaxon(std::uint32_t i)
Definition nft.h:21
API version numbers used in later API versions.
Definition ApiVersion.h:36
Check operations.
Definition check.h:18
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:16
Escrow operations.
Definition escrow.h:19
json::Value setTx(AccountID const &account, Credentials const &credentials, std::optional< UInt256 > domain)
Create create()
Check the ledger effects a TransactionProposalCreate must have, whatever its outcome: on tesSUCCESS a...
Definition proposal.h:76
std::uint32_t createTicket(Env &env, Account const &account, std::uint32_t count)
Create tickets for a proposal to be built against, and close the ledger.
Definition proposal.cpp:118
json::Value unsignedPayload(Env const &env, json::Value tx, std::uint32_t ticketSeq)
Put a transaction of any type into the form a proposal stores it in: unsigned and ticket-based,...
Definition proposal.cpp:51
json::Value create(Account const &proposer, json::Value const &proposedTx, std::uint32_t expiration)
Build a TransactionProposalCreate carrying an unsigned proposed transaction.
Definition proposal.cpp:40
std::uint32_t expiration(Env &env, NetClock::duration delta)
An absolute expiration delta past the environment's current time.
Definition proposal.cpp:129
json::Value transfer(jtx::Account const &account, uint32_t flags, std::optional< UInt256 > const &index)
Definition sponsor.cpp:52
json::Value set(jtx::Account const &account, uint32_t flags, std::optional< int32_t > const reserveCountDelta, std::optional< STAmount > const feeAmountDelta, std::optional< STAmount > const maxFee)
Definition sponsor.cpp:22
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
json::Value offerCancel(Account const &account, std::uint32_t offerSeq)
Cancel an offer.
Definition offer.cpp:31
XrpT const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:92
FeatureBitset testableAmendments()
Definition Env.h:92
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:38
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)
json::Value trust(Account const &account, STAmount const &amount, std::uint32_t flags)
Modify a trust line.
Definition trust.cpp:18
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:13
std::string toBase58(AccountID const &v)
Convert AccountID to base58 checked string.
Definition AccountID.cpp:95
Seed generateSeed(std::string const &passPhrase)
Generate a seed deterministically.
Definition Seed.cpp:58
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:657
BaseUInt< 256 > UInt256
Definition base_uint.h:580
BaseUInt< 160, detail::AccountIDTag > AccountID
A 160-bit unsigned that uniquely identifies an account.
Definition AccountID.h:34
constexpr FlagValue spfSponsorReserve
Definition TxFlags.h:466
T has_value(T... args)
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)