diff --git a/CMakeLists.txt b/CMakeLists.txt index 141890b1..c52f8815 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,7 @@ target_sources(clio PRIVATE src/rpc/handlers/AccountTx.cpp src/rpc/handlers/BookChanges.cpp src/rpc/handlers/BookOffers.cpp + src/rpc/handlers/DepositAuthorized.cpp src/rpc/handlers/GatewayBalances.cpp src/rpc/handlers/Ledger.cpp src/rpc/handlers/LedgerData.cpp @@ -140,6 +141,7 @@ if(BUILD_TESTS) unittests/rpc/handlers/AccountChannelsTest.cpp unittests/rpc/handlers/AccountNFTsTest.cpp unittests/rpc/handlers/BookOffersTest.cpp + unittests/rpc/handlers/DepositAuthorizedTest.cpp unittests/rpc/handlers/GatewayBalancesTest.cpp unittests/rpc/handlers/TxTest.cpp unittests/rpc/handlers/TransactionEntryTest.cpp diff --git a/src/rpc/common/impl/HandlerProvider.cpp b/src/rpc/common/impl/HandlerProvider.cpp index 7b76451d..7aee94b4 100644 --- a/src/rpc/common/impl/HandlerProvider.cpp +++ b/src/rpc/common/impl/HandlerProvider.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -70,6 +71,7 @@ ProductionHandlerProvider::ProductionHandlerProvider( {"account_tx", {AccountTxHandler{backend}}}, {"book_changes", {BookChangesHandler{backend}}}, {"book_offers", {BookOffersHandler{backend}}}, + {"deposit_authorized", {DepositAuthorizedHandler{backend}}}, {"gateway_balances", {GatewayBalancesHandler{backend}}}, {"ledger", {LedgerHandler{backend}}}, {"ledger_data", {LedgerDataHandler{backend}}}, diff --git a/src/rpc/handlers/DepositAuthorized.cpp b/src/rpc/handlers/DepositAuthorized.cpp new file mode 100644 index 00000000..95309a0b --- /dev/null +++ b/src/rpc/handlers/DepositAuthorized.cpp @@ -0,0 +1,115 @@ +//------------------------------------------------------------------------------ +/* + 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 + +namespace RPC { + +DepositAuthorizedHandler::Result +DepositAuthorizedHandler::process(DepositAuthorizedHandler::Input input, Context const& ctx) const +{ + auto const range = sharedPtrBackend_->fetchLedgerRange(); + auto const lgrInfoOrStatus = getLedgerInfoFromHashOrSeq( + *sharedPtrBackend_, ctx.yield, input.ledgerHash, input.ledgerIndex, range->maxSequence); + + if (auto status = std::get_if(&lgrInfoOrStatus)) + return Error{*status}; + + auto const lgrInfo = std::get(lgrInfoOrStatus); + auto const sourceAccountID = accountFromStringStrict(input.sourceAccount); + auto const destinationAccountID = accountFromStringStrict(input.destinationAccount); + + auto const srcAccountLedgerObject = + sharedPtrBackend_->fetchLedgerObject(ripple::keylet::account(*sourceAccountID).key, lgrInfo.seq, ctx.yield); + + if (!srcAccountLedgerObject) + return Error{Status{RippledError::rpcSRC_ACT_NOT_FOUND, "source_accountNotFound"}}; + + auto const dstKeylet = ripple::keylet::account(*destinationAccountID).key; + auto const dstAccountLedgerObject = sharedPtrBackend_->fetchLedgerObject(dstKeylet, lgrInfo.seq, ctx.yield); + + if (!dstAccountLedgerObject) + return Error{Status{RippledError::rpcDST_ACT_NOT_FOUND, "destination_accountNotFound"}}; + + Output response; + + response.sourceAccount = input.sourceAccount; + response.destinationAccount = input.destinationAccount; + response.ledgerHash = ripple::strHex(lgrInfo.hash); + response.ledgerIndex = lgrInfo.seq; + + // If the two accounts are the same, then the deposit should be fine. + if (sourceAccountID != destinationAccountID) + { + auto it = ripple::SerialIter{dstAccountLedgerObject->data(), dstAccountLedgerObject->size()}; + auto sle = ripple::SLE{it, dstKeylet}; + + // Check destination for the DepositAuth flag. + // If that flag is not set then a deposit should be just fine. + if (sle.getFieldU32(ripple::sfFlags) & ripple::lsfDepositAuth) + { + // See if a preauthorization entry is in the ledger. + auto const depositPreauthKeylet = ripple::keylet::depositPreauth(*destinationAccountID, *sourceAccountID); + auto const sleDepositAuth = + sharedPtrBackend_->fetchLedgerObject(depositPreauthKeylet.key, lgrInfo.seq, ctx.yield); + response.depositAuthorized = static_cast(sleDepositAuth); + } + } + + return response; +} + +DepositAuthorizedHandler::Input +tag_invoke(boost::json::value_to_tag, boost::json::value const& jv) +{ + auto input = DepositAuthorizedHandler::Input{}; + auto const& jsonObject = jv.as_object(); + + input.sourceAccount = jv.at(JS(source_account)).as_string().c_str(); + input.destinationAccount = jv.at(JS(destination_account)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_hash))) + input.ledgerHash = jv.at(JS(ledger_hash)).as_string().c_str(); + + if (jsonObject.contains(JS(ledger_index))) + { + if (!jsonObject.at(JS(ledger_index)).is_string()) + input.ledgerIndex = jv.at(JS(ledger_index)).as_int64(); + else if (jsonObject.at(JS(ledger_index)).as_string() != "validated") + input.ledgerIndex = std::stoi(jv.at(JS(ledger_index)).as_string().c_str()); + } + + return input; +} + +void +tag_invoke(boost::json::value_from_tag, boost::json::value& jv, DepositAuthorizedHandler::Output const& output) +{ + jv = boost::json::object{ + {JS(deposit_authorized), output.depositAuthorized}, + {JS(source_account), output.sourceAccount}, + {JS(destination_account), output.destinationAccount}, + {JS(ledger_hash), output.ledgerHash}, + {JS(ledger_index), output.ledgerIndex}, + {JS(validated), output.validated}, + }; +} + +} // namespace RPC diff --git a/src/rpc/handlers/DepositAuthorized.h b/src/rpc/handlers/DepositAuthorized.h new file mode 100644 index 00000000..545e709a --- /dev/null +++ b/src/rpc/handlers/DepositAuthorized.h @@ -0,0 +1,93 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include +#include + +#include + +namespace RPC { + +/** + * @brief The deposit_authorized command indicates whether one account is authorized to send payments directly to + * another. See Deposit Authorization for information on how to require authorization to deliver money to your account. + * + * For more details see: https://xrpl.org/deposit_authorized.html + */ +class DepositAuthorizedHandler +{ + // dependencies + std::shared_ptr const sharedPtrBackend_; + +public: + // Note: `ledger_current_index` is omitted because it only makes sense for rippled + struct Output + { + bool depositAuthorized = true; + std::string sourceAccount; + std::string destinationAccount; + std::string ledgerHash; + uint32_t ledgerIndex; + // validated should be sent via framework + bool validated = true; + }; + + struct Input + { + std::string sourceAccount; + std::string destinationAccount; + std::optional ledgerHash; + std::optional ledgerIndex; + }; + + using Result = HandlerReturnType; + + DepositAuthorizedHandler(std::shared_ptr const& sharedPtrBackend) + : sharedPtrBackend_(sharedPtrBackend) + { + } + + RpcSpecConstRef + spec([[maybe_unused]] uint32_t apiVersion) const + { + static auto const rpcSpec = RpcSpec{ + {JS(source_account), validation::Required{}, validation::AccountValidator}, + {JS(destination_account), validation::Required{}, validation::AccountValidator}, + {JS(ledger_hash), validation::Uint256HexStringValidator}, + {JS(ledger_index), validation::LedgerIndexValidator}, + }; + + return rpcSpec; + } + + Result + process(Input input, Context const& ctx) const; + +private: + friend void + tag_invoke(boost::json::value_from_tag, boost::json::value& jv, Output const& output); + + friend Input + tag_invoke(boost::json::value_to_tag, boost::json::value const& jv); +}; +} // namespace RPC diff --git a/unittests/rpc/handlers/DepositAuthorizedTest.cpp b/unittests/rpc/handlers/DepositAuthorizedTest.cpp new file mode 100644 index 00000000..0a9b54ba --- /dev/null +++ b/unittests/rpc/handlers/DepositAuthorizedTest.cpp @@ -0,0 +1,542 @@ +//------------------------------------------------------------------------------ +/* + 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 +#include +#include + +#include + +constexpr static auto ACCOUNT = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"; +constexpr static auto ACCOUNT2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun"; +constexpr static auto LEDGERHASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652"; +constexpr static auto INDEX1 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC"; +constexpr static auto INDEX2 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515B1"; + +constexpr static auto RANGEMIN = 10; +constexpr static auto RANGEMAX = 30; + +using namespace RPC; +namespace json = boost::json; +using namespace testing; + +class RPCDepositAuthorizedTest : public HandlerBaseTest +{ +}; + +struct DepositAuthorizedTestCaseBundle +{ + std::string testName; + std::string testJson; + std::string expectedError; + std::string expectedErrorMessage; +}; + +// parameterized test cases for parameters check +struct DepositAuthorizedParameterTest : public RPCDepositAuthorizedTest, + public WithParamInterface +{ + struct NameGenerator + { + template + std::string + operator()(const testing::TestParamInfo& info) const + { + auto bundle = static_cast(info.param); + return bundle.testName; + } + }; +}; + +static auto +generateTestValuesForParametersTest() +{ + return std::vector{ + { + "SourceAccountMissing", + R"({ + "destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" + })", + "invalidParams", + "Required field 'source_account' missing", + }, + { + "SourceAccountMalformed", + R"({ + "source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jp", + "destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" + })", + "actMalformed", + "source_accountMalformed", + }, + { + "SourceAccountNotString", + R"({ + "source_account": 1234, + "destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" + })", + "invalidParams", + "source_accountNotString", + }, + { + "DestinationAccountMissing", + R"({ + "source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" + })", + "invalidParams", + "Required field 'destination_account' missing", + }, + { + "DestinationAccountMalformed", + R"({ + "source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jp", + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" + })", + "actMalformed", + "destination_accountMalformed", + }, + { + "DestinationAccountNotString", + R"({ + "source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "destination_account": 1234, + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652" + })", + "invalidParams", + "destination_accountNotString", + }, + { + "LedgerHashInvalid", + R"({ + "source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "ledger_hash": "x" + })", + "invalidParams", + "ledger_hashMalformed", + }, + { + "LedgerHashNotString", + R"({ + "source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "ledger_hash": 123 + })", + "invalidParams", + "ledger_hashNotString", + }, + { + "LedgerIndexNotInt", + R"({ + "source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "ledger_index": "x" + })", + "invalidParams", + "ledgerIndexMalformed", + }, + }; +} + +INSTANTIATE_TEST_CASE_P( + RPCDepositAuthorizedGroup, + DepositAuthorizedParameterTest, + ValuesIn(generateTestValuesForParametersTest()), + DepositAuthorizedParameterTest::NameGenerator{}); + +TEST_P(DepositAuthorizedParameterTest, InvalidParams) +{ + auto const testBundle = GetParam(); + runSpawn([&, this](auto& yield) { + auto const handler = AnyHandler{DepositAuthorizedHandler{mockBackendPtr}}; + auto const req = json::parse(testBundle.testJson); + auto const output = handler.process(req, Context{std::ref(yield)}); + + ASSERT_FALSE(output); + + auto const err = RPC::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), testBundle.expectedError); + EXPECT_EQ(err.at("error_message").as_string(), testBundle.expectedErrorMessage); + }); +} + +TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaIntSequence) +{ + auto const rawBackendPtr = static_cast(mockBackendPtr.get()); + mockBackendPtr->updateRange(RANGEMIN); // min + mockBackendPtr->updateRange(RANGEMAX); // max + + EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1); + ON_CALL(*rawBackendPtr, fetchLedgerBySequence(RANGEMAX, _)).WillByDefault(Return(std::nullopt)); + + runSpawn([&, this](auto& yield) { + auto const handler = AnyHandler{DepositAuthorizedHandler{mockBackendPtr}}; + auto const req = json::parse(fmt::format( + R"({{ + "source_account": "{}", + "destination_account": "{}", + "ledger_index": {} + }})", + ACCOUNT, + ACCOUNT2, + RANGEMAX)); + + auto const output = handler.process(req, Context{std::ref(yield)}); + ASSERT_FALSE(output); + + auto const err = RPC::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "lgrNotFound"); + EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound"); + }); +} + +TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaStringSequence) +{ + auto const rawBackendPtr = static_cast(mockBackendPtr.get()); + mockBackendPtr->updateRange(RANGEMIN); // min + mockBackendPtr->updateRange(RANGEMAX); // max + + EXPECT_CALL(*rawBackendPtr, fetchLedgerBySequence).Times(1); + ON_CALL(*rawBackendPtr, fetchLedgerBySequence(RANGEMAX, _)).WillByDefault(Return(std::nullopt)); + + runSpawn([&, this](auto& yield) { + auto const handler = AnyHandler{DepositAuthorizedHandler{mockBackendPtr}}; + auto const req = json::parse(fmt::format( + R"({{ + "source_account": "{}", + "destination_account": "{}", + "ledger_index": "{}" + }})", + ACCOUNT, + ACCOUNT2, + RANGEMAX)); + + auto const output = handler.process(req, Context{std::ref(yield)}); + ASSERT_FALSE(output); + + auto const err = RPC::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "lgrNotFound"); + EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound"); + }); +} + +TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaHash) +{ + auto const rawBackendPtr = static_cast(mockBackendPtr.get()); + mockBackendPtr->updateRange(RANGEMIN); // min + mockBackendPtr->updateRange(RANGEMAX); // max + + EXPECT_CALL(*rawBackendPtr, fetchLedgerByHash).Times(1); + ON_CALL(*rawBackendPtr, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(std::nullopt)); + + runSpawn([&, this](auto& yield) { + auto const handler = AnyHandler{DepositAuthorizedHandler{mockBackendPtr}}; + auto const req = json::parse(fmt::format( + R"({{ + "source_account": "{}", + "destination_account": "{}", + "ledger_hash": "{}" + }})", + ACCOUNT, + ACCOUNT2, + LEDGERHASH)); + + auto const output = handler.process(req, Context{std::ref(yield)}); + ASSERT_FALSE(output); + + auto const err = RPC::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "lgrNotFound"); + EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound"); + }); +} + +TEST_F(RPCDepositAuthorizedTest, SourceAccountDoesNotExist) +{ + auto const rawBackendPtr = static_cast(mockBackendPtr.get()); + mockBackendPtr->updateRange(10); // min + mockBackendPtr->updateRange(30); // max + + auto ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30); + + ON_CALL(*rawBackendPtr, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerinfo)); + EXPECT_CALL(*rawBackendPtr, fetchLedgerByHash).Times(1); + + ON_CALL(*rawBackendPtr, doFetchLedgerObject).WillByDefault(Return(std::optional{})); + EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(1); + + auto const input = json::parse(fmt::format( + R"({{ + "source_account": "{}", + "destination_account": "{}", + "ledger_hash": "{}" + }})", + ACCOUNT, + ACCOUNT2, + LEDGERHASH)); + + runSpawn([&, this](auto& yield) { + auto const handler = AnyHandler{DepositAuthorizedHandler{mockBackendPtr}}; + auto const output = handler.process(input, Context{std::ref(yield)}); + + ASSERT_FALSE(output); + + auto const err = RPC::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "srcActNotFound"); + EXPECT_EQ(err.at("error_message").as_string(), "source_accountNotFound"); + }); +} + +TEST_F(RPCDepositAuthorizedTest, DestinationAccountDoesNotExist) +{ + auto const rawBackendPtr = static_cast(mockBackendPtr.get()); + mockBackendPtr->updateRange(10); // min + mockBackendPtr->updateRange(30); // max + + auto ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30); + + ON_CALL(*rawBackendPtr, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerinfo)); + EXPECT_CALL(*rawBackendPtr, fetchLedgerByHash).Times(1); + + auto const accountRoot = CreateAccountRootObject(ACCOUNT, 0, 2, 200, 2, INDEX1, 2); + ON_CALL(*rawBackendPtr, doFetchLedgerObject(_, _, _)).WillByDefault(Return(accountRoot.getSerializer().peekData())); + ON_CALL(*rawBackendPtr, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT2)).key, _, _)) + .WillByDefault(Return(std::optional{})); + + EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2); + + auto const input = json::parse(fmt::format( + R"({{ + "source_account": "{}", + "destination_account": "{}", + "ledger_hash": "{}" + }})", + ACCOUNT, + ACCOUNT2, + LEDGERHASH)); + + runSpawn([&, this](auto& yield) { + auto const handler = AnyHandler{DepositAuthorizedHandler{mockBackendPtr}}; + auto const output = handler.process(input, Context{std::ref(yield)}); + + ASSERT_FALSE(output); + + auto const err = RPC::makeError(output.error()); + EXPECT_EQ(err.at("error").as_string(), "dstActNotFound"); + EXPECT_EQ(err.at("error_message").as_string(), "destination_accountNotFound"); + }); +} + +TEST_F(RPCDepositAuthorizedTest, AccountsAreEqual) +{ + static auto constexpr expectedOut = + R"({ + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, + "deposit_authorized": true, + "source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn" + })"; + + auto const rawBackendPtr = static_cast(mockBackendPtr.get()); + mockBackendPtr->updateRange(10); // min + mockBackendPtr->updateRange(30); // max + + auto ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30); + + ON_CALL(*rawBackendPtr, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerinfo)); + EXPECT_CALL(*rawBackendPtr, fetchLedgerByHash).Times(1); + + auto const accountRoot = CreateAccountRootObject(ACCOUNT, 0, 2, 200, 2, INDEX1, 2); + ON_CALL(*rawBackendPtr, doFetchLedgerObject).WillByDefault(Return(accountRoot.getSerializer().peekData())); + EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2); + + auto const input = json::parse(fmt::format( + R"({{ + "source_account": "{}", + "destination_account": "{}", + "ledger_hash": "{}" + }})", + ACCOUNT, + ACCOUNT, + LEDGERHASH)); + + runSpawn([&, this](auto& yield) { + auto const handler = AnyHandler{DepositAuthorizedHandler{mockBackendPtr}}; + auto const output = handler.process(input, Context{std::ref(yield)}); + + ASSERT_TRUE(output); + EXPECT_EQ(*output, json::parse(expectedOut)); + }); +} + +TEST_F(RPCDepositAuthorizedTest, DifferentAccountsNoDepositAuthFlag) +{ + static auto constexpr expectedOut = + R"({ + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, + "deposit_authorized": true, + "source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "destination_account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun" + })"; + + auto const rawBackendPtr = static_cast(mockBackendPtr.get()); + mockBackendPtr->updateRange(10); // min + mockBackendPtr->updateRange(30); // max + + auto ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30); + + ON_CALL(*rawBackendPtr, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerinfo)); + EXPECT_CALL(*rawBackendPtr, fetchLedgerByHash).Times(1); + + auto const account1Root = CreateAccountRootObject(ACCOUNT, 0, 2, 200, 2, INDEX1, 2); + auto const account2Root = CreateAccountRootObject(ACCOUNT2, 0, 2, 200, 2, INDEX2, 2); + + ON_CALL(*rawBackendPtr, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key, _, _)) + .WillByDefault(Return(account1Root.getSerializer().peekData())); + ON_CALL(*rawBackendPtr, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT2)).key, _, _)) + .WillByDefault(Return(account2Root.getSerializer().peekData())); + EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(2); + + auto const input = json::parse(fmt::format( + R"({{ + "source_account": "{}", + "destination_account": "{}", + "ledger_hash": "{}" + }})", + ACCOUNT, + ACCOUNT2, + LEDGERHASH)); + + runSpawn([&, this](auto& yield) { + auto const handler = AnyHandler{DepositAuthorizedHandler{mockBackendPtr}}; + auto const output = handler.process(input, Context{std::ref(yield)}); + + ASSERT_TRUE(output); + EXPECT_EQ(*output, json::parse(expectedOut)); + }); +} + +TEST_F(RPCDepositAuthorizedTest, DifferentAccountsWithDepositAuthFlagReturnsFalse) +{ + static auto constexpr expectedOut = + R"({ + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, + "deposit_authorized": false, + "source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "destination_account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun" + })"; + + auto const rawBackendPtr = static_cast(mockBackendPtr.get()); + mockBackendPtr->updateRange(10); // min + mockBackendPtr->updateRange(30); // max + + auto ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30); + + ON_CALL(*rawBackendPtr, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerinfo)); + EXPECT_CALL(*rawBackendPtr, fetchLedgerByHash).Times(1); + + auto const account1Root = CreateAccountRootObject(ACCOUNT, 0, 2, 200, 2, INDEX1, 2); + auto const account2Root = CreateAccountRootObject(ACCOUNT2, ripple::lsfDepositAuth, 2, 200, 2, INDEX2, 2); + + ON_CALL(*rawBackendPtr, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::nullopt)); + ON_CALL(*rawBackendPtr, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key, _, _)) + .WillByDefault(Return(account1Root.getSerializer().peekData())); + ON_CALL(*rawBackendPtr, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT2)).key, _, _)) + .WillByDefault(Return(account2Root.getSerializer().peekData())); + EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3); + + auto const input = json::parse(fmt::format( + R"({{ + "source_account": "{}", + "destination_account": "{}", + "ledger_hash": "{}" + }})", + ACCOUNT, + ACCOUNT2, + LEDGERHASH)); + + runSpawn([&, this](auto& yield) { + auto const handler = AnyHandler{DepositAuthorizedHandler{mockBackendPtr}}; + auto const output = handler.process(input, Context{std::ref(yield)}); + + ASSERT_TRUE(output); + EXPECT_EQ(*output, json::parse(expectedOut)); + }); +} + +TEST_F(RPCDepositAuthorizedTest, DifferentAccountsWithDepositAuthFlagReturnsTrue) +{ + static auto constexpr expectedOut = + R"({ + "ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652", + "ledger_index": 30, + "validated": true, + "deposit_authorized": true, + "source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn", + "destination_account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun" + })"; + + auto const rawBackendPtr = static_cast(mockBackendPtr.get()); + mockBackendPtr->updateRange(10); // min + mockBackendPtr->updateRange(30); // max + + auto ledgerinfo = CreateLedgerInfo(LEDGERHASH, 30); + + ON_CALL(*rawBackendPtr, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerinfo)); + EXPECT_CALL(*rawBackendPtr, fetchLedgerByHash).Times(1); + + auto const account1Root = CreateAccountRootObject(ACCOUNT, 0, 2, 200, 2, INDEX1, 2); + auto const account2Root = CreateAccountRootObject(ACCOUNT2, ripple::lsfDepositAuth, 2, 200, 2, INDEX2, 2); + + ON_CALL(*rawBackendPtr, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional{{1, 2, 3}})); + ON_CALL(*rawBackendPtr, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key, _, _)) + .WillByDefault(Return(account1Root.getSerializer().peekData())); + ON_CALL(*rawBackendPtr, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT2)).key, _, _)) + .WillByDefault(Return(account2Root.getSerializer().peekData())); + EXPECT_CALL(*rawBackendPtr, doFetchLedgerObject).Times(3); + + auto const input = json::parse(fmt::format( + R"({{ + "source_account": "{}", + "destination_account": "{}", + "ledger_hash": "{}" + }})", + ACCOUNT, + ACCOUNT2, + LEDGERHASH)); + + runSpawn([&, this](auto& yield) { + auto const handler = AnyHandler{DepositAuthorizedHandler{mockBackendPtr}}; + auto const output = handler.process(input, Context{std::ref(yield)}); + + ASSERT_TRUE(output); + EXPECT_EQ(*output, json::parse(expectedOut)); + }); +}