22#include <xrpld/app/misc/CredentialHelpers.h>
23#include <xrpld/ledger/ApplyViewImpl.h>
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>
43 return strHex(expected) ==
strHex(sle->getFieldVL(field));
61 using namespace test::jtx;
63 const char credType[] =
"abcde";
64 const char uri[] =
"uri";
67 Account const subject{
"subject"};
70 Env env{*
this, features};
77 env.fund(
XRP(5000), subject, issuer, other);
85 auto const sleCred = env.le(credKey);
86 BEAST_EXPECT(
static_cast<bool>(sleCred));
90 BEAST_EXPECT(sleCred->getAccountID(sfSubject) == subject.id());
91 BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id());
92 BEAST_EXPECT(!sleCred->getFieldU32(sfFlags));
95 BEAST_EXPECT(
checkVL(sleCred, sfCredentialType, credType));
96 BEAST_EXPECT(
checkVL(sleCred, sfURI, uri));
100 jle.isObject() && jle.isMember(jss::result) &&
101 !jle[jss::result].isMember(jss::error) &&
102 jle[jss::result].isMember(jss::node) &&
103 jle[jss::result][jss::node].isMember(
"LedgerEntryType") &&
104 jle[jss::result][jss::node][
"LedgerEntryType"] ==
106 jle[jss::result][jss::node][jss::Issuer] ==
108 jle[jss::result][jss::node][jss::Subject] ==
110 jle[jss::result][jss::node][
"CredentialType"] ==
119 auto const sleCred = env.le(credKey);
120 BEAST_EXPECT(
static_cast<bool>(sleCred));
124 BEAST_EXPECT(sleCred->getAccountID(sfSubject) == subject.id());
125 BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id());
128 BEAST_EXPECT(
checkVL(sleCred, sfCredentialType, credType));
129 BEAST_EXPECT(
checkVL(sleCred, sfURI, uri));
130 BEAST_EXPECT(sleCred->getFieldU32(sfFlags) ==
lsfAccepted);
136 BEAST_EXPECT(!env.le(credKey));
144 jle.isObject() && jle.isMember(jss::result) &&
145 jle[jss::result].isMember(jss::error) &&
146 jle[jss::result][jss::error] ==
"entryNotFound");
159 auto const sleCred = env.le(credKey);
160 BEAST_EXPECT(
static_cast<bool>(sleCred));
164 BEAST_EXPECT(sleCred->getAccountID(sfSubject) == issuer.id());
165 BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id());
166 BEAST_EXPECT((sleCred->getFieldU32(sfFlags) &
lsfAccepted));
168 sleCred->getFieldU64(sfIssuerNode) ==
169 sleCred->getFieldU64(sfSubjectNode));
171 BEAST_EXPECT(
checkVL(sleCred, sfCredentialType, credType));
172 BEAST_EXPECT(
checkVL(sleCred, sfURI, uri));
176 jle.isObject() && jle.isMember(jss::result) &&
177 !jle[jss::result].isMember(jss::error) &&
178 jle[jss::result].isMember(jss::node) &&
179 jle[jss::result][jss::node].isMember(
"LedgerEntryType") &&
180 jle[jss::result][jss::node][
"LedgerEntryType"] ==
182 jle[jss::result][jss::node][jss::Issuer] ==
184 jle[jss::result][jss::node][jss::Subject] ==
186 jle[jss::result][jss::node][
"CredentialType"] ==
193 BEAST_EXPECT(!env.le(credKey));
200 jle.isObject() && jle.isMember(jss::result) &&
201 jle[jss::result].isMember(jss::error) &&
202 jle[jss::result][jss::error] ==
"entryNotFound");
210 using namespace test::jtx;
212 const char credType[] =
"abcde";
214 Account const issuer{
"issuer"};
215 Account const subject{
"subject"};
218 Env env{*
this, features};
221 env.fund(
XRP(5000), issuer, subject, other);
225 testcase(
"Delete issuer before accept");
233 int const delta = env.seq(issuer) + 255;
234 for (
int i = 0; i < delta; ++i)
236 auto const acctDelFee{
drops(env.current()->fees().increment)};
243 BEAST_EXPECT(!env.le(credKey));
250 jle.isObject() && jle.isMember(jss::result) &&
251 jle[jss::result].isMember(jss::error) &&
252 jle[jss::result][jss::error] ==
"entryNotFound");
256 env.fund(
XRP(5000), issuer);
261 testcase(
"Delete issuer after accept");
271 int const delta = env.seq(issuer) + 255;
272 for (
int i = 0; i < delta; ++i)
274 auto const acctDelFee{
drops(env.current()->fees().increment)};
281 BEAST_EXPECT(!env.le(credKey));
288 jle.isObject() && jle.isMember(jss::result) &&
289 jle[jss::result].isMember(jss::error) &&
290 jle[jss::result][jss::error] ==
"entryNotFound");
294 env.fund(
XRP(5000), issuer);
299 testcase(
"Delete subject before accept");
307 int const delta = env.seq(subject) + 255;
308 for (
int i = 0; i < delta; ++i)
310 auto const acctDelFee{
drops(env.current()->fees().increment)};
317 BEAST_EXPECT(!env.le(credKey));
324 jle.isObject() && jle.isMember(jss::result) &&
325 jle[jss::result].isMember(jss::error) &&
326 jle[jss::result][jss::error] ==
"entryNotFound");
330 env.fund(
XRP(5000), subject);
335 testcase(
"Delete subject after accept");
345 int const delta = env.seq(subject) + 255;
346 for (
int i = 0; i < delta; ++i)
348 auto const acctDelFee{
drops(env.current()->fees().increment)};
355 BEAST_EXPECT(!env.le(credKey));
362 jle.isObject() && jle.isMember(jss::result) &&
363 jle[jss::result].isMember(jss::error) &&
364 jle[jss::result][jss::error] ==
"entryNotFound");
368 env.fund(
XRP(5000), subject);
377 uint32_t
const t = env.current()
379 .parentCloseTime.time_since_epoch()
381 jv[sfExpiration.jsonName] = t + 20;
395 BEAST_EXPECT(!env.le(credKey));
403 jle.isObject() && jle.isMember(jss::result) &&
404 jle[jss::result].isMember(jss::error) &&
405 jle[jss::result][jss::error] ==
"entryNotFound");
421 BEAST_EXPECT(!env.le(credKey));
427 jle.isObject() && jle.isMember(jss::result) &&
428 jle[jss::result].isMember(jss::error) &&
429 jle[jss::result][jss::error] ==
"entryNotFound");
443 BEAST_EXPECT(!env.le(credKey));
449 jle.isObject() && jle.isMember(jss::result) &&
450 jle[jss::result].isMember(jss::error) &&
451 jle[jss::result][jss::error] ==
"entryNotFound");
459 using namespace test::jtx;
461 const char credType[] =
"abcde";
463 Account const issuer{
"issuer"};
464 Account const subject{
"subject"};
468 Env env{*
this, features};
470 env.fund(
XRP(5000), subject, issuer);
474 testcase(
"Credentials fail, no subject param.");
476 jv.removeMember(jss::Subject);
487 testcase(
"Credentials fail, no credentialType param.");
489 jv.removeMember(sfCredentialType.jsonName);
494 testcase(
"Credentials fail, empty credentialType param.");
501 "Credentials fail, credentialType length > "
502 "maxCredentialTypeLength.");
504 "abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]"
505 "asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p";
512 testcase(
"Credentials fail, URI length > 256.");
514 "abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]"
515 "asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p "
516 "9hfup;wDJFBVSD8f72 "
518 "djvbldafghwpEFHdjfaidfgio84763tfysgdvhjasbd "
519 "vujhgWQIE7F6WEUYFGWUKEYFVQW87FGWOEFWEFUYWVEF8723GFWEFB"
521 "fv28o37gfwEFB3872TFO8GSDSDVD";
529 testcase(
"Credentials fail, URI empty.");
536 testcase(
"Credentials fail, expiration in the past.");
539 uint32_t
const t = env.current()
541 .parentCloseTime.time_since_epoch()
544 jv[sfExpiration.jsonName] = t;
549 testcase(
"Credentials fail, invalid fee.");
557 testcase(
"Credentials fail, duplicate.");
568 jle.isObject() && jle.isMember(jss::result) &&
569 !jle[jss::result].isMember(jss::error) &&
570 jle[jss::result].isMember(jss::node) &&
571 jle[jss::result][jss::node].isMember(
"LedgerEntryType") &&
572 jle[jss::result][jss::node][
"LedgerEntryType"] ==
574 jle[jss::result][jss::node][jss::Issuer] ==
576 jle[jss::result][jss::node][jss::Subject] ==
578 jle[jss::result][jss::node][
"CredentialType"] ==
585 Env env{*
this, features};
587 env.fund(
XRP(5000), issuer);
591 testcase(
"Credentials fail, subject doesn't exist.");
599 Env env{*
this, features};
601 auto const reserve =
drops(env.current()->fees().accountReserve(0));
602 env.fund(
reserve, subject, issuer);
605 testcase(
"Credentials fail, not enough reserve.");
619 const char credType[] =
"abcde";
620 Account const issuer{
"issuer"};
621 Account const subject{
"subject"};
625 Env env{*
this, features};
627 env.fund(
XRP(5000), subject, issuer);
630 testcase(
"CredentialsAccept fail, Credential doesn't exist.");
637 testcase(
"CredentialsAccept fail, invalid Issuer account.");
646 "CredentialsAccept fail, invalid credentialType param.");
653 Env env{*
this, features};
655 env.fund(
drops(env.current()->fees().accountReserve(1)), issuer);
656 env.fund(
drops(env.current()->fees().accountReserve(0)), subject);
660 testcase(
"CredentialsAccept fail, not enough reserve.");
672 jle.isObject() && jle.isMember(jss::result) &&
673 !jle[jss::result].isMember(jss::error) &&
674 jle[jss::result].isMember(jss::node) &&
675 jle[jss::result][jss::node].isMember(
"LedgerEntryType") &&
676 jle[jss::result][jss::node][
"LedgerEntryType"] ==
678 jle[jss::result][jss::node][jss::Issuer] ==
680 jle[jss::result][jss::node][jss::Subject] ==
682 jle[jss::result][jss::node][
"CredentialType"] ==
689 Env env{*
this, features};
691 env.fund(
XRP(5000), subject, issuer);
698 testcase(
"CredentialsAccept fail, invalid fee.");
703 testcase(
"CredentialsAccept fail, lsfAccepted already set.");
714 jle.isObject() && jle.isMember(jss::result) &&
715 !jle[jss::result].isMember(jss::error) &&
716 jle[jss::result].isMember(jss::node) &&
717 jle[jss::result][jss::node].isMember(
"LedgerEntryType") &&
718 jle[jss::result][jss::node][
"LedgerEntryType"] ==
720 jle[jss::result][jss::node][jss::Issuer] ==
722 jle[jss::result][jss::node][jss::Subject] ==
724 jle[jss::result][jss::node][
"CredentialType"] ==
729 const char credType2[] =
"efghi";
731 testcase(
"CredentialsAccept fail, expired credentials.");
733 uint32_t
const t = env.current()
735 .parentCloseTime.time_since_epoch()
737 jv[sfExpiration.jsonName] = t;
747 auto const jDelCred =
750 jDelCred.isObject() && jDelCred.isMember(jss::result) &&
751 jDelCred[jss::result].isMember(jss::error) &&
752 jDelCred[jss::result][jss::error] ==
"entryNotFound");
761 Env env{*
this, features};
763 env.fund(
XRP(5000), issuer, subject, other);
767 testcase(
"CredentialsAccept fail, issuer doesn't exist.");
773 int const delta = env.seq(issuer) + 255;
774 for (
int i = 0; i < delta; ++i)
776 auto const acctDelFee{
drops(env.current()->fees().increment)};
785 auto const jDelCred =
788 jDelCred.isObject() && jDelCred.isMember(jss::result) &&
789 jDelCred[jss::result].isMember(jss::error) &&
790 jDelCred[jss::result][jss::error] ==
"entryNotFound");
798 using namespace test::jtx;
800 const char credType[] =
"abcde";
801 Account const issuer{
"issuer"};
802 Account const subject{
"subject"};
807 Env env{*
this, features};
809 env.fund(
XRP(5000), subject, issuer, other);
813 testcase(
"CredentialsDelete fail, no Credentials.");
820 testcase(
"CredentialsDelete fail, invalid Subject account.");
829 testcase(
"CredentialsDelete fail, invalid Issuer account.");
839 "CredentialsDelete fail, invalid credentialType param.");
845 const char credType2[] =
"fghij";
859 jle.isObject() && jle.isMember(jss::result) &&
860 !jle[jss::result].isMember(jss::error) &&
861 jle[jss::result].isMember(jss::node) &&
862 jle[jss::result][jss::node].isMember(
"LedgerEntryType") &&
863 jle[jss::result][jss::node][
"LedgerEntryType"] ==
865 jle[jss::result][jss::node][jss::Issuer] ==
867 jle[jss::result][jss::node][jss::Subject] ==
869 jle[jss::result][jss::node][
"CredentialType"] ==
874 testcase(
"CredentialsDelete fail, time not expired yet.");
878 uint32_t
const t = env.current()
880 .parentCloseTime.time_since_epoch()
883 jv[sfExpiration.jsonName] = t;
896 jle.isObject() && jle.isMember(jss::result) &&
897 !jle[jss::result].isMember(jss::error) &&
898 jle[jss::result].isMember(jss::node) &&
899 jle[jss::result][jss::node].isMember(
"LedgerEntryType") &&
900 jle[jss::result][jss::node][
"LedgerEntryType"] ==
902 jle[jss::result][jss::node][jss::Issuer] ==
904 jle[jss::result][jss::node][jss::Subject] ==
906 jle[jss::result][jss::node][
"CredentialType"] ==
911 testcase(
"CredentialsDelete fail, no Issuer and Subject.");
915 jv.removeMember(jss::Subject);
916 jv.removeMember(jss::Issuer);
922 testcase(
"CredentialsDelete fail, invalid fee.");
932 testcase(
"deleteSLE fail, bad SLE.");
933 auto view = std::make_shared<ApplyViewImpl>(
945 using namespace test::jtx;
947 const char credType[] =
"abcde";
948 Account const issuer{
"issuer"};
949 Account const subject{
"subject"};
953 Env env{*
this, features};
955 env.fund(
XRP(5000), subject, issuer);
959 testcase(
"Credentials fail, Feature is not enabled.");
973 using namespace test::jtx;
975 const char credType[] =
"abcde";
976 Account const issuer{
"issuer"};
977 Account const subject{
"subject"};
983 env.
fund(
XRP(5000), subject, issuer);
997 params[jss::account] = subject.human();
998 auto const jv = env.rpc(
999 "json",
"account_tx",
to_string(params))[jss::result];
1001 BEAST_EXPECT(jv[jss::transactions].size() == 4);
1002 auto const& tx0(jv[jss::transactions][0u][jss::tx]);
1004 tx0[jss::TransactionType] == jss::CredentialAccept);
1005 auto const& tx1(jv[jss::transactions][1u][jss::tx]);
1007 tx1[jss::TransactionType] == jss::CredentialCreate);
1008 txHash0 = tx0[jss::hash].asString();
1009 txHash1 = tx1[jss::hash].asString();
1014 params[jss::account] = issuer.human();
1015 auto const jv = env.rpc(
1016 "json",
"account_tx",
to_string(params))[jss::result];
1018 BEAST_EXPECT(jv[jss::transactions].size() == 4);
1019 auto const& tx0(jv[jss::transactions][0u][jss::tx]);
1021 tx0[jss::TransactionType] == jss::CredentialAccept);
1022 auto const& tx1(jv[jss::transactions][1u][jss::tx]);
1024 tx1[jss::TransactionType] == jss::CredentialCreate);
1026 BEAST_EXPECT(txHash0 == tx0[jss::hash].asString());
1027 BEAST_EXPECT(txHash1 == tx1[jss::hash].asString());
1034 params[jss::account] = subject.human();
1036 "json",
"account_objects",
to_string(params))[jss::result];
1038 BEAST_EXPECT(jv[jss::account_objects].size() == 1);
1039 auto const& object(jv[jss::account_objects][0u]);
1042 object[
"LedgerEntryType"].asString() == jss::Credential);
1043 objectIdx =
object[jss::index].asString();
1048 params[jss::account] = issuer.human();
1050 "json",
"account_objects",
to_string(params))[jss::result];
1052 BEAST_EXPECT(jv[jss::account_objects].size() == 1);
1053 auto const& object(jv[jss::account_objects][0u]);
1056 object[
"LedgerEntryType"].asString() == jss::Credential);
1057 BEAST_EXPECT(objectIdx ==
object[jss::index].asString());
1065 using namespace test::jtx;
1067 bool const enabled = features[fixInvalidTxFlags];
1070 (enabled ?
"enabled" :
"disabled"));
1072 const char credType[] =
"abcde";
1073 Account const issuer{
"issuer"};
1074 Account const subject{
"subject"};
1077 using namespace jtx;
1078 Env env{*
this, features};
1080 env.fund(
XRP(5000), subject, issuer);
1102 using namespace test::jtx;
1116BEAST_DEFINE_TESTSUITE(Credentials, app,
ripple);
testcase_t testcase
Memberspace for declaring test cases.
An immutable linear range of bytes.
Immutable cryptographic account descriptor.
AccountID id() const
Returns the Account ID.
A transaction testing environment.
void fund(bool setDefaultRipple, STAmount const &amount, Account const &account)
Set the expected result code for a JTx The test will fail if the code doesn't match.
TER deleteSLE(ApplyView &view, std::shared_ptr< SLE > const &sleCredential, beast::Journal j)
Keylet credential(AccountID const &subject, AccountID const &issuer, Slice const &credType) noexcept
Json::Value create(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Json::Value accept(jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Json::Value deleteCred(jtx::Account const &acc, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
Json::Value ledgerEntry(jtx::Env &env, jtx::Account const &subject, jtx::Account const &issuer, std::string_view credType)
std::uint32_t ownerCount(Env const &env, Account const &account)
PrettyAmount drops(Integer i)
Returns an XRP PrettyAmount, which is trivially convertible to STAmount.
Json::Value acctdelete(Account const &account, Account const &dest)
Delete account.
XRP_t const XRP
Converts to XRP Issue or STAmount.
FeatureBitset supported_amendments()
static bool checkVL(std::shared_ptr< SLE const > const &sle, SField const &field, std::string const &expected)
static Keylet credentialKeylet(test::jtx::Account const &subject, test::jtx::Account const &issuer, std::string_view credType)
static XRPAmount reserve(jtx::Env &env, std::uint32_t count)
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
AccountID const & xrpAccount()
Compute AccountID from public key.
std::size_t constexpr maxCredentialURILength
The maximum length of a URI inside a Credential.
constexpr std::uint32_t const tfSellNFToken
constexpr std::uint32_t tfPassive
std::string strHex(FwdIt begin, FwdIt end)
std::size_t constexpr maxCredentialTypeLength
The maximum length of a CredentialType inside a Credential.
@ tecINSUFFICIENT_RESERVE
std::string to_string(base_uint< Bits, Tag > const &a)
TERSubset< CanCvtToTER > TER
constexpr std::uint32_t const tfTransferable
A pair of SHAMap key and LedgerEntryType.
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)