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