diff --git a/CMakeLists.txt b/CMakeLists.txt index 44416808..fdb403e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,6 +158,7 @@ if(tests) unittests/rpc/APIVersionTests.cpp unittests/rpc/ForwardingProxyTests.cpp unittests/rpc/WorkQueueTest.cpp + unittests/rpc/AmendmentsTest.cpp ## RPC handlers unittests/rpc/handlers/DefaultProcessorTests.cpp unittests/rpc/handlers/TestHandlerTests.cpp diff --git a/src/rpc/Amendments.h b/src/rpc/Amendments.h new file mode 100644 index 00000000..c643a7e0 --- /dev/null +++ b/src/rpc/Amendments.h @@ -0,0 +1,41 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2023, the clio developers. + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#pragma once + +#include + +#include + +namespace RPC { + +#define REGISTER_AMENDMENT(name) inline static const ripple::uint256 name = GetAmendmentId(#name); + +struct Amendments +{ + static ripple::uint256 const + GetAmendmentId(std::string_view const name) + { + return ripple::sha512Half(ripple::Slice(name.data(), name.size())); + } + + REGISTER_AMENDMENT(DisallowIncoming) + REGISTER_AMENDMENT(Clawback) +}; +} // namespace RPC diff --git a/src/rpc/RPCHelpers.cpp b/src/rpc/RPCHelpers.cpp index ab22fbf8..cd1d4519 100644 --- a/src/rpc/RPCHelpers.cpp +++ b/src/rpc/RPCHelpers.cpp @@ -1325,4 +1325,21 @@ getNFTID(boost::json::object const& request) return tokenid; } +bool +isAmendmentEnabled( + std::shared_ptr const& backend, + boost::asio::yield_context yield, + uint32_t seq, + ripple::uint256 amendmentId) +{ + // the amendments should always be present in ledger + auto const& amendments = backend->fetchLedgerObject(ripple::keylet::amendments().key, seq, yield); + + ripple::SLE amendmentsSLE{ + ripple::SerialIter{amendments->data(), amendments->size()}, ripple::keylet::amendments().key}; + + auto const listAmendments = amendmentsSLE.getFieldV256(ripple::sfAmendments); + return std::find(listAmendments.begin(), listAmendments.end(), amendmentId) != listAmendments.end(); +} + } // namespace RPC diff --git a/src/rpc/RPCHelpers.h b/src/rpc/RPCHelpers.h index 1eca4f49..cdcb977e 100644 --- a/src/rpc/RPCHelpers.h +++ b/src/rpc/RPCHelpers.h @@ -24,6 +24,7 @@ */ #include +#include #include #include #include @@ -216,6 +217,13 @@ specifiesCurrentOrClosedLedger(boost::json::object const& request); std::variant getNFTID(boost::json::object const& request); +bool +isAmendmentEnabled( + std::shared_ptr const& backend, + boost::asio::yield_context yield, + uint32_t seq, + ripple::uint256 amendmentId); + template void logDuration(Web::Context const& ctx, T const& dur) diff --git a/src/rpc/handlers/AccountInfo.cpp b/src/rpc/handlers/AccountInfo.cpp index 2d073b39..5344bb68 100644 --- a/src/rpc/handlers/AccountInfo.cpp +++ b/src/rpc/handlers/AccountInfo.cpp @@ -50,6 +50,12 @@ AccountInfoHandler::process(AccountInfoHandler::Input input, Context const& ctx) if (!accountKeylet.check(sle)) return Error{Status{RippledError::rpcDB_DESERIALIZATION}}; + auto const isDisallowIncomingEnabled = + RPC::isAmendmentEnabled(sharedPtrBackend_, ctx.yield, lgrInfo.seq, RPC::Amendments::DisallowIncoming); + + auto const isClawbackEnabled = + RPC::isAmendmentEnabled(sharedPtrBackend_, ctx.yield, lgrInfo.seq, RPC::Amendments::Clawback); + // Return SignerList(s) if that is requested. if (input.signerLists) { @@ -73,10 +79,11 @@ AccountInfoHandler::process(AccountInfoHandler::Input input, Context const& ctx) signerList.push_back(sleSigners); } - return Output(lgrInfo.seq, ripple::strHex(lgrInfo.hash), sle, signerList); + return Output( + lgrInfo.seq, ripple::strHex(lgrInfo.hash), sle, isDisallowIncomingEnabled, isClawbackEnabled, signerList); } - return Output(lgrInfo.seq, ripple::strHex(lgrInfo.hash), sle); + return Output(lgrInfo.seq, ripple::strHex(lgrInfo.hash), sle, isDisallowIncomingEnabled, isClawbackEnabled); } void @@ -89,7 +96,7 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountInfoHandl {JS(validated), output.validated}, }; - static constexpr std::array, 9> lsFlags{{ + std::vector> lsFlags{{ {"defaultRipple", ripple::lsfDefaultRipple}, {"depositAuth", ripple::lsfDepositAuth}, {"disableMasterKey", ripple::lsfDisableMaster}, @@ -98,14 +105,25 @@ tag_invoke(boost::json::value_from_tag, boost::json::value& jv, AccountInfoHandl {"noFreeze", ripple::lsfNoFreeze}, {"passwordSpent", ripple::lsfPasswordSpent}, {"requireAuthorization", ripple::lsfRequireAuth}, - {"requireDestinationTag", ripple::lsfRequireDestTag} - // TODO: wait for conan integration - // {"disallowIncomingNFTokenOffer", ripple::lsfDisallowIncomingNFTokenOffer}, - // {"disallowIncomingCheck", ripple::lsfDisallowIncomingCheck}, - // {"disallowIncomingPayChan", ripple::lsfDisallowIncomingPayChan}, - // {"disallowIncomingTrustline", ripple::lsfDisallowIncomingTrustline} + {"requireDestinationTag", ripple::lsfRequireDestTag}, }}; + if (output.isDisallowIncomingEnabled) + { + std::vector> const disallowIncomingFlags = { + {"disallowIncomingNFTokenOffer", ripple::lsfDisallowIncomingNFTokenOffer}, + {"disallowIncomingCheck", ripple::lsfDisallowIncomingCheck}, + {"disallowIncomingPayChan", ripple::lsfDisallowIncomingPayChan}, + {"disallowIncomingTrustline", ripple::lsfDisallowIncomingTrustline}, + }; + lsFlags.insert(lsFlags.end(), disallowIncomingFlags.begin(), disallowIncomingFlags.end()); + } + + if (output.isClawbackEnabled) + { + lsFlags.push_back({"allowTrustLineClawback", ripple::lsfAllowTrustLineClawback}); + } + boost::json::object acctFlags; for (auto const& lsf : lsFlags) acctFlags[lsf.first.data()] = output.accountData.isFlag(lsf.second); diff --git a/src/rpc/handlers/AccountInfo.h b/src/rpc/handlers/AccountInfo.h index 6749c466..dcec5006 100644 --- a/src/rpc/handlers/AccountInfo.h +++ b/src/rpc/handlers/AccountInfo.h @@ -42,6 +42,8 @@ public: uint32_t ledgerIndex; std::string ledgerHash; ripple::STLedgerEntry accountData; + bool isDisallowIncomingEnabled = false; + bool isClawbackEnabled = false; std::optional> signerLists; // validated should be sent via framework bool validated = true; @@ -50,18 +52,17 @@ public: uint32_t ledgerId, std::string ledgerHash, ripple::STLedgerEntry sle, - std::vector signerLists) + bool isDisallowIncomingEnabled, + bool isClawbackEnabled, + std::optional> signerLists = std::nullopt) : ledgerIndex(ledgerId) , ledgerHash(std::move(ledgerHash)) , accountData(std::move(sle)) + , isDisallowIncomingEnabled(isDisallowIncomingEnabled) + , isClawbackEnabled(isClawbackEnabled) , signerLists(std::move(signerLists)) { } - - Output(uint32_t ledgerId, std::string ledgerHash, ripple::STLedgerEntry sle) - : ledgerIndex(ledgerId), ledgerHash(std::move(ledgerHash)), accountData(std::move(sle)) - { - } }; // "queue" is not available in Reporting mode diff --git a/unittests/rpc/AmendmentsTest.cpp b/unittests/rpc/AmendmentsTest.cpp new file mode 100644 index 00000000..0d9f212e --- /dev/null +++ b/unittests/rpc/AmendmentsTest.cpp @@ -0,0 +1,32 @@ +//------------------------------------------------------------------------------ +/* + This file is part of clio: https://github.com/XRPLF/clio + Copyright (c) 2023, the clio developers. + + Permission to use, copy, modify, and distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include + +using namespace RPC; + +TEST(RPCAmendmentsTest, GenerateAmendmentId) +{ + // https://xrpl.org/known-amendments.html#disallowincoming refer to the published id + EXPECT_EQ( + ripple::uint256("47C3002ABA31628447E8E9A8B315FAA935CE30183F9A9B86845E469CA2CDC3DF"), + Amendments::GetAmendmentId("DisallowIncoming")); +} diff --git a/unittests/rpc/handlers/AccountInfoTest.cpp b/unittests/rpc/handlers/AccountInfoTest.cpp index a8de8e30..31f1d74d 100644 --- a/unittests/rpc/handlers/AccountInfoTest.cpp +++ b/unittests/rpc/handlers/AccountInfoTest.cpp @@ -22,6 +22,7 @@ #include #include +#include #include using namespace RPC; @@ -126,8 +127,8 @@ TEST_F(RPCAccountInfoHandlerTest, LedgerNonExistViaIntSequence) auto const static input = boost::json::parse(fmt::format( R"({{ - "account":"{}", - "ledger_index":30 + "account": "{}", + "ledger_index": 30 }})", ACCOUNT)); auto const handler = AnyHandler{AccountInfoHandler{mockBackendPtr}}; @@ -151,8 +152,8 @@ TEST_F(RPCAccountInfoHandlerTest, LedgerNonExistViaStringSequence) auto const static input = boost::json::parse(fmt::format( R"({{ - "account":"{}", - "ledger_index":"30" + "account": "{}", + "ledger_index": "30" }})", ACCOUNT)); auto const handler = AnyHandler{AccountInfoHandler{mockBackendPtr}}; @@ -177,8 +178,8 @@ TEST_F(RPCAccountInfoHandlerTest, LedgerNonExistViaHash) auto const static input = boost::json::parse(fmt::format( R"({{ - "account":"{}", - "ledger_hash":"{}" + "account": "{}", + "ledger_hash": "{}" }})", ACCOUNT, LEDGERHASH)); @@ -206,7 +207,7 @@ TEST_F(RPCAccountInfoHandlerTest, AccountNotExist) auto const static input = boost::json::parse(fmt::format( R"({{ - "account":"{}" + "account": "{}" }})", ACCOUNT)); auto const handler = AnyHandler{AccountInfoHandler{mockBackendPtr}}; @@ -234,7 +235,7 @@ TEST_F(RPCAccountInfoHandlerTest, AccountInvalid) auto const static input = boost::json::parse(fmt::format( R"({{ - "account":"{}" + "account": "{}" }})", ACCOUNT)); auto const handler = AnyHandler{AccountInfoHandler{mockBackendPtr}}; @@ -264,12 +265,14 @@ TEST_F(RPCAccountInfoHandlerTest, SignerListsInvalid) auto signersKey = ripple::keylet::signers(account).key; ON_CALL(*rawBackendPtr, doFetchLedgerObject(signersKey, 30, _)) .WillByDefault(Return(CreateFeeSettingBlob(1, 2, 3, 4, 0))); - EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2); + ON_CALL(*rawBackendPtr, doFetchLedgerObject(ripple::keylet::amendments().key, 30, _)) + .WillByDefault(Return(CreateAmendmentsObject({}).getSerializer().peekData())); + EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(4); auto const static input = boost::json::parse(fmt::format( R"({{ - "account":"{}", - "signer_lists":true + "account": "{}", + "signer_lists": true }})", ACCOUNT)); auto const handler = AnyHandler{AccountInfoHandler{mockBackendPtr}}; @@ -286,46 +289,46 @@ TEST_F(RPCAccountInfoHandlerTest, SignerListsTrue) { auto const expectedOutput = fmt::format( R"({{ - "account_data":{{ - "Account":"{}", - "Balance":"200", - "Flags":0, - "LedgerEntryType":"AccountRoot", - "OwnerCount":2, - "PreviousTxnID":"{}", - "PreviousTxnLgrSeq":2, - "Sequence":2, - "TransferRate":0, - "index":"13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8" + "account_data": {{ + "Account": "{}", + "Balance": "200", + "Flags": 0, + "LedgerEntryType": "AccountRoot", + "OwnerCount": 2, + "PreviousTxnID": "{}", + "PreviousTxnLgrSeq": 2, + "Sequence": 2, + "TransferRate": 0, + "index": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8" }}, "signer_lists": [ {{ - "Flags":0, - "LedgerEntryType":"SignerList", - "OwnerNode":"0", - "PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000", - "PreviousTxnLgrSeq":0, + "Flags": 0, + "LedgerEntryType": "SignerList", + "OwnerNode": "0", + "PreviousTxnID": "0000000000000000000000000000000000000000000000000000000000000000", + "PreviousTxnLgrSeq": 0, "SignerEntries": [ {{ "SignerEntry": {{ - "Account":"{}", - "SignerWeight":1 + "Account": "{}", + "SignerWeight": 1 }} }}, {{ "SignerEntry": {{ - "Account":"{}", - "SignerWeight":1 + "Account": "{}", + "SignerWeight": 1 }} }} ], - "SignerListID":0, - "SignerQuorum":2, - "index":"A9C28A28B85CD533217F5C0A0C7767666B093FA58A0F2D80026FCC4CD932DDC7" + "SignerListID": 0, + "SignerQuorum": 2, + "index": "A9C28A28B85CD533217F5C0A0C7767666B093FA58A0F2D80026FCC4CD932DDC7" }} ], "account_flags": {{ @@ -339,9 +342,9 @@ TEST_F(RPCAccountInfoHandlerTest, SignerListsTrue) "requireAuthorization": false, "requireDestinationTag": false }}, - "ledger_hash":"{}", - "ledger_index":30, - "validated":true + "ledger_hash": "{}", + "ledger_index": 30, + "validated": true }})", ACCOUNT, INDEX1, @@ -363,12 +366,14 @@ TEST_F(RPCAccountInfoHandlerTest, SignerListsTrue) auto signersKey = ripple::keylet::signers(account).key; ON_CALL(*rawBackendPtr, doFetchLedgerObject(signersKey, 30, _)) .WillByDefault(Return(CreateSignerLists({{ACCOUNT1, 1}, {ACCOUNT2, 1}}).getSerializer().peekData())); - EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2); + ON_CALL(*rawBackendPtr, doFetchLedgerObject(ripple::keylet::amendments().key, 30, _)) + .WillByDefault(Return(CreateAmendmentsObject({}).getSerializer().peekData())); + EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(4); auto const static input = boost::json::parse(fmt::format( R"({{ - "account":"{}", - "signer_lists":true + "account": "{}", + "signer_lists": true }})", ACCOUNT)); auto const handler = AnyHandler{AccountInfoHandler{mockBackendPtr}}; @@ -383,17 +388,17 @@ TEST_F(RPCAccountInfoHandlerTest, Flags) { auto const expectedOutput = fmt::format( R"({{ - "account_data":{{ - "Account":"{}", - "Balance":"200", - "Flags":33488896, - "LedgerEntryType":"AccountRoot", - "OwnerCount":2, - "PreviousTxnID":"{}", - "PreviousTxnLgrSeq":2, - "Sequence":2, - "TransferRate":0, - "index":"13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8" + "account_data": {{ + "Account": "{}", + "Balance": "200", + "Flags": 33488896, + "LedgerEntryType": "AccountRoot", + "OwnerCount": 2, + "PreviousTxnID": "{}", + "PreviousTxnLgrSeq": 2, + "Sequence": 2, + "TransferRate": 0, + "index": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8" }}, "account_flags": {{ "defaultRipple": true, @@ -406,9 +411,9 @@ TEST_F(RPCAccountInfoHandlerTest, Flags) "requireAuthorization": true, "requireDestinationTag": true }}, - "ledger_hash":"{}", - "ledger_index":30, - "validated":true + "ledger_hash": "{}", + "ledger_index": 30, + "validated": true }})", ACCOUNT, INDEX1, @@ -434,11 +439,13 @@ TEST_F(RPCAccountInfoHandlerTest, Flags) 2); ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, 30, _)) .WillByDefault(Return(accountRoot.getSerializer().peekData())); - EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1); + ON_CALL(*rawBackendPtr, doFetchLedgerObject(ripple::keylet::amendments().key, 30, _)) + .WillByDefault(Return(CreateAmendmentsObject({}).getSerializer().peekData())); + EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3); auto const static input = boost::json::parse(fmt::format( R"({{ - "account":"{}" + "account": "{}" }})", ACCOUNT)); auto const handler = AnyHandler{AccountInfoHandler{mockBackendPtr}}; @@ -463,11 +470,13 @@ TEST_F(RPCAccountInfoHandlerTest, IdentAndSignerListsFalse) auto const accountRoot = CreateAccountRootObject(ACCOUNT, 0, 2, 200, 2, INDEX1, 2); ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, 30, _)) .WillByDefault(Return(accountRoot.getSerializer().peekData())); - EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1); + ON_CALL(*rawBackendPtr, doFetchLedgerObject(ripple::keylet::amendments().key, 30, _)) + .WillByDefault(Return(CreateAmendmentsObject({}).getSerializer().peekData())); + EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3); auto const static input = boost::json::parse(fmt::format( R"({{ - "ident":"{}" + "ident": "{}" }})", ACCOUNT)); auto const handler = AnyHandler{AccountInfoHandler{mockBackendPtr}}; @@ -477,3 +486,153 @@ TEST_F(RPCAccountInfoHandlerTest, IdentAndSignerListsFalse) EXPECT_FALSE(output->as_object().contains("signer_lists")); }); } + +TEST_F(RPCAccountInfoHandlerTest, DisallowIncoming) +{ + auto const expectedOutput = fmt::format( + R"({{ + "account_data": {{ + "Account": "{}", + "Balance": "200", + "Flags": 1040121856, + "LedgerEntryType": "AccountRoot", + "OwnerCount": 2, + "PreviousTxnID": "{}", + "PreviousTxnLgrSeq": 2, + "Sequence": 2, + "TransferRate": 0, + "index": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8" + }}, + "account_flags": {{ + "defaultRipple": true, + "depositAuth": true, + "disableMasterKey": true, + "disallowIncomingXRP": true, + "globalFreeze": true, + "noFreeze": true, + "passwordSpent": true, + "requireAuthorization": true, + "requireDestinationTag": true, + "disallowIncomingCheck": true, + "disallowIncomingNFTokenOffer": true, + "disallowIncomingPayChan": true, + "disallowIncomingTrustline": true + }}, + "ledger_hash": "{}", + "ledger_index": 30, + "validated": true + }})", + ACCOUNT, + INDEX1, + LEDGERHASH); + auto const rawBackendPtr = static_cast(mockBackendPtr.get()); + mockBackendPtr->updateRange(10); // min + mockBackendPtr->updateRange(30); // max + auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30); + EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1); + ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo)); + + auto const account = GetAccountIDWithString(ACCOUNT); + auto const accountKk = ripple::keylet::account(account).key; + auto const accountRoot = CreateAccountRootObject( + ACCOUNT, + ripple::lsfDefaultRipple | ripple::lsfGlobalFreeze | ripple::lsfRequireDestTag | ripple::lsfRequireAuth | + ripple::lsfDepositAuth | ripple::lsfDisableMaster | ripple::lsfDisallowXRP | ripple::lsfNoFreeze | + ripple::lsfPasswordSpent | ripple::lsfDisallowIncomingNFTokenOffer | ripple::lsfDisallowIncomingCheck | + ripple::lsfDisallowIncomingPayChan | ripple::lsfDisallowIncomingTrustline, + 2, + 200, + 2, + INDEX1, + 2); + ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, 30, _)) + .WillByDefault(Return(accountRoot.getSerializer().peekData())); + ON_CALL(*rawBackendPtr, doFetchLedgerObject(ripple::keylet::amendments().key, 30, _)) + .WillByDefault(Return(CreateAmendmentsObject({RPC::Amendments::DisallowIncoming}).getSerializer().peekData())); + EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3); + + auto const static input = boost::json::parse(fmt::format( + R"({{ + "account": "{}" + }})", + ACCOUNT)); + auto const handler = AnyHandler{AccountInfoHandler{mockBackendPtr}}; + runSpawn([&](auto yield) { + auto const output = handler.process(input, Context{yield}); + ASSERT_TRUE(output); + EXPECT_EQ(*output, json::parse(expectedOutput)); + }); +} + +TEST_F(RPCAccountInfoHandlerTest, Clawback) +{ + auto const expectedOutput = fmt::format( + R"({{ + "account_data": {{ + "Account": "{}", + "Balance": "200", + "Flags": 2180972544, + "LedgerEntryType": "AccountRoot", + "OwnerCount": 2, + "PreviousTxnID": "{}", + "PreviousTxnLgrSeq": 2, + "Sequence": 2, + "TransferRate": 0, + "index": "13F1A95D7AAB7108D5CE7EEAF504B2894B8C674E6D68499076441C4837282BF8" + }}, + "account_flags": {{ + "defaultRipple": true, + "depositAuth": true, + "disableMasterKey": true, + "disallowIncomingXRP": true, + "globalFreeze": true, + "noFreeze": true, + "passwordSpent": true, + "requireAuthorization": true, + "requireDestinationTag": true, + "allowTrustLineClawback": true + }}, + "ledger_hash": "{}", + "ledger_index": 30, + "validated": true + }})", + ACCOUNT, + INDEX1, + LEDGERHASH); + auto const rawBackendPtr = static_cast(mockBackendPtr.get()); + mockBackendPtr->updateRange(10); // min + mockBackendPtr->updateRange(30); // max + auto const ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30); + EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1); + ON_CALL(*rawBackendPtr, fetchLedgerBySequence).WillByDefault(Return(ledgerinfo)); + + auto const account = GetAccountIDWithString(ACCOUNT); + auto const accountKk = ripple::keylet::account(account).key; + auto const accountRoot = CreateAccountRootObject( + ACCOUNT, + ripple::lsfDefaultRipple | ripple::lsfGlobalFreeze | ripple::lsfRequireDestTag | ripple::lsfRequireAuth | + ripple::lsfDepositAuth | ripple::lsfDisableMaster | ripple::lsfDisallowXRP | ripple::lsfNoFreeze | + ripple::lsfPasswordSpent | ripple::lsfAllowTrustLineClawback, + 2, + 200, + 2, + INDEX1, + 2); + ON_CALL(*rawBackendPtr, doFetchLedgerObject(accountKk, 30, _)) + .WillByDefault(Return(accountRoot.getSerializer().peekData())); + ON_CALL(*rawBackendPtr, doFetchLedgerObject(ripple::keylet::amendments().key, 30, _)) + .WillByDefault(Return(CreateAmendmentsObject({RPC::Amendments::Clawback}).getSerializer().peekData())); + EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3); + + auto const static input = boost::json::parse(fmt::format( + R"({{ + "account": "{}" + }})", + ACCOUNT)); + auto const handler = AnyHandler{AccountInfoHandler{mockBackendPtr}}; + runSpawn([&](auto yield) { + auto const output = handler.process(input, Context{yield}); + ASSERT_TRUE(output); + EXPECT_EQ(*output, json::parse(expectedOutput)); + }); +} diff --git a/unittests/util/TestObject.cpp b/unittests/util/TestObject.cpp index 5d8484ff..eb46c459 100644 --- a/unittests/util/TestObject.cpp +++ b/unittests/util/TestObject.cpp @@ -729,3 +729,14 @@ CreateCreateNFTOfferTxWithMetadata( ret.metadata = metaObj.getSerializer().peekData(); return ret; } + +ripple::STObject +CreateAmendmentsObject(std::vector const& enabledAmendments) +{ + auto amendments = ripple::STObject(ripple::sfLedgerEntry); + amendments.setFieldU16(ripple::sfLedgerEntryType, ripple::ltAMENDMENTS); + amendments.setFieldU32(ripple::sfFlags, 0); + ripple::STVector256 list(enabledAmendments); + amendments.setFieldV256(ripple::sfAmendments, list); + return amendments; +} diff --git a/unittests/util/TestObject.h b/unittests/util/TestObject.h index 9f171618..5dde9619 100644 --- a/unittests/util/TestObject.h +++ b/unittests/util/TestObject.h @@ -258,3 +258,6 @@ CreateCreateNFTOfferTxWithMetadata( std::string_view nftId, std::uint32_t offerPrice, std::string_view offerId); + +[[nodiscard]] ripple::STObject +CreateAmendmentsObject(std::vector const& enabledAmendments);