diff --git a/include/xrpl/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index ff4653e5c7d..14c318e5a56 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -149,6 +149,8 @@ enum LedgerSpecificFlags { 0x40000000, // True, enable trustline locking lsfAllowTrustLineClawback = 0x80000000, // True, enable clawback + lsfDisallowIncomingCredential = + 0x00008000, // True, reject new credentials // ltOFFER lsfPassive = 0x00010000, diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index dcbc10b9275..efd7d5c9e24 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -93,6 +93,7 @@ constexpr std::uint32_t asfDisallowIncomingPayChan = 14; constexpr std::uint32_t asfDisallowIncomingTrustline = 15; constexpr std::uint32_t asfAllowTrustLineClawback = 16; constexpr std::uint32_t asfAllowTrustLineLocking = 17; +constexpr std::uint32_t asfDisallowIncomingCredential = 18; // OfferCreate flags: constexpr std::uint32_t tfPassive = 0x00010000; diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index d5351b1c404..406116f4c3e 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -32,6 +32,7 @@ // If you add an amendment here, then do not forget to increment `numFeatures` // in include/xrpl/protocol/Feature.h. +XRPL_FEATURE(DisallowIncomingCredential, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (IncludeKeyletFields, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(DynamicMPT, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (TokenEscrowV1, Supported::yes, VoteBehavior::DefaultNo) diff --git a/src/test/app/Credentials_test.cpp b/src/test/app/Credentials_test.cpp index 102d516b893..676dbe4df72 100644 --- a/src/test/app/Credentials_test.cpp +++ b/src/test/app/Credentials_test.cpp @@ -589,6 +589,52 @@ struct Credentials_test : public beast::unit_test::suite env.close(); } } + + { + using namespace jtx; + { + // test flag doesn't set unless amendment enabled + Env env{*this, features - featureDisallowIncomingCredential}; + env.fund(XRP(5000), subject, issuer); + env.close(); + + env(fset(subject, asfDisallowIncomingCredential)); + env.close(); + auto const sle = env.le(subject); + uint32_t flags = sle->getFlags(); + BEAST_EXPECT(!(flags & lsfDisallowIncomingCredential)); + } + + testcase("Credentials fail, disallow incoming credential."); + { + Env env{*this, features | featureDisallowIncomingCredential}; + env.fund(XRP(5000), subject, issuer); + env.close(); + + env(fset(subject, asfDisallowIncomingCredential, 1)); + env.close(); + auto const sle = env.le(subject); + uint32_t flags = sle->getFlags(); + BEAST_EXPECT((flags & lsfDisallowIncomingCredential)); + + auto const jv = credentials::create(subject, issuer, credType); + env(jv, ter(tecNO_PERMISSION)); + env.close(); + + // allow self-issued credential + auto const jv2 = + credentials::create(subject, subject, credType); + env(jv2, ter(tesSUCCESS)); + env.close(); + + // clear flag + env(fset(subject, 0, asfDisallowIncomingCredential)); + env.close(); + auto const sle2 = env.le(subject); + uint32_t flags2 = sle2->getFlags(); + BEAST_EXPECT(!(flags2 & lsfDisallowIncomingCredential)); + } + } } void diff --git a/src/test/rpc/AccountSet_test.cpp b/src/test/rpc/AccountSet_test.cpp index 52dc331a2b1..98a18afa607 100644 --- a/src/test/rpc/AccountSet_test.cpp +++ b/src/test/rpc/AccountSet_test.cpp @@ -89,7 +89,8 @@ class AccountSet_test : public beast::unit_test::suite if (flag == asfDisallowIncomingCheck || flag == asfDisallowIncomingPayChan || flag == asfDisallowIncomingNFTokenOffer || - flag == asfDisallowIncomingTrustline) + flag == asfDisallowIncomingTrustline || + flag == asfDisallowIncomingCredential) { // These flags are part of the DisallowIncoming amendment // and are tested elsewhere diff --git a/src/xrpld/app/tx/detail/Credentials.cpp b/src/xrpld/app/tx/detail/Credentials.cpp index 4b77163c5db..ca8e8fd667b 100644 --- a/src/xrpld/app/tx/detail/Credentials.cpp +++ b/src/xrpld/app/tx/detail/Credentials.cpp @@ -91,7 +91,9 @@ CredentialCreate::preclaim(PreclaimContext const& ctx) auto const credType(ctx.tx[sfCredentialType]); auto const subject = ctx.tx[sfSubject]; - if (!ctx.view.exists(keylet::account(subject))) + auto const sleSubject = ctx.view.read(keylet::account(subject)); + + if (!sleSubject) { JLOG(ctx.j.trace()) << "Subject doesn't exist."; return tecNO_TARGET; @@ -104,6 +106,16 @@ CredentialCreate::preclaim(PreclaimContext const& ctx) return tecDUPLICATE; } + if (ctx.view.rules().enabled(featureDisallowIncoming) && + ctx.view.rules().enabled(featureDisallowIncomingCredential)) + { + auto const id = ctx.tx[sfAccount]; + if (id != subject && sleSubject->isFlag(lsfDisallowIncomingCredential)) + { + return tecNO_PERMISSION; + } + } + return tesSUCCESS; } diff --git a/src/xrpld/app/tx/detail/SetAccount.cpp b/src/xrpld/app/tx/detail/SetAccount.cpp index c2129ba1e11..04d3cc3353f 100644 --- a/src/xrpld/app/tx/detail/SetAccount.cpp +++ b/src/xrpld/app/tx/detail/SetAccount.cpp @@ -645,6 +645,14 @@ SetAccount::doApply() uFlagsOut |= lsfDisallowIncomingTrustline; else if (uClearFlag == asfDisallowIncomingTrustline) uFlagsOut &= ~lsfDisallowIncomingTrustline; + + if (ctx_.view().rules().enabled(featureDisallowIncomingCredential)) + { + if (uSetFlag == asfDisallowIncomingCredential) + uFlagsOut |= lsfDisallowIncomingCredential; + else if (uClearFlag == asfDisallowIncomingCredential) + uFlagsOut &= ~lsfDisallowIncomingCredential; + } } // Set or clear flags for disallowing escrow