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