diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index 1c5bf8463b..3932349c82 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -114,6 +114,7 @@ JSS(account); // in/out: many JSS(accountState); // out: LedgerToJson JSS(accountTreeHash); // out: ledger/Ledger.cpp JSS(account_data); // out: AccountInfo +JSS(account_flags); // out: AccountInfo JSS(account_hash); // out: LedgerToJson JSS(account_id); // out: WalletPropose JSS(account_nfts); // out: AccountNFTs diff --git a/src/ripple/rpc/handlers/AccountInfo.cpp b/src/ripple/rpc/handlers/AccountInfo.cpp index f08805761e..ef130ef185 100644 --- a/src/ripple/rpc/handlers/AccountInfo.cpp +++ b/src/ripple/rpc/handlers/AccountInfo.cpp @@ -77,6 +77,28 @@ doAccountInfo(RPC::JsonContext& context) if (jvAccepted) return jvAccepted; + static constexpr std:: + array, 9> + lsFlags{ + {{"defaultRipple", lsfDefaultRipple}, + {"depositAuth", lsfDepositAuth}, + {"disableMasterKey", lsfDisableMaster}, + {"disallowIncomingXRP", lsfDisallowXRP}, + {"globalFreeze", lsfGlobalFreeze}, + {"noFreeze", lsfNoFreeze}, + {"passwordSpent", lsfPasswordSpent}, + {"requireAuthorization", lsfRequireAuth}, + {"requireDestinationTag", lsfRequireDestTag}}}; + + static constexpr std:: + array, 4> + disallowIncomingFlags{ + {{"disallowIncomingNFTokenOffer", + lsfDisallowIncomingNFTokenOffer}, + {"disallowIncomingCheck", lsfDisallowIncomingCheck}, + {"disallowIncomingPayChan", lsfDisallowIncomingPayChan}, + {"disallowIncomingTrustline", lsfDisallowIncomingTrustline}}}; + auto const sleAccepted = ledger->read(keylet::account(accountID)); if (sleAccepted) { @@ -94,6 +116,17 @@ doAccountInfo(RPC::JsonContext& context) RPC::injectSLE(jvAccepted, *sleAccepted); result[jss::account_data] = jvAccepted; + Json::Value acctFlags{Json::objectValue}; + for (auto const& lsf : lsFlags) + acctFlags[lsf.first.data()] = sleAccepted->isFlag(lsf.second); + + if (ledger->rules().enabled(featureDisallowIncoming)) + { + for (auto const& lsf : disallowIncomingFlags) + acctFlags[lsf.first.data()] = sleAccepted->isFlag(lsf.second); + } + result[jss::account_flags] = std::move(acctFlags); + // Return SignerList(s) if that is requested. if (params.isMember(jss::signer_lists) && params[jss::signer_lists].asBool()) diff --git a/src/test/rpc/AccountInfo_test.cpp b/src/test/rpc/AccountInfo_test.cpp index 9772a0ffb7..0cda0632ed 100644 --- a/src/test/rpc/AccountInfo_test.cpp +++ b/src/test/rpc/AccountInfo_test.cpp @@ -46,7 +46,7 @@ public: "Missing field 'account'."); } { - // account_info with a malformed account sting. + // account_info with a malformed account string. auto const info = env.rpc( "json", "account_info", @@ -491,6 +491,100 @@ public: } } + void + testAccountFlags(FeatureBitset const& features) + { + using namespace jtx; + + Env env(*this, features); + Account const alice{"alice"}; + env.fund(XRP(1000), alice); + + auto getAccountFlag = [&env, &alice](std::string_view fName) { + auto const info = env.rpc( + "json", + "account_info", + R"({"account" : ")" + alice.human() + R"("})"); + + std::optional res; + if (info[jss::result][jss::status] == "success" && + info[jss::result][jss::account_flags].isMember(fName.data())) + res.emplace(info[jss::result][jss::account_flags][fName.data()] + .asBool()); + + return res; + }; + + static constexpr std:: + array, 7> + asFlags{ + {{"defaultRipple", asfDefaultRipple}, + {"depositAuth", asfDepositAuth}, + {"disallowIncomingXRP", asfDisallowXRP}, + {"globalFreeze", asfGlobalFreeze}, + {"noFreeze", asfNoFreeze}, + {"requireAuthorization", asfRequireAuth}, + {"requireDestinationTag", asfRequireDest}}}; + + for (auto& asf : asFlags) + { + // Clear a flag and check that account_info returns results + // as expected + env(fclear(alice, asf.second)); + env.close(); + auto const f1 = getAccountFlag(asf.first); + BEAST_EXPECT(f1.has_value()); + BEAST_EXPECT(!f1.value()); + + // Set a flag and check that account_info returns results + // as expected + env(fset(alice, asf.second)); + env.close(); + auto const f2 = getAccountFlag(asf.first); + BEAST_EXPECT(f2.has_value()); + BEAST_EXPECT(f2.value()); + } + + static constexpr std:: + array, 4> + disallowIncomingFlags{ + {{"disallowIncomingCheck", asfDisallowIncomingCheck}, + {"disallowIncomingNFTokenOffer", + asfDisallowIncomingNFTokenOffer}, + {"disallowIncomingPayChan", asfDisallowIncomingPayChan}, + {"disallowIncomingTrustline", + asfDisallowIncomingTrustline}}}; + + if (features[featureDisallowIncoming]) + { + for (auto& asf : disallowIncomingFlags) + { + // Clear a flag and check that account_info returns results + // as expected + env(fclear(alice, asf.second)); + env.close(); + auto const f1 = getAccountFlag(asf.first); + BEAST_EXPECT(f1.has_value()); + BEAST_EXPECT(!f1.value()); + + // Set a flag and check that account_info returns results + // as expected + env(fset(alice, asf.second)); + env.close(); + auto const f2 = getAccountFlag(asf.first); + BEAST_EXPECT(f2.has_value()); + BEAST_EXPECT(f2.value()); + } + } + else + { + for (auto& asf : disallowIncomingFlags) + { + BEAST_EXPECT(!getAccountFlag(asf.first)); + } + } + } + void run() override { @@ -498,6 +592,11 @@ public: testSignerLists(); testSignerListsApiVersion2(); testSignerListsV2(); + + FeatureBitset const allFeatures{ + ripple::test::jtx::supported_amendments()}; + testAccountFlags(allFeatures); + testAccountFlags(allFeatures - featureDisallowIncoming); } };