Files
rippled/src/test/app/Credentials_test.cpp
Bart 1d42c4f6de refactor: Remove unnecessary copyright notices already covered by LICENSE.md (#5929)
Per XLS-0095, we are taking steps to rename ripple(d) to xrpl(d).

This change specifically removes all copyright notices referencing Ripple, XRPLF, and certain affiliated contributors upon mutual agreement, so the notice in the LICENSE.md file applies throughout. Copyright notices referencing external contributions remain as-is. Duplicate verbiage is also removed.
2025-11-04 08:33:42 +00:00

1115 lines
41 KiB
C++

#include <test/jtx.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/ledger/ApplyViewImpl.h>
#include <xrpl/ledger/CredentialHelpers.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Protocol.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/jss.h>
#include <string_view>
namespace ripple {
namespace test {
struct Credentials_test : public beast::unit_test::suite
{
void
testSuccessful(FeatureBitset features)
{
using namespace test::jtx;
char const credType[] = "abcde";
char const uri[] = "uri";
Account const issuer{"issuer"};
Account const subject{"subject"};
Account const other{"other"};
Env env{*this, features};
{
testcase("Create for subject.");
auto const credKey = credentials::keylet(subject, issuer, credType);
env.fund(XRP(5000), subject, issuer, other);
env.close();
// Test Create credentials
env(credentials::create(subject, issuer, credType),
credentials::uri(uri));
env.close();
{
auto const sleCred = env.le(credKey);
BEAST_EXPECT(static_cast<bool>(sleCred));
if (!sleCred)
return;
BEAST_EXPECT(sleCred->getAccountID(sfSubject) == subject.id());
BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id());
BEAST_EXPECT(!sleCred->getFieldU32(sfFlags));
BEAST_EXPECT(ownerCount(env, issuer) == 1);
BEAST_EXPECT(!ownerCount(env, subject));
BEAST_EXPECT(checkVL(sleCred, sfCredentialType, credType));
BEAST_EXPECT(checkVL(sleCred, sfURI, uri));
auto const jle =
credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
!jle[jss::result].isMember(jss::error) &&
jle[jss::result].isMember(jss::node) &&
jle[jss::result][jss::node].isMember("LedgerEntryType") &&
jle[jss::result][jss::node]["LedgerEntryType"] ==
jss::Credential &&
jle[jss::result][jss::node][jss::Issuer] ==
issuer.human() &&
jle[jss::result][jss::node][jss::Subject] ==
subject.human() &&
jle[jss::result][jss::node]["CredentialType"] ==
strHex(std::string_view(credType)));
}
env(credentials::accept(subject, issuer, credType));
env.close();
{
// check switching owner of the credentials from issuer to
// subject
auto const sleCred = env.le(credKey);
BEAST_EXPECT(static_cast<bool>(sleCred));
if (!sleCred)
return;
BEAST_EXPECT(sleCred->getAccountID(sfSubject) == subject.id());
BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id());
BEAST_EXPECT(!ownerCount(env, issuer));
BEAST_EXPECT(ownerCount(env, subject) == 1);
BEAST_EXPECT(checkVL(sleCred, sfCredentialType, credType));
BEAST_EXPECT(checkVL(sleCred, sfURI, uri));
BEAST_EXPECT(sleCred->getFieldU32(sfFlags) == lsfAccepted);
}
env(credentials::deleteCred(subject, subject, issuer, credType));
env.close();
{
BEAST_EXPECT(!env.le(credKey));
BEAST_EXPECT(!ownerCount(env, issuer));
BEAST_EXPECT(!ownerCount(env, subject));
// check no credential exists anymore
auto const jle =
credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
}
}
{
testcase("Create for themself.");
auto const credKey = credentials::keylet(issuer, issuer, credType);
env(credentials::create(issuer, issuer, credType),
credentials::uri(uri));
env.close();
{
auto const sleCred = env.le(credKey);
BEAST_EXPECT(static_cast<bool>(sleCred));
if (!sleCred)
return;
BEAST_EXPECT(sleCred->getAccountID(sfSubject) == issuer.id());
BEAST_EXPECT(sleCred->getAccountID(sfIssuer) == issuer.id());
BEAST_EXPECT((sleCred->getFieldU32(sfFlags) & lsfAccepted));
BEAST_EXPECT(
sleCred->getFieldU64(sfIssuerNode) ==
sleCred->getFieldU64(sfSubjectNode));
BEAST_EXPECT(ownerCount(env, issuer) == 1);
BEAST_EXPECT(checkVL(sleCred, sfCredentialType, credType));
BEAST_EXPECT(checkVL(sleCred, sfURI, uri));
auto const jle =
credentials::ledgerEntry(env, issuer, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
!jle[jss::result].isMember(jss::error) &&
jle[jss::result].isMember(jss::node) &&
jle[jss::result][jss::node].isMember("LedgerEntryType") &&
jle[jss::result][jss::node]["LedgerEntryType"] ==
jss::Credential &&
jle[jss::result][jss::node][jss::Issuer] ==
issuer.human() &&
jle[jss::result][jss::node][jss::Subject] ==
issuer.human() &&
jle[jss::result][jss::node]["CredentialType"] ==
strHex(std::string_view(credType)));
}
env(credentials::deleteCred(issuer, issuer, issuer, credType));
env.close();
{
BEAST_EXPECT(!env.le(credKey));
BEAST_EXPECT(!ownerCount(env, issuer));
// check no credential exists anymore
auto const jle =
credentials::ledgerEntry(env, issuer, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
}
}
}
void
testCredentialsDelete(FeatureBitset features)
{
using namespace test::jtx;
char const credType[] = "abcde";
Account const issuer{"issuer"};
Account const subject{"subject"};
Account const other{"other"};
Env env{*this, features};
// fund subject and issuer
env.fund(XRP(5000), issuer, subject, other);
env.close();
{
testcase("Delete issuer before accept");
auto const credKey = credentials::keylet(subject, issuer, credType);
env(credentials::create(subject, issuer, credType));
env.close();
// delete issuer
{
int const delta = env.seq(issuer) + 255;
for (int i = 0; i < delta; ++i)
env.close();
auto const acctDelFee{drops(env.current()->fees().increment)};
env(acctdelete(issuer, other), fee(acctDelFee));
env.close();
}
// check credentials deleted too
{
BEAST_EXPECT(!env.le(credKey));
BEAST_EXPECT(!ownerCount(env, subject));
// check no credential exists anymore
auto const jle =
credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
}
// resurrection
env.fund(XRP(5000), issuer);
env.close();
}
{
testcase("Delete issuer after accept");
auto const credKey = credentials::keylet(subject, issuer, credType);
env(credentials::create(subject, issuer, credType));
env.close();
env(credentials::accept(subject, issuer, credType));
env.close();
// delete issuer
{
int const delta = env.seq(issuer) + 255;
for (int i = 0; i < delta; ++i)
env.close();
auto const acctDelFee{drops(env.current()->fees().increment)};
env(acctdelete(issuer, other), fee(acctDelFee));
env.close();
}
// check credentials deleted too
{
BEAST_EXPECT(!env.le(credKey));
BEAST_EXPECT(!ownerCount(env, subject));
// check no credential exists anymore
auto const jle =
credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
}
// resurrection
env.fund(XRP(5000), issuer);
env.close();
}
{
testcase("Delete subject before accept");
auto const credKey = credentials::keylet(subject, issuer, credType);
env(credentials::create(subject, issuer, credType));
env.close();
// delete subject
{
int const delta = env.seq(subject) + 255;
for (int i = 0; i < delta; ++i)
env.close();
auto const acctDelFee{drops(env.current()->fees().increment)};
env(acctdelete(subject, other), fee(acctDelFee));
env.close();
}
// check credentials deleted too
{
BEAST_EXPECT(!env.le(credKey));
BEAST_EXPECT(!ownerCount(env, issuer));
// check no credential exists anymore
auto const jle =
credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
}
// resurrection
env.fund(XRP(5000), subject);
env.close();
}
{
testcase("Delete subject after accept");
auto const credKey = credentials::keylet(subject, issuer, credType);
env(credentials::create(subject, issuer, credType));
env.close();
env(credentials::accept(subject, issuer, credType));
env.close();
// delete subject
{
int const delta = env.seq(subject) + 255;
for (int i = 0; i < delta; ++i)
env.close();
auto const acctDelFee{drops(env.current()->fees().increment)};
env(acctdelete(subject, other), fee(acctDelFee));
env.close();
}
// check credentials deleted too
{
BEAST_EXPECT(!env.le(credKey));
BEAST_EXPECT(!ownerCount(env, issuer));
// check no credential exists anymore
auto const jle =
credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
}
// resurrection
env.fund(XRP(5000), subject);
env.close();
}
{
testcase("Delete by other");
auto const credKey = credentials::keylet(subject, issuer, credType);
auto jv = credentials::create(subject, issuer, credType);
uint32_t const t = env.current()
->info()
.parentCloseTime.time_since_epoch()
.count();
jv[sfExpiration.jsonName] = t + 20;
env(jv);
// time advance
env.close();
env.close();
env.close();
// Other account delete credentials
env(credentials::deleteCred(other, subject, issuer, credType));
env.close();
// check credentials object
{
BEAST_EXPECT(!env.le(credKey));
BEAST_EXPECT(!ownerCount(env, issuer));
BEAST_EXPECT(!ownerCount(env, subject));
// check no credential exists anymore
auto const jle =
credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
}
}
{
testcase("Delete by subject");
env(credentials::create(subject, issuer, credType));
env.close();
// Subject can delete
env(credentials::deleteCred(subject, subject, issuer, credType));
env.close();
{
auto const credKey =
credentials::keylet(subject, issuer, credType);
BEAST_EXPECT(!env.le(credKey));
BEAST_EXPECT(!ownerCount(env, subject));
BEAST_EXPECT(!ownerCount(env, issuer));
auto const jle =
credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
}
}
{
testcase("Delete by issuer");
env(credentials::create(subject, issuer, credType));
env.close();
env(credentials::deleteCred(issuer, subject, issuer, credType));
env.close();
{
auto const credKey =
credentials::keylet(subject, issuer, credType);
BEAST_EXPECT(!env.le(credKey));
BEAST_EXPECT(!ownerCount(env, subject));
BEAST_EXPECT(!ownerCount(env, issuer));
auto const jle =
credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
jle[jss::result].isMember(jss::error) &&
jle[jss::result][jss::error] == "entryNotFound");
}
}
}
void
testCreateFailed(FeatureBitset features)
{
using namespace test::jtx;
char const credType[] = "abcde";
Account const issuer{"issuer"};
Account const subject{"subject"};
{
using namespace jtx;
Env env{*this, features};
env.fund(XRP(5000), subject, issuer);
env.close();
{
testcase("Credentials fail, no subject param.");
auto jv = credentials::create(subject, issuer, credType);
jv.removeMember(jss::Subject);
env(jv, ter(temMALFORMED));
}
{
auto jv = credentials::create(subject, issuer, credType);
jv[jss::Subject] = to_string(xrpAccount());
env(jv, ter(temMALFORMED));
}
{
testcase("Credentials fail, no credentialType param.");
auto jv = credentials::create(subject, issuer, credType);
jv.removeMember(sfCredentialType.jsonName);
env(jv, ter(temMALFORMED));
}
{
testcase("Credentials fail, empty credentialType param.");
auto jv = credentials::create(subject, issuer, "");
env(jv, ter(temMALFORMED));
}
{
testcase(
"Credentials fail, credentialType length > "
"maxCredentialTypeLength.");
constexpr std::string_view longCredType =
"abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]"
"asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p";
static_assert(longCredType.size() > maxCredentialTypeLength);
auto jv = credentials::create(subject, issuer, longCredType);
env(jv, ter(temMALFORMED));
}
{
testcase("Credentials fail, URI length > 256.");
constexpr std::string_view longURI =
"abcdefghijklmnopqrstuvwxyz01234567890qwertyuiop[]"
"asdfghjkl;'zxcvbnm8237tr28weufwldebvfv8734t07p "
"9hfup;wDJFBVSD8f72 "
"pfhiusdovnbs;"
"djvbldafghwpEFHdjfaidfgio84763tfysgdvhjasbd "
"vujhgWQIE7F6WEUYFGWUKEYFVQW87FGWOEFWEFUYWVEF8723GFWEFB"
"WULE"
"fv28o37gfwEFB3872TFO8GSDSDVD";
static_assert(longURI.size() > maxCredentialURILength);
env(credentials::create(subject, issuer, credType),
credentials::uri(longURI),
ter(temMALFORMED));
}
{
testcase("Credentials fail, URI empty.");
env(credentials::create(subject, issuer, credType),
credentials::uri(""),
ter(temMALFORMED));
}
{
testcase("Credentials fail, expiration in the past.");
auto jv = credentials::create(subject, issuer, credType);
// current time in ripple epoch - 1s
uint32_t const t = env.current()
->info()
.parentCloseTime.time_since_epoch()
.count() -
1;
jv[sfExpiration.jsonName] = t;
env(jv, ter(tecEXPIRED));
}
{
testcase("Credentials fail, invalid fee.");
auto jv = credentials::create(subject, issuer, credType);
jv[jss::Fee] = -1;
env(jv, ter(temBAD_FEE));
}
{
testcase("Credentials fail, duplicate.");
auto const jv = credentials::create(subject, issuer, credType);
env(jv);
env.close();
env(jv, ter(tecDUPLICATE));
env.close();
// check credential still present
auto const jle =
credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
!jle[jss::result].isMember(jss::error) &&
jle[jss::result].isMember(jss::node) &&
jle[jss::result][jss::node].isMember("LedgerEntryType") &&
jle[jss::result][jss::node]["LedgerEntryType"] ==
jss::Credential &&
jle[jss::result][jss::node][jss::Issuer] ==
issuer.human() &&
jle[jss::result][jss::node][jss::Subject] ==
subject.human() &&
jle[jss::result][jss::node]["CredentialType"] ==
strHex(std::string_view(credType)));
}
{
testcase("Credentials fail, directory full");
std::uint32_t const issuerSeq{env.seq(issuer) + 1};
env(ticket::create(issuer, 63));
env.close();
// Everything below can only be tested on open ledger.
auto const res1 = directory::bumpLastPage(
env,
directory::maximumPageIndex(env),
keylet::ownerDir(issuer.id()),
directory::adjustOwnerNode);
BEAST_EXPECT(res1);
auto const jv = credentials::create(issuer, subject, credType);
env(jv, ter(tecDIR_FULL));
// Free one directory entry by using a ticket
env(noop(issuer), ticket::use(issuerSeq + 40));
// Fill subject directory
env(ticket::create(subject, 63));
auto const res2 = directory::bumpLastPage(
env,
directory::maximumPageIndex(env),
keylet::ownerDir(subject.id()),
directory::adjustOwnerNode);
BEAST_EXPECT(res2);
env(jv, ter(tecDIR_FULL));
// End test
env.close();
}
}
{
using namespace jtx;
Env env{*this, features};
env.fund(XRP(5000), issuer);
env.close();
{
testcase("Credentials fail, subject doesn't exist.");
auto const jv = credentials::create(subject, issuer, credType);
env(jv, ter(tecNO_TARGET));
}
}
{
using namespace jtx;
Env env{*this, features};
auto const reserve = drops(env.current()->fees().reserve);
env.fund(reserve, subject, issuer);
env.close();
testcase("Credentials fail, not enough reserve.");
{
auto const jv = credentials::create(subject, issuer, credType);
env(jv, ter(tecINSUFFICIENT_RESERVE));
env.close();
}
}
}
void
testAcceptFailed(FeatureBitset features)
{
using namespace jtx;
char const credType[] = "abcde";
Account const issuer{"issuer"};
Account const subject{"subject"};
Account const other{"other"};
{
Env env{*this, features};
env.fund(XRP(5000), subject, issuer);
{
testcase("CredentialsAccept fail, Credential doesn't exist.");
env(credentials::accept(subject, issuer, credType),
ter(tecNO_ENTRY));
env.close();
}
{
testcase("CredentialsAccept fail, invalid Issuer account.");
auto jv = credentials::accept(subject, issuer, credType);
jv[jss::Issuer] = to_string(xrpAccount());
env(jv, ter(temINVALID_ACCOUNT_ID));
env.close();
}
{
testcase(
"CredentialsAccept fail, invalid credentialType param.");
auto jv = credentials::accept(subject, issuer, "");
env(jv, ter(temMALFORMED));
}
}
{
Env env{*this, features};
env.fund(drops(env.current()->fees().accountReserve(1)), issuer);
env.fund(drops(env.current()->fees().accountReserve(0)), subject);
env.close();
{
testcase("CredentialsAccept fail, not enough reserve.");
env(credentials::create(subject, issuer, credType));
env.close();
env(credentials::accept(subject, issuer, credType),
ter(tecINSUFFICIENT_RESERVE));
env.close();
// check credential still present
auto const jle =
credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
!jle[jss::result].isMember(jss::error) &&
jle[jss::result].isMember(jss::node) &&
jle[jss::result][jss::node].isMember("LedgerEntryType") &&
jle[jss::result][jss::node]["LedgerEntryType"] ==
jss::Credential &&
jle[jss::result][jss::node][jss::Issuer] ==
issuer.human() &&
jle[jss::result][jss::node][jss::Subject] ==
subject.human() &&
jle[jss::result][jss::node]["CredentialType"] ==
strHex(std::string_view(credType)));
}
}
{
using namespace jtx;
Env env{*this, features};
env.fund(XRP(5000), subject, issuer);
env.close();
{
env(credentials::create(subject, issuer, credType));
env.close();
testcase("CredentialsAccept fail, invalid fee.");
auto jv = credentials::accept(subject, issuer, credType);
jv[jss::Fee] = -1;
env(jv, ter(temBAD_FEE));
testcase("CredentialsAccept fail, lsfAccepted already set.");
env(credentials::accept(subject, issuer, credType));
env.close();
env(credentials::accept(subject, issuer, credType),
ter(tecDUPLICATE));
env.close();
// check credential still present
auto const jle =
credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
!jle[jss::result].isMember(jss::error) &&
jle[jss::result].isMember(jss::node) &&
jle[jss::result][jss::node].isMember("LedgerEntryType") &&
jle[jss::result][jss::node]["LedgerEntryType"] ==
jss::Credential &&
jle[jss::result][jss::node][jss::Issuer] ==
issuer.human() &&
jle[jss::result][jss::node][jss::Subject] ==
subject.human() &&
jle[jss::result][jss::node]["CredentialType"] ==
strHex(std::string_view(credType)));
}
{
char const credType2[] = "efghi";
testcase("CredentialsAccept fail, expired credentials.");
auto jv = credentials::create(subject, issuer, credType2);
uint32_t const t = env.current()
->info()
.parentCloseTime.time_since_epoch()
.count();
jv[sfExpiration.jsonName] = t;
env(jv);
env.close();
// credentials are expired now
env(credentials::accept(subject, issuer, credType2),
ter(tecEXPIRED));
env.close();
// check that expired credentials were deleted
auto const jDelCred =
credentials::ledgerEntry(env, subject, issuer, credType2);
BEAST_EXPECT(
jDelCred.isObject() && jDelCred.isMember(jss::result) &&
jDelCred[jss::result].isMember(jss::error) &&
jDelCred[jss::result][jss::error] == "entryNotFound");
BEAST_EXPECT(ownerCount(env, issuer) == 0);
BEAST_EXPECT(ownerCount(env, subject) == 1);
}
}
{
using namespace jtx;
Env env{*this, features};
env.fund(XRP(5000), issuer, subject, other);
env.close();
{
testcase("CredentialsAccept fail, issuer doesn't exist.");
auto jv = credentials::create(subject, issuer, credType);
env(jv);
env.close();
// delete issuer
int const delta = env.seq(issuer) + 255;
for (int i = 0; i < delta; ++i)
env.close();
auto const acctDelFee{drops(env.current()->fees().increment)};
env(acctdelete(issuer, other), fee(acctDelFee));
// can't accept - no issuer account
jv = credentials::accept(subject, issuer, credType);
env(jv, ter(tecNO_ISSUER));
env.close();
// check that expired credentials were deleted
auto const jDelCred =
credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jDelCred.isObject() && jDelCred.isMember(jss::result) &&
jDelCred[jss::result].isMember(jss::error) &&
jDelCred[jss::result][jss::error] == "entryNotFound");
}
}
}
void
testDeleteFailed(FeatureBitset features)
{
using namespace test::jtx;
char const credType[] = "abcde";
Account const issuer{"issuer"};
Account const subject{"subject"};
Account const other{"other"};
{
using namespace jtx;
Env env{*this, features};
env.fund(XRP(5000), subject, issuer, other);
env.close();
{
testcase("CredentialsDelete fail, no Credentials.");
env(credentials::deleteCred(subject, subject, issuer, credType),
ter(tecNO_ENTRY));
env.close();
}
{
testcase("CredentialsDelete fail, invalid Subject account.");
auto jv =
credentials::deleteCred(subject, subject, issuer, credType);
jv[jss::Subject] = to_string(xrpAccount());
env(jv, ter(temINVALID_ACCOUNT_ID));
env.close();
}
{
testcase("CredentialsDelete fail, invalid Issuer account.");
auto jv =
credentials::deleteCred(subject, subject, issuer, credType);
jv[jss::Issuer] = to_string(xrpAccount());
env(jv, ter(temINVALID_ACCOUNT_ID));
env.close();
}
{
testcase(
"CredentialsDelete fail, invalid credentialType param.");
auto jv = credentials::deleteCred(subject, subject, issuer, "");
env(jv, ter(temMALFORMED));
}
{
char const credType2[] = "fghij";
env(credentials::create(subject, issuer, credType2));
env.close();
// Other account can't delete credentials without expiration
env(credentials::deleteCred(other, subject, issuer, credType2),
ter(tecNO_PERMISSION));
env.close();
// check credential still present
auto const jle =
credentials::ledgerEntry(env, subject, issuer, credType2);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
!jle[jss::result].isMember(jss::error) &&
jle[jss::result].isMember(jss::node) &&
jle[jss::result][jss::node].isMember("LedgerEntryType") &&
jle[jss::result][jss::node]["LedgerEntryType"] ==
jss::Credential &&
jle[jss::result][jss::node][jss::Issuer] ==
issuer.human() &&
jle[jss::result][jss::node][jss::Subject] ==
subject.human() &&
jle[jss::result][jss::node]["CredentialType"] ==
strHex(std::string_view(credType2)));
}
{
testcase("CredentialsDelete fail, time not expired yet.");
auto jv = credentials::create(subject, issuer, credType);
// current time in ripple epoch + 1000s
uint32_t const t = env.current()
->info()
.parentCloseTime.time_since_epoch()
.count() +
1000;
jv[sfExpiration.jsonName] = t;
env(jv);
env.close();
// Other account can't delete credentials that not expired
env(credentials::deleteCred(other, subject, issuer, credType),
ter(tecNO_PERMISSION));
env.close();
// check credential still present
auto const jle =
credentials::ledgerEntry(env, subject, issuer, credType);
BEAST_EXPECT(
jle.isObject() && jle.isMember(jss::result) &&
!jle[jss::result].isMember(jss::error) &&
jle[jss::result].isMember(jss::node) &&
jle[jss::result][jss::node].isMember("LedgerEntryType") &&
jle[jss::result][jss::node]["LedgerEntryType"] ==
jss::Credential &&
jle[jss::result][jss::node][jss::Issuer] ==
issuer.human() &&
jle[jss::result][jss::node][jss::Subject] ==
subject.human() &&
jle[jss::result][jss::node]["CredentialType"] ==
strHex(std::string_view(credType)));
}
{
testcase("CredentialsDelete fail, no Issuer and Subject.");
auto jv =
credentials::deleteCred(subject, subject, issuer, credType);
jv.removeMember(jss::Subject);
jv.removeMember(jss::Issuer);
env(jv, ter(temMALFORMED));
env.close();
}
{
testcase("CredentialsDelete fail, invalid fee.");
auto jv =
credentials::deleteCred(subject, subject, issuer, credType);
jv[jss::Fee] = -1;
env(jv, ter(temBAD_FEE));
env.close();
}
{
testcase("deleteSLE fail, bad SLE.");
auto view = std::make_shared<ApplyViewImpl>(
env.current().get(), ApplyFlags::tapNONE);
auto ter =
ripple::credentials::deleteSLE(*view, {}, env.journal);
BEAST_EXPECT(ter == tecNO_ENTRY);
}
}
}
void
testFeatureFailed(FeatureBitset features)
{
using namespace test::jtx;
char const credType[] = "abcde";
Account const issuer{"issuer"};
Account const subject{"subject"};
{
using namespace jtx;
Env env{*this, features};
env.fund(XRP(5000), subject, issuer);
env.close();
{
testcase("Credentials fail, Feature is not enabled.");
env(credentials::create(subject, issuer, credType),
ter(temDISABLED));
env(credentials::accept(subject, issuer, credType),
ter(temDISABLED));
env(credentials::deleteCred(subject, subject, issuer, credType),
ter(temDISABLED));
}
}
}
void
testRPC()
{
using namespace test::jtx;
char const credType[] = "abcde";
Account const issuer{"issuer"};
Account const subject{"subject"};
{
using namespace jtx;
Env env{*this};
env.fund(XRP(5000), subject, issuer);
env.close();
env(credentials::create(subject, issuer, credType));
env.close();
env(credentials::accept(subject, issuer, credType));
env.close();
testcase("account_tx");
std::string txHash0, txHash1;
{
Json::Value params;
params[jss::account] = subject.human();
auto const jv = env.rpc(
"json", "account_tx", to_string(params))[jss::result];
BEAST_EXPECT(jv[jss::transactions].size() == 4);
auto const& tx0(jv[jss::transactions][0u][jss::tx]);
BEAST_EXPECT(
tx0[jss::TransactionType] == jss::CredentialAccept);
auto const& tx1(jv[jss::transactions][1u][jss::tx]);
BEAST_EXPECT(
tx1[jss::TransactionType] == jss::CredentialCreate);
txHash0 = tx0[jss::hash].asString();
txHash1 = tx1[jss::hash].asString();
}
{
Json::Value params;
params[jss::account] = issuer.human();
auto const jv = env.rpc(
"json", "account_tx", to_string(params))[jss::result];
BEAST_EXPECT(jv[jss::transactions].size() == 4);
auto const& tx0(jv[jss::transactions][0u][jss::tx]);
BEAST_EXPECT(
tx0[jss::TransactionType] == jss::CredentialAccept);
auto const& tx1(jv[jss::transactions][1u][jss::tx]);
BEAST_EXPECT(
tx1[jss::TransactionType] == jss::CredentialCreate);
BEAST_EXPECT(txHash0 == tx0[jss::hash].asString());
BEAST_EXPECT(txHash1 == tx1[jss::hash].asString());
}
testcase("account_objects");
std::string objectIdx;
{
Json::Value params;
params[jss::account] = subject.human();
auto jv = env.rpc(
"json", "account_objects", to_string(params))[jss::result];
BEAST_EXPECT(jv[jss::account_objects].size() == 1);
auto const& object(jv[jss::account_objects][0u]);
BEAST_EXPECT(
object["LedgerEntryType"].asString() == jss::Credential);
objectIdx = object[jss::index].asString();
}
{
Json::Value params;
params[jss::account] = issuer.human();
auto jv = env.rpc(
"json", "account_objects", to_string(params))[jss::result];
BEAST_EXPECT(jv[jss::account_objects].size() == 1);
auto const& object(jv[jss::account_objects][0u]);
BEAST_EXPECT(
object["LedgerEntryType"].asString() == jss::Credential);
BEAST_EXPECT(objectIdx == object[jss::index].asString());
}
}
}
void
testFlags(FeatureBitset features)
{
using namespace test::jtx;
bool const enabled = features[fixInvalidTxFlags];
testcase(
std::string("Test flag, fix ") +
(enabled ? "enabled" : "disabled"));
char const credType[] = "abcde";
Account const issuer{"issuer"};
Account const subject{"subject"};
{
using namespace jtx;
Env env{*this, features};
env.fund(XRP(5000), subject, issuer);
env.close();
{
ter const expected(
enabled ? TER(temINVALID_FLAG) : TER(tesSUCCESS));
env(credentials::create(subject, issuer, credType),
txflags(tfTransferable),
expected);
env(credentials::accept(subject, issuer, credType),
txflags(tfSellNFToken),
expected);
env(credentials::deleteCred(subject, subject, issuer, credType),
txflags(tfPassive),
expected);
}
}
}
void
run() override
{
using namespace test::jtx;
FeatureBitset const all{testable_amendments()};
testSuccessful(all);
testCredentialsDelete(all);
testCreateFailed(all);
testCreateFailed(all - fixDirectoryLimit);
testAcceptFailed(all);
testDeleteFailed(all);
testFeatureFailed(all - featureCredentials);
testFlags(all - fixInvalidTxFlags);
testFlags(all);
testRPC();
}
};
BEAST_DEFINE_TESTSUITE(Credentials, app, ripple);
} // namespace test
} // namespace ripple