rippled
Loading...
Searching...
No Matches
Credentials_test.cpp
1//------------------------------------------------------------------------------
2/*
3 This file is part of rippled: https://github.com/ripple/rippled
4 Copyright (c) 2024 Ripple Labs Inc.
5
6 Permission to use, copy, modify, and/or distribute this software for any
7 purpose with or without fee is hereby granted, provided that the above
8 copyright notice and this permission notice appear in all copies.
9
10 THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17*/
18//==============================================================================
19
20#include <test/jtx.h>
21
22#include <xrpl/basics/strHex.h>
23#include <xrpl/ledger/ApplyViewImpl.h>
24#include <xrpl/ledger/CredentialHelpers.h>
25#include <xrpl/protocol/Feature.h>
26#include <xrpl/protocol/Indexes.h>
27#include <xrpl/protocol/Protocol.h>
28#include <xrpl/protocol/TxFlags.h>
29#include <xrpl/protocol/jss.h>
30
31#include <string_view>
32
33namespace ripple {
34namespace test {
35
37{
38 void
40 {
41 using namespace test::jtx;
42
43 char const credType[] = "abcde";
44 char const uri[] = "uri";
45
46 Account const issuer{"issuer"};
47 Account const subject{"subject"};
48 Account const other{"other"};
49
50 Env env{*this, features};
51
52 {
53 testcase("Create for subject.");
54
55 auto const credKey = credentials::keylet(subject, issuer, credType);
56
57 env.fund(XRP(5000), subject, issuer, other);
58 env.close();
59
60 // Test Create credentials
61 env(credentials::create(subject, issuer, credType),
62 credentials::uri(uri));
63 env.close();
64 {
65 auto const sleCred = env.le(credKey);
66 BEAST_EXPECT(static_cast<bool>(sleCred));
67 if (!sleCred)
68 return;
69
70 BEAST_EXPECT(sleCred->getAccountID(sfSubject) == subject.id());
71 BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id());
72 BEAST_EXPECT(!sleCred->getFieldU32(sfFlags));
73 BEAST_EXPECT(ownerCount(env, issuer) == 1);
74 BEAST_EXPECT(!ownerCount(env, subject));
75 BEAST_EXPECT(checkVL(sleCred, sfCredentialType, credType));
76 BEAST_EXPECT(checkVL(sleCred, sfURI, uri));
77 auto const jle =
78 credentials::ledgerEntry(env, subject, issuer, credType);
79 BEAST_EXPECT(
80 jle.isObject() && jle.isMember(jss::result) &&
81 !jle[jss::result].isMember(jss::error) &&
82 jle[jss::result].isMember(jss::node) &&
83 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
84 jle[jss::result][jss::node]["LedgerEntryType"] ==
85 jss::Credential &&
86 jle[jss::result][jss::node][jss::Issuer] ==
87 issuer.human() &&
88 jle[jss::result][jss::node][jss::Subject] ==
89 subject.human() &&
90 jle[jss::result][jss::node]["CredentialType"] ==
91 strHex(std::string_view(credType)));
92 }
93
94 env(credentials::accept(subject, issuer, credType));
95 env.close();
96 {
97 // check switching owner of the credentials from issuer to
98 // subject
99 auto const sleCred = env.le(credKey);
100 BEAST_EXPECT(static_cast<bool>(sleCred));
101 if (!sleCred)
102 return;
103
104 BEAST_EXPECT(sleCred->getAccountID(sfSubject) == subject.id());
105 BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id());
106 BEAST_EXPECT(!ownerCount(env, issuer));
107 BEAST_EXPECT(ownerCount(env, subject) == 1);
108 BEAST_EXPECT(checkVL(sleCred, sfCredentialType, credType));
109 BEAST_EXPECT(checkVL(sleCred, sfURI, uri));
110 BEAST_EXPECT(sleCred->getFieldU32(sfFlags) == lsfAccepted);
111 }
112
113 env(credentials::deleteCred(subject, subject, issuer, credType));
114 env.close();
115 {
116 BEAST_EXPECT(!env.le(credKey));
117 BEAST_EXPECT(!ownerCount(env, issuer));
118 BEAST_EXPECT(!ownerCount(env, subject));
119
120 // check no credential exists anymore
121 auto const jle =
122 credentials::ledgerEntry(env, subject, issuer, credType);
123 BEAST_EXPECT(
124 jle.isObject() && jle.isMember(jss::result) &&
125 jle[jss::result].isMember(jss::error) &&
126 jle[jss::result][jss::error] == "entryNotFound");
127 }
128 }
129
130 {
131 testcase("Create for themself.");
132
133 auto const credKey = credentials::keylet(issuer, issuer, credType);
134
135 env(credentials::create(issuer, issuer, credType),
136 credentials::uri(uri));
137 env.close();
138 {
139 auto const sleCred = env.le(credKey);
140 BEAST_EXPECT(static_cast<bool>(sleCred));
141 if (!sleCred)
142 return;
143
144 BEAST_EXPECT(sleCred->getAccountID(sfSubject) == issuer.id());
145 BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id());
146 BEAST_EXPECT((sleCred->getFieldU32(sfFlags) & lsfAccepted));
147 BEAST_EXPECT(
148 sleCred->getFieldU64(sfIssuerNode) ==
149 sleCred->getFieldU64(sfSubjectNode));
150 BEAST_EXPECT(ownerCount(env, issuer) == 1);
151 BEAST_EXPECT(checkVL(sleCred, sfCredentialType, credType));
152 BEAST_EXPECT(checkVL(sleCred, sfURI, uri));
153 auto const jle =
154 credentials::ledgerEntry(env, issuer, issuer, credType);
155 BEAST_EXPECT(
156 jle.isObject() && jle.isMember(jss::result) &&
157 !jle[jss::result].isMember(jss::error) &&
158 jle[jss::result].isMember(jss::node) &&
159 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
160 jle[jss::result][jss::node]["LedgerEntryType"] ==
161 jss::Credential &&
162 jle[jss::result][jss::node][jss::Issuer] ==
163 issuer.human() &&
164 jle[jss::result][jss::node][jss::Subject] ==
165 issuer.human() &&
166 jle[jss::result][jss::node]["CredentialType"] ==
167 strHex(std::string_view(credType)));
168 }
169
170 env(credentials::deleteCred(issuer, issuer, issuer, credType));
171 env.close();
172 {
173 BEAST_EXPECT(!env.le(credKey));
174 BEAST_EXPECT(!ownerCount(env, issuer));
175
176 // check no credential exists anymore
177 auto const jle =
178 credentials::ledgerEntry(env, issuer, issuer, credType);
179 BEAST_EXPECT(
180 jle.isObject() && jle.isMember(jss::result) &&
181 jle[jss::result].isMember(jss::error) &&
182 jle[jss::result][jss::error] == "entryNotFound");
183 }
184 }
185 }
186
187 void
189 {
190 using namespace test::jtx;
191
192 char const credType[] = "abcde";
193
194 Account const issuer{"issuer"};
195 Account const subject{"subject"};
196 Account const other{"other"};
197
198 Env env{*this, features};
199
200 // fund subject and issuer
201 env.fund(XRP(5000), issuer, subject, other);
202 env.close();
203
204 {
205 testcase("Delete issuer before accept");
206
207 auto const credKey = credentials::keylet(subject, issuer, credType);
208 env(credentials::create(subject, issuer, credType));
209 env.close();
210
211 // delete issuer
212 {
213 int const delta = env.seq(issuer) + 255;
214 for (int i = 0; i < delta; ++i)
215 env.close();
216 auto const acctDelFee{drops(env.current()->fees().increment)};
217 env(acctdelete(issuer, other), fee(acctDelFee));
218 env.close();
219 }
220
221 // check credentials deleted too
222 {
223 BEAST_EXPECT(!env.le(credKey));
224 BEAST_EXPECT(!ownerCount(env, subject));
225
226 // check no credential exists anymore
227 auto const jle =
228 credentials::ledgerEntry(env, subject, issuer, credType);
229 BEAST_EXPECT(
230 jle.isObject() && jle.isMember(jss::result) &&
231 jle[jss::result].isMember(jss::error) &&
232 jle[jss::result][jss::error] == "entryNotFound");
233 }
234
235 // resurrection
236 env.fund(XRP(5000), issuer);
237 env.close();
238 }
239
240 {
241 testcase("Delete issuer after accept");
242
243 auto const credKey = credentials::keylet(subject, issuer, credType);
244 env(credentials::create(subject, issuer, credType));
245 env.close();
246 env(credentials::accept(subject, issuer, credType));
247 env.close();
248
249 // delete issuer
250 {
251 int const delta = env.seq(issuer) + 255;
252 for (int i = 0; i < delta; ++i)
253 env.close();
254 auto const acctDelFee{drops(env.current()->fees().increment)};
255 env(acctdelete(issuer, other), fee(acctDelFee));
256 env.close();
257 }
258
259 // check credentials deleted too
260 {
261 BEAST_EXPECT(!env.le(credKey));
262 BEAST_EXPECT(!ownerCount(env, subject));
263
264 // check no credential exists anymore
265 auto const jle =
266 credentials::ledgerEntry(env, subject, issuer, credType);
267 BEAST_EXPECT(
268 jle.isObject() && jle.isMember(jss::result) &&
269 jle[jss::result].isMember(jss::error) &&
270 jle[jss::result][jss::error] == "entryNotFound");
271 }
272
273 // resurrection
274 env.fund(XRP(5000), issuer);
275 env.close();
276 }
277
278 {
279 testcase("Delete subject before accept");
280
281 auto const credKey = credentials::keylet(subject, issuer, credType);
282 env(credentials::create(subject, issuer, credType));
283 env.close();
284
285 // delete subject
286 {
287 int const delta = env.seq(subject) + 255;
288 for (int i = 0; i < delta; ++i)
289 env.close();
290 auto const acctDelFee{drops(env.current()->fees().increment)};
291 env(acctdelete(subject, other), fee(acctDelFee));
292 env.close();
293 }
294
295 // check credentials deleted too
296 {
297 BEAST_EXPECT(!env.le(credKey));
298 BEAST_EXPECT(!ownerCount(env, issuer));
299
300 // check no credential exists anymore
301 auto const jle =
302 credentials::ledgerEntry(env, subject, issuer, credType);
303 BEAST_EXPECT(
304 jle.isObject() && jle.isMember(jss::result) &&
305 jle[jss::result].isMember(jss::error) &&
306 jle[jss::result][jss::error] == "entryNotFound");
307 }
308
309 // resurrection
310 env.fund(XRP(5000), subject);
311 env.close();
312 }
313
314 {
315 testcase("Delete subject after accept");
316
317 auto const credKey = credentials::keylet(subject, issuer, credType);
318 env(credentials::create(subject, issuer, credType));
319 env.close();
320 env(credentials::accept(subject, issuer, credType));
321 env.close();
322
323 // delete subject
324 {
325 int const delta = env.seq(subject) + 255;
326 for (int i = 0; i < delta; ++i)
327 env.close();
328 auto const acctDelFee{drops(env.current()->fees().increment)};
329 env(acctdelete(subject, other), fee(acctDelFee));
330 env.close();
331 }
332
333 // check credentials deleted too
334 {
335 BEAST_EXPECT(!env.le(credKey));
336 BEAST_EXPECT(!ownerCount(env, issuer));
337
338 // check no credential exists anymore
339 auto const jle =
340 credentials::ledgerEntry(env, subject, issuer, credType);
341 BEAST_EXPECT(
342 jle.isObject() && jle.isMember(jss::result) &&
343 jle[jss::result].isMember(jss::error) &&
344 jle[jss::result][jss::error] == "entryNotFound");
345 }
346
347 // resurrection
348 env.fund(XRP(5000), subject);
349 env.close();
350 }
351
352 {
353 testcase("Delete by other");
354
355 auto const credKey = credentials::keylet(subject, issuer, credType);
356 auto jv = credentials::create(subject, issuer, credType);
357 uint32_t const t = env.current()
358 ->info()
359 .parentCloseTime.time_since_epoch()
360 .count();
361 jv[sfExpiration.jsonName] = t + 20;
362 env(jv);
363
364 // time advance
365 env.close();
366 env.close();
367 env.close();
368
369 // Other account delete credentials
370 env(credentials::deleteCred(other, subject, issuer, credType));
371 env.close();
372
373 // check credentials object
374 {
375 BEAST_EXPECT(!env.le(credKey));
376 BEAST_EXPECT(!ownerCount(env, issuer));
377 BEAST_EXPECT(!ownerCount(env, subject));
378
379 // check no credential exists anymore
380 auto const jle =
381 credentials::ledgerEntry(env, subject, issuer, credType);
382 BEAST_EXPECT(
383 jle.isObject() && jle.isMember(jss::result) &&
384 jle[jss::result].isMember(jss::error) &&
385 jle[jss::result][jss::error] == "entryNotFound");
386 }
387 }
388
389 {
390 testcase("Delete by subject");
391
392 env(credentials::create(subject, issuer, credType));
393 env.close();
394
395 // Subject can delete
396 env(credentials::deleteCred(subject, subject, issuer, credType));
397 env.close();
398 {
399 auto const credKey =
400 credentials::keylet(subject, issuer, credType);
401 BEAST_EXPECT(!env.le(credKey));
402 BEAST_EXPECT(!ownerCount(env, subject));
403 BEAST_EXPECT(!ownerCount(env, issuer));
404 auto const jle =
405 credentials::ledgerEntry(env, subject, issuer, credType);
406 BEAST_EXPECT(
407 jle.isObject() && jle.isMember(jss::result) &&
408 jle[jss::result].isMember(jss::error) &&
409 jle[jss::result][jss::error] == "entryNotFound");
410 }
411 }
412
413 {
414 testcase("Delete by issuer");
415 env(credentials::create(subject, issuer, credType));
416 env.close();
417
418 env(credentials::deleteCred(issuer, subject, issuer, credType));
419 env.close();
420 {
421 auto const credKey =
422 credentials::keylet(subject, issuer, credType);
423 BEAST_EXPECT(!env.le(credKey));
424 BEAST_EXPECT(!ownerCount(env, subject));
425 BEAST_EXPECT(!ownerCount(env, issuer));
426 auto const jle =
427 credentials::ledgerEntry(env, subject, issuer, credType);
428 BEAST_EXPECT(
429 jle.isObject() && jle.isMember(jss::result) &&
430 jle[jss::result].isMember(jss::error) &&
431 jle[jss::result][jss::error] == "entryNotFound");
432 }
433 }
434 }
435
436 void
438 {
439 using namespace test::jtx;
440
441 char const credType[] = "abcde";
442
443 Account const issuer{"issuer"};
444 Account const subject{"subject"};
445
446 {
447 using namespace jtx;
448 Env env{*this, features};
449
450 env.fund(XRP(5000), subject, issuer);
451 env.close();
452
453 {
454 testcase("Credentials fail, no subject param.");
455 auto jv = credentials::create(subject, issuer, credType);
456 jv.removeMember(jss::Subject);
457 env(jv, ter(temMALFORMED));
458 }
459
460 {
461 auto jv = credentials::create(subject, issuer, credType);
462 jv[jss::Subject] = to_string(xrpAccount());
463 env(jv, ter(temMALFORMED));
464 }
465
466 {
467 testcase("Credentials fail, no credentialType param.");
468 auto jv = credentials::create(subject, issuer, credType);
469 jv.removeMember(sfCredentialType.jsonName);
470 env(jv, ter(temMALFORMED));
471 }
472
473 {
474 testcase("Credentials fail, empty credentialType param.");
475 auto jv = credentials::create(subject, issuer, "");
476 env(jv, ter(temMALFORMED));
477 }
478
479 {
480 testcase(
481 "Credentials fail, credentialType length > "
482 "maxCredentialTypeLength.");
483 constexpr std::string_view longCredType =
484 "abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]"
485 "asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p";
486 static_assert(longCredType.size() > maxCredentialTypeLength);
487 auto jv = credentials::create(subject, issuer, longCredType);
488 env(jv, ter(temMALFORMED));
489 }
490
491 {
492 testcase("Credentials fail, URI length > 256.");
493 constexpr std::string_view longURI =
494 "abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]"
495 "asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p "
496 "9hfup;wDJFBVSD8f72 "
497 "pfhiusdovnbs;"
498 "djvbldafghwpEFHdjfaidfgio84763tfysgdvhjasbd "
499 "vujhgWQIE7F6WEUYFGWUKEYFVQW87FGWOEFWEFUYWVEF8723GFWEFB"
500 "WULE"
501 "fv28o37gfwEFB3872TFO8GSDSDVD";
502 static_assert(longURI.size() > maxCredentialURILength);
503 env(credentials::create(subject, issuer, credType),
504 credentials::uri(longURI),
506 }
507
508 {
509 testcase("Credentials fail, URI empty.");
510 env(credentials::create(subject, issuer, credType),
513 }
514
515 {
516 testcase("Credentials fail, expiration in the past.");
517 auto jv = credentials::create(subject, issuer, credType);
518 // current time in ripple epoch - 1s
519 uint32_t const t = env.current()
520 ->info()
521 .parentCloseTime.time_since_epoch()
522 .count() -
523 1;
524 jv[sfExpiration.jsonName] = t;
525 env(jv, ter(tecEXPIRED));
526 }
527
528 {
529 testcase("Credentials fail, invalid fee.");
530
531 auto jv = credentials::create(subject, issuer, credType);
532 jv[jss::Fee] = -1;
533 env(jv, ter(temBAD_FEE));
534 }
535
536 {
537 testcase("Credentials fail, duplicate.");
538 auto const jv = credentials::create(subject, issuer, credType);
539 env(jv);
540 env.close();
541 env(jv, ter(tecDUPLICATE));
542 env.close();
543
544 // check credential still present
545 auto const jle =
546 credentials::ledgerEntry(env, subject, issuer, credType);
547 BEAST_EXPECT(
548 jle.isObject() && jle.isMember(jss::result) &&
549 !jle[jss::result].isMember(jss::error) &&
550 jle[jss::result].isMember(jss::node) &&
551 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
552 jle[jss::result][jss::node]["LedgerEntryType"] ==
553 jss::Credential &&
554 jle[jss::result][jss::node][jss::Issuer] ==
555 issuer.human() &&
556 jle[jss::result][jss::node][jss::Subject] ==
557 subject.human() &&
558 jle[jss::result][jss::node]["CredentialType"] ==
559 strHex(std::string_view(credType)));
560 }
561 }
562
563 {
564 using namespace jtx;
565 Env env{*this, features};
566
567 env.fund(XRP(5000), issuer);
568 env.close();
569
570 {
571 testcase("Credentials fail, subject doesn't exist.");
572 auto const jv = credentials::create(subject, issuer, credType);
573 env(jv, ter(tecNO_TARGET));
574 }
575 }
576
577 {
578 using namespace jtx;
579 Env env{*this, features};
580
581 auto const reserve = drops(env.current()->fees().accountReserve(0));
582 env.fund(reserve, subject, issuer);
583 env.close();
584
585 testcase("Credentials fail, not enough reserve.");
586 {
587 auto const jv = credentials::create(subject, issuer, credType);
589 env.close();
590 }
591 }
592 }
593
594 void
596 {
597 using namespace jtx;
598
599 char const credType[] = "abcde";
600 Account const issuer{"issuer"};
601 Account const subject{"subject"};
602 Account const other{"other"};
603
604 {
605 Env env{*this, features};
606
607 env.fund(XRP(5000), subject, issuer);
608
609 {
610 testcase("CredentialsAccept fail, Credential doesn't exist.");
611 env(credentials::accept(subject, issuer, credType),
613 env.close();
614 }
615
616 {
617 testcase("CredentialsAccept fail, invalid Issuer account.");
618 auto jv = credentials::accept(subject, issuer, credType);
619 jv[jss::Issuer] = to_string(xrpAccount());
620 env(jv, ter(temINVALID_ACCOUNT_ID));
621 env.close();
622 }
623
624 {
625 testcase(
626 "CredentialsAccept fail, invalid credentialType param.");
627 auto jv = credentials::accept(subject, issuer, "");
628 env(jv, ter(temMALFORMED));
629 }
630 }
631
632 {
633 Env env{*this, features};
634
635 env.fund(drops(env.current()->fees().accountReserve(1)), issuer);
636 env.fund(drops(env.current()->fees().accountReserve(0)), subject);
637 env.close();
638
639 {
640 testcase("CredentialsAccept fail, not enough reserve.");
641 env(credentials::create(subject, issuer, credType));
642 env.close();
643
644 env(credentials::accept(subject, issuer, credType),
646 env.close();
647
648 // check credential still present
649 auto const jle =
650 credentials::ledgerEntry(env, subject, issuer, credType);
651 BEAST_EXPECT(
652 jle.isObject() && jle.isMember(jss::result) &&
653 !jle[jss::result].isMember(jss::error) &&
654 jle[jss::result].isMember(jss::node) &&
655 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
656 jle[jss::result][jss::node]["LedgerEntryType"] ==
657 jss::Credential &&
658 jle[jss::result][jss::node][jss::Issuer] ==
659 issuer.human() &&
660 jle[jss::result][jss::node][jss::Subject] ==
661 subject.human() &&
662 jle[jss::result][jss::node]["CredentialType"] ==
663 strHex(std::string_view(credType)));
664 }
665 }
666
667 {
668 using namespace jtx;
669 Env env{*this, features};
670
671 env.fund(XRP(5000), subject, issuer);
672 env.close();
673
674 {
675 env(credentials::create(subject, issuer, credType));
676 env.close();
677
678 testcase("CredentialsAccept fail, invalid fee.");
679 auto jv = credentials::accept(subject, issuer, credType);
680 jv[jss::Fee] = -1;
681 env(jv, ter(temBAD_FEE));
682
683 testcase("CredentialsAccept fail, lsfAccepted already set.");
684 env(credentials::accept(subject, issuer, credType));
685 env.close();
686 env(credentials::accept(subject, issuer, credType),
688 env.close();
689
690 // check credential still present
691 auto const jle =
692 credentials::ledgerEntry(env, subject, issuer, credType);
693 BEAST_EXPECT(
694 jle.isObject() && jle.isMember(jss::result) &&
695 !jle[jss::result].isMember(jss::error) &&
696 jle[jss::result].isMember(jss::node) &&
697 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
698 jle[jss::result][jss::node]["LedgerEntryType"] ==
699 jss::Credential &&
700 jle[jss::result][jss::node][jss::Issuer] ==
701 issuer.human() &&
702 jle[jss::result][jss::node][jss::Subject] ==
703 subject.human() &&
704 jle[jss::result][jss::node]["CredentialType"] ==
705 strHex(std::string_view(credType)));
706 }
707
708 {
709 char const credType2[] = "efghi";
710
711 testcase("CredentialsAccept fail, expired credentials.");
712 auto jv = credentials::create(subject, issuer, credType2);
713 uint32_t const t = env.current()
714 ->info()
715 .parentCloseTime.time_since_epoch()
716 .count();
717 jv[sfExpiration.jsonName] = t;
718 env(jv);
719 env.close();
720
721 // credentials are expired now
722 env(credentials::accept(subject, issuer, credType2),
723 ter(tecEXPIRED));
724 env.close();
725
726 // check that expired credentials were deleted
727 auto const jDelCred =
728 credentials::ledgerEntry(env, subject, issuer, credType2);
729 BEAST_EXPECT(
730 jDelCred.isObject() && jDelCred.isMember(jss::result) &&
731 jDelCred[jss::result].isMember(jss::error) &&
732 jDelCred[jss::result][jss::error] == "entryNotFound");
733
734 BEAST_EXPECT(ownerCount(env, issuer) == 0);
735 BEAST_EXPECT(ownerCount(env, subject) == 1);
736 }
737 }
738
739 {
740 using namespace jtx;
741 Env env{*this, features};
742
743 env.fund(XRP(5000), issuer, subject, other);
744 env.close();
745
746 {
747 testcase("CredentialsAccept fail, issuer doesn't exist.");
748 auto jv = credentials::create(subject, issuer, credType);
749 env(jv);
750 env.close();
751
752 // delete issuer
753 int const delta = env.seq(issuer) + 255;
754 for (int i = 0; i < delta; ++i)
755 env.close();
756 auto const acctDelFee{drops(env.current()->fees().increment)};
757 env(acctdelete(issuer, other), fee(acctDelFee));
758
759 // can't accept - no issuer account
760 jv = credentials::accept(subject, issuer, credType);
761 env(jv, ter(tecNO_ISSUER));
762 env.close();
763
764 // check that expired credentials were deleted
765 auto const jDelCred =
766 credentials::ledgerEntry(env, subject, issuer, credType);
767 BEAST_EXPECT(
768 jDelCred.isObject() && jDelCred.isMember(jss::result) &&
769 jDelCred[jss::result].isMember(jss::error) &&
770 jDelCred[jss::result][jss::error] == "entryNotFound");
771 }
772 }
773 }
774
775 void
777 {
778 using namespace test::jtx;
779
780 char const credType[] = "abcde";
781 Account const issuer{"issuer"};
782 Account const subject{"subject"};
783 Account const other{"other"};
784
785 {
786 using namespace jtx;
787 Env env{*this, features};
788
789 env.fund(XRP(5000), subject, issuer, other);
790 env.close();
791
792 {
793 testcase("CredentialsDelete fail, no Credentials.");
794 env(credentials::deleteCred(subject, subject, issuer, credType),
796 env.close();
797 }
798
799 {
800 testcase("CredentialsDelete fail, invalid Subject account.");
801 auto jv =
802 credentials::deleteCred(subject, subject, issuer, credType);
803 jv[jss::Subject] = to_string(xrpAccount());
804 env(jv, ter(temINVALID_ACCOUNT_ID));
805 env.close();
806 }
807
808 {
809 testcase("CredentialsDelete fail, invalid Issuer account.");
810 auto jv =
811 credentials::deleteCred(subject, subject, issuer, credType);
812 jv[jss::Issuer] = to_string(xrpAccount());
813 env(jv, ter(temINVALID_ACCOUNT_ID));
814 env.close();
815 }
816
817 {
818 testcase(
819 "CredentialsDelete fail, invalid credentialType param.");
820 auto jv = credentials::deleteCred(subject, subject, issuer, "");
821 env(jv, ter(temMALFORMED));
822 }
823
824 {
825 char const credType2[] = "fghij";
826
827 env(credentials::create(subject, issuer, credType2));
828 env.close();
829
830 // Other account can't delete credentials without expiration
831 env(credentials::deleteCred(other, subject, issuer, credType2),
833 env.close();
834
835 // check credential still present
836 auto const jle =
837 credentials::ledgerEntry(env, subject, issuer, credType2);
838 BEAST_EXPECT(
839 jle.isObject() && jle.isMember(jss::result) &&
840 !jle[jss::result].isMember(jss::error) &&
841 jle[jss::result].isMember(jss::node) &&
842 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
843 jle[jss::result][jss::node]["LedgerEntryType"] ==
844 jss::Credential &&
845 jle[jss::result][jss::node][jss::Issuer] ==
846 issuer.human() &&
847 jle[jss::result][jss::node][jss::Subject] ==
848 subject.human() &&
849 jle[jss::result][jss::node]["CredentialType"] ==
850 strHex(std::string_view(credType2)));
851 }
852
853 {
854 testcase("CredentialsDelete fail, time not expired yet.");
855
856 auto jv = credentials::create(subject, issuer, credType);
857 // current time in ripple epoch + 1000s
858 uint32_t const t = env.current()
859 ->info()
860 .parentCloseTime.time_since_epoch()
861 .count() +
862 1000;
863 jv[sfExpiration.jsonName] = t;
864 env(jv);
865 env.close();
866
867 // Other account can't delete credentials that not expired
868 env(credentials::deleteCred(other, subject, issuer, credType),
870 env.close();
871
872 // check credential still present
873 auto const jle =
874 credentials::ledgerEntry(env, subject, issuer, credType);
875 BEAST_EXPECT(
876 jle.isObject() && jle.isMember(jss::result) &&
877 !jle[jss::result].isMember(jss::error) &&
878 jle[jss::result].isMember(jss::node) &&
879 jle[jss::result][jss::node].isMember("LedgerEntryType") &&
880 jle[jss::result][jss::node]["LedgerEntryType"] ==
881 jss::Credential &&
882 jle[jss::result][jss::node][jss::Issuer] ==
883 issuer.human() &&
884 jle[jss::result][jss::node][jss::Subject] ==
885 subject.human() &&
886 jle[jss::result][jss::node]["CredentialType"] ==
887 strHex(std::string_view(credType)));
888 }
889
890 {
891 testcase("CredentialsDelete fail, no Issuer and Subject.");
892
893 auto jv =
894 credentials::deleteCred(subject, subject, issuer, credType);
895 jv.removeMember(jss::Subject);
896 jv.removeMember(jss::Issuer);
897 env(jv, ter(temMALFORMED));
898 env.close();
899 }
900
901 {
902 testcase("CredentialsDelete fail, invalid fee.");
903
904 auto jv =
905 credentials::deleteCred(subject, subject, issuer, credType);
906 jv[jss::Fee] = -1;
907 env(jv, ter(temBAD_FEE));
908 env.close();
909 }
910
911 {
912 testcase("deleteSLE fail, bad SLE.");
914 env.current().get(), ApplyFlags::tapNONE);
915 auto ter =
916 ripple::credentials::deleteSLE(*view, {}, env.journal);
917 BEAST_EXPECT(ter == tecNO_ENTRY);
918 }
919 }
920 }
921
922 void
924 {
925 using namespace test::jtx;
926
927 char const credType[] = "abcde";
928 Account const issuer{"issuer"};
929 Account const subject{"subject"};
930
931 {
932 using namespace jtx;
933 Env env{*this, features};
934
935 env.fund(XRP(5000), subject, issuer);
936 env.close();
937
938 {
939 testcase("Credentials fail, Feature is not enabled.");
940 env(credentials::create(subject, issuer, credType),
942 env(credentials::accept(subject, issuer, credType),
944 env(credentials::deleteCred(subject, subject, issuer, credType),
946 }
947 }
948 }
949
950 void
952 {
953 using namespace test::jtx;
954
955 char const credType[] = "abcde";
956 Account const issuer{"issuer"};
957 Account const subject{"subject"};
958
959 {
960 using namespace jtx;
961 Env env{*this};
962
963 env.fund(XRP(5000), subject, issuer);
964 env.close();
965
966 env(credentials::create(subject, issuer, credType));
967 env.close();
968
969 env(credentials::accept(subject, issuer, credType));
970 env.close();
971
972 testcase("account_tx");
973
974 std::string txHash0, txHash1;
975 {
976 Json::Value params;
977 params[jss::account] = subject.human();
978 auto const jv = env.rpc(
979 "json", "account_tx", to_string(params))[jss::result];
980
981 BEAST_EXPECT(jv[jss::transactions].size() == 4);
982 auto const& tx0(jv[jss::transactions][0u][jss::tx]);
983 BEAST_EXPECT(
984 tx0[jss::TransactionType] == jss::CredentialAccept);
985 auto const& tx1(jv[jss::transactions][1u][jss::tx]);
986 BEAST_EXPECT(
987 tx1[jss::TransactionType] == jss::CredentialCreate);
988 txHash0 = tx0[jss::hash].asString();
989 txHash1 = tx1[jss::hash].asString();
990 }
991
992 {
993 Json::Value params;
994 params[jss::account] = issuer.human();
995 auto const jv = env.rpc(
996 "json", "account_tx", to_string(params))[jss::result];
997
998 BEAST_EXPECT(jv[jss::transactions].size() == 4);
999 auto const& tx0(jv[jss::transactions][0u][jss::tx]);
1000 BEAST_EXPECT(
1001 tx0[jss::TransactionType] == jss::CredentialAccept);
1002 auto const& tx1(jv[jss::transactions][1u][jss::tx]);
1003 BEAST_EXPECT(
1004 tx1[jss::TransactionType] == jss::CredentialCreate);
1005
1006 BEAST_EXPECT(txHash0 == tx0[jss::hash].asString());
1007 BEAST_EXPECT(txHash1 == tx1[jss::hash].asString());
1008 }
1009
1010 testcase("account_objects");
1011 std::string objectIdx;
1012 {
1013 Json::Value params;
1014 params[jss::account] = subject.human();
1015 auto jv = env.rpc(
1016 "json", "account_objects", to_string(params))[jss::result];
1017
1018 BEAST_EXPECT(jv[jss::account_objects].size() == 1);
1019 auto const& object(jv[jss::account_objects][0u]);
1020
1021 BEAST_EXPECT(
1022 object["LedgerEntryType"].asString() == jss::Credential);
1023 objectIdx = object[jss::index].asString();
1024 }
1025
1026 {
1027 Json::Value params;
1028 params[jss::account] = issuer.human();
1029 auto jv = env.rpc(
1030 "json", "account_objects", to_string(params))[jss::result];
1031
1032 BEAST_EXPECT(jv[jss::account_objects].size() == 1);
1033 auto const& object(jv[jss::account_objects][0u]);
1034
1035 BEAST_EXPECT(
1036 object["LedgerEntryType"].asString() == jss::Credential);
1037 BEAST_EXPECT(objectIdx == object[jss::index].asString());
1038 }
1039 }
1040 }
1041
1042 void
1044 {
1045 using namespace test::jtx;
1046
1047 bool const enabled = features[fixInvalidTxFlags];
1048 testcase(
1049 std::string("Test flag, fix ") +
1050 (enabled ? "enabled" : "disabled"));
1051
1052 char const credType[] = "abcde";
1053 Account const issuer{"issuer"};
1054 Account const subject{"subject"};
1055
1056 {
1057 using namespace jtx;
1058 Env env{*this, features};
1059
1060 env.fund(XRP(5000), subject, issuer);
1061 env.close();
1062
1063 {
1064 ter const expected(
1065 enabled ? TER(temINVALID_FLAG) : TER(tesSUCCESS));
1066 env(credentials::create(subject, issuer, credType),
1068 expected);
1069 env(credentials::accept(subject, issuer, credType),
1071 expected);
1072 env(credentials::deleteCred(subject, subject, issuer, credType),
1074 expected);
1075 }
1076 }
1077 }
1078
1079 void
1080 run() override
1081 {
1082 using namespace test::jtx;
1089 testFeatureFailed(all - featureCredentials);
1090 testFlags(all - fixInvalidTxFlags);
1091 testFlags(all);
1092 testRPC();
1093 }
1094};
1095
1096BEAST_DEFINE_TESTSUITE(Credentials, app, ripple);
1097
1098} // namespace test
1099} // namespace ripple
Represents a JSON value.
Definition json_value.h:149
A testsuite class.
Definition suite.h:55
testcase_t testcase
Memberspace for declaring test cases.
Definition suite.h:155
Immutable cryptographic account descriptor.
Definition Account.h:39
A transaction testing environment.
Definition Env.h:121
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Definition Env.cpp:289
Set the fee on a JTx.
Definition fee.h:37
Set the expected result code for a JTx The test will fail if the code doesn't match.
Definition ter.h:35
Set the flags on a JTx.
Definition txflags.h:31
T is_same_v
TER deleteSLE(ApplyView &view, std::shared_ptr< SLE > const &sleCredential, beast::Journal j)
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:32
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:48
Keylet keylet(test::jtx::Account const &subject, test::jtx::Account const &issuer, std::string_view credType)
Definition credentials.h:34
Json::Value deleteCred(jtx::Account const &acc, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:62
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Definition creds.cpp:78
std::uint32_t ownerCount(Env const &env, Account const &account)
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
bool checkVL(Slice const &result, std::string const &expected)
FeatureBitset testable_amendments()
Definition Env.h:74
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
XRP_t const XRP
Converts to XRP Issue or STAmount.
Definition amount.cpp:111
static XRPAmount reserve(jtx::Env &env, std::uint32_t count)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:25
AccountID const & xrpAccount()
Compute AccountID from public key.
std::size_t constexpr maxCredentialURILength
The maximum length of a URI inside a Credential.
Definition Protocol.h:100
constexpr std::uint32_t const tfSellNFToken
Definition TxFlags.h:230
constexpr std::uint32_t tfPassive
Definition TxFlags.h:98
std::string strHex(FwdIt begin, FwdIt end)
Definition strHex.h:30
std::size_t constexpr maxCredentialTypeLength
The maximum length of a CredentialType inside a Credential.
Definition Protocol.h:103
@ tecNO_ENTRY
Definition TER.h:306
@ tecNO_ISSUER
Definition TER.h:299
@ tecNO_TARGET
Definition TER.h:304
@ tecDUPLICATE
Definition TER.h:315
@ tecNO_PERMISSION
Definition TER.h:305
@ tecINSUFFICIENT_RESERVE
Definition TER.h:307
@ tecEXPIRED
Definition TER.h:314
@ tesSUCCESS
Definition TER.h:244
std::string to_string(base_uint< Bits, Tag > const &a)
Definition base_uint.h:630
@ tapNONE
Definition ApplyView.h:31
TERSubset< CanCvtToTER > TER
Definition TER.h:645
constexpr std::uint32_t const tfTransferable
Definition TxFlags.h:142
@ temBAD_FEE
Definition TER.h:92
@ temMALFORMED
Definition TER.h:87
@ temINVALID_FLAG
Definition TER.h:111
@ temDISABLED
Definition TER.h:114
@ temINVALID_ACCOUNT_ID
Definition TER.h:119
T size(T... args)
void testAcceptFailed(FeatureBitset features)
void testSuccessful(FeatureBitset features)
void testDeleteFailed(FeatureBitset features)
void testFeatureFailed(FeatureBitset features)
void testFlags(FeatureBitset features)
void testCredentialsDelete(FeatureBitset features)
void run() override
Runs the suite.
void testCreateFailed(FeatureBitset features)