mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-03 00:36:48 +00:00
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.
1115 lines
41 KiB
C++
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
|