feat: Add Support Credentials for Clio (#1712)

Rippled PR: [here](https://github.com/XRPLF/rippled/pull/5103)
This commit is contained in:
Peter Chen
2024-11-14 14:52:03 -05:00
committed by Alex Kremer
parent e38658a0d6
commit 39419c8b58
23 changed files with 1560 additions and 99 deletions

View File

@@ -26,6 +26,7 @@
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/json/json_value.h>
@@ -48,6 +49,7 @@
#include <algorithm>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
@@ -540,7 +542,7 @@ CreateCheckLedgerObject(std::string_view account, std::string_view dest)
}
ripple::STObject
CreateDepositPreauthLedgerObject(std::string_view account, std::string_view auth)
CreateDepositPreauthLedgerObjectByAuth(std::string_view account, std::string_view auth)
{
ripple::STObject depositPreauth(ripple::sfLedgerEntry);
depositPreauth.setFieldU16(ripple::sfLedgerEntryType, ripple::ltDEPOSIT_PREAUTH);
@@ -553,6 +555,27 @@ CreateDepositPreauthLedgerObject(std::string_view account, std::string_view auth
return depositPreauth;
}
ripple::STObject
CreateDepositPreauthLedgerObjectByAuthCredentials(
std::string_view account,
std::string_view issuer,
std::string_view credType
)
{
ripple::STObject depositPreauth(ripple::sfLedgerEntry);
depositPreauth.setFieldU16(ripple::sfLedgerEntryType, ripple::ltDEPOSIT_PREAUTH);
depositPreauth.setAccountID(ripple::sfAccount, GetAccountIDWithString(account));
depositPreauth.setFieldArray(
ripple::sfAuthorizeCredentials,
CreateAuthCredentialArray(std::vector<std::string_view>{issuer}, std::vector<std::string_view>{credType})
);
depositPreauth.setFieldU32(ripple::sfFlags, 0);
depositPreauth.setFieldU64(ripple::sfOwnerNode, 0);
depositPreauth.setFieldH256(ripple::sfPreviousTxnID, ripple::uint256{});
depositPreauth.setFieldU32(ripple::sfPreviousTxnLgrSeq, 0);
return depositPreauth;
}
data::NFT
CreateNFT(std::string_view tokenID, std::string_view account, ripple::LedgerIndex seq, ripple::Blob uri, bool isBurned)
{
@@ -1192,3 +1215,47 @@ CreateOracleObject(
return ledgerObject;
}
// acc2 issue credential for acc1 so acc2 is issuer
ripple::STObject
CreateCredentialObject(
std::string_view acc1,
std::string_view acc2,
std::string_view credType,
bool accept,
std::optional<uint32_t> expiration
)
{
ripple::STObject credObj(ripple::sfCredential);
credObj.setFieldU16(ripple::sfLedgerEntryType, ripple::ltCREDENTIAL);
credObj.setFieldVL(ripple::sfCredentialType, ripple::Blob{credType.begin(), credType.end()});
credObj.setAccountID(ripple::sfSubject, GetAccountIDWithString(acc1));
credObj.setAccountID(ripple::sfIssuer, GetAccountIDWithString(acc2));
if (expiration.has_value())
credObj.setFieldU32(ripple::sfExpiration, expiration.value());
if (accept) {
credObj.setFieldU32(ripple::sfFlags, ripple::lsfAccepted);
} else {
credObj.setFieldU32(ripple::sfFlags, 0);
}
credObj.setFieldU64(ripple::sfSubjectNode, 0);
credObj.setFieldU64(ripple::sfIssuerNode, 0);
credObj.setFieldH256(ripple::sfPreviousTxnID, ripple::uint256{});
credObj.setFieldU32(ripple::sfPreviousTxnLgrSeq, 0);
return credObj;
}
ripple::STArray
CreateAuthCredentialArray(std::vector<std::string_view> issuer, std::vector<std::string_view> credType)
{
ripple::STArray arr;
ASSERT(issuer.size() == credType.size(), "issuer and credtype vector must be same length");
for (std::size_t i = 0; i < issuer.size(); ++i) {
auto credential = ripple::STObject::makeInnerObject(ripple::sfCredential);
credential.setAccountID(ripple::sfIssuer, GetAccountIDWithString(issuer[i]));
credential.setFieldVL(ripple::sfCredentialType, ripple::strUnHex(std::string(credType[i])).value());
arr.push_back(credential);
}
return arr;
}

View File

@@ -22,6 +22,7 @@
#include "data/Types.hpp"
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Issue.h>
@@ -268,7 +269,14 @@ CreateEscrowLedgerObject(std::string_view account, std::string_view dest);
CreateCheckLedgerObject(std::string_view account, std::string_view dest);
[[nodiscard]] ripple::STObject
CreateDepositPreauthLedgerObject(std::string_view account, std::string_view auth);
CreateDepositPreauthLedgerObjectByAuth(std::string_view account, std::string_view auth);
[[nodiscard]] ripple::STObject
CreateDepositPreauthLedgerObjectByAuthCredentials(
std::string_view account,
std::string_view issuer,
std::string_view credType
);
[[nodiscard]] data::NFT
CreateNFT(
@@ -435,3 +443,15 @@ CreateOracleSetTxWithMetadata(
bool created,
std::string_view previousTxnId
);
[[nodiscard]] ripple::STObject
CreateCredentialObject(
std::string_view acc1,
std::string_view acc2,
std::string_view credType,
bool accept = true,
std::optional<uint32_t> expiration = std::nullopt
);
[[nodiscard]] ripple::STArray
CreateAuthCredentialArray(std::vector<std::string_view> issuer, std::vector<std::string_view> credType);

View File

@@ -67,6 +67,7 @@ target_sources(
rpc/handlers/AMMInfoTests.cpp
rpc/handlers/BookChangesTests.cpp
rpc/handlers/BookOffersTests.cpp
rpc/handlers/CredentialHelpersTests.cpp
rpc/handlers/DefaultProcessorTests.cpp
rpc/handlers/DepositAuthorizedTests.cpp
rpc/handlers/FeatureTests.cpp

View File

@@ -30,10 +30,12 @@
#include <boost/asio/impl/spawn.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/json/array.hpp>
#include <boost/json/parse.hpp>
#include <fmt/core.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/ErrorCodes.h>
#include <xrpl/protocol/Indexes.h>
@@ -47,6 +49,7 @@
#include <cstdint>
#include <stdexcept>
#include <string>
#include <string_view>
#include <tuple>
#include <variant>
#include <vector>

View File

@@ -0,0 +1,153 @@
//------------------------------------------------------------------------------
/*
This file is part of clio: https://github.com/XRPLF/clio
Copyright (c) 2024, 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 "rpc/CredentialHelpers.hpp"
#include "rpc/Errors.hpp"
#include "rpc/JS.hpp"
#include "util/AsioContextTestFixture.hpp"
#include "util/MockBackendTestFixture.hpp"
#include "util/MockPrometheus.hpp"
#include "util/TestObject.hpp"
#include <boost/asio/spawn.hpp>
#include <boost/json/array.hpp>
#include <boost/json/object.hpp>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/STArray.h>
#include <xrpl/protocol/STObject.h>
#include <xrpl/protocol/jss.h>
#include <string>
#include <string_view>
#include <utility>
using namespace rpc;
using namespace testing;
constexpr static auto Account = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn";
constexpr static auto Account2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun";
constexpr static auto Index1 = "E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321";
constexpr static auto CredentialID = "c7a14f6b9d5d4a9cb9c223a61b8e5c7df58e8b7ad1c6b4f8e7a321fa4e5b4c9d";
constexpr static std::string_view CredentialType = "credType";
TEST(CreateAuthCredentialsTest, UniqueCredentials)
{
ripple::STArray credentials;
auto const cred1 = CreateCredentialObject(Account, Account2, CredentialType);
auto const cred2 = CreateCredentialObject(Account2, Account, CredentialType);
credentials.push_back(cred1);
credentials.push_back(cred2);
auto const result = credentials::createAuthCredentials(credentials);
// Validate that the result contains the correct set of credentials
ASSERT_EQ(result.size(), 2);
auto const cred1Type = cred1.getFieldVL(ripple::sfCredentialType);
auto const cred2Type = cred2.getFieldVL(ripple::sfCredentialType);
auto const expected_cred1 =
std::make_pair(cred1.getAccountID(ripple::sfIssuer), ripple::Slice{cred1Type.data(), cred1Type.size()});
auto const expected_cred2 =
std::make_pair(cred2.getAccountID(ripple::sfIssuer), ripple::Slice{cred2Type.data(), cred2Type.size()});
EXPECT_TRUE(result.count(expected_cred1));
EXPECT_TRUE(result.count(expected_cred2));
}
TEST(ParseAuthorizeCredentialsTest, ValidCredentialsArray)
{
boost::json::array credentials;
boost::json::object credential1;
credential1[JS(issuer)] = Account;
credential1[JS(credential_type)] = ripple::strHex(CredentialType);
credentials.push_back(credential1);
ripple::STArray const parsedCredentials = credentials::parseAuthorizeCredentials(credentials);
ASSERT_EQ(parsedCredentials.size(), 1);
ripple::STObject const& cred = parsedCredentials[0];
ASSERT_TRUE(cred.isFieldPresent(ripple::sfIssuer));
ASSERT_TRUE(cred.isFieldPresent(ripple::sfCredentialType));
auto const expectedIssuer =
*ripple::parseBase58<ripple::AccountID>(static_cast<std::string>(credential1[JS(issuer)].as_string()));
auto const expectedCredentialType =
ripple::strUnHex(static_cast<std::string>(credential1[JS(credential_type)].as_string())).value();
EXPECT_EQ(cred.getAccountID(ripple::sfIssuer), expectedIssuer);
EXPECT_EQ(cred.getFieldVL(ripple::sfCredentialType), expectedCredentialType);
}
class CredentialHelperTest : public util::prometheus::WithPrometheus,
public MockBackendTest,
public SyncAsioContextTest {};
TEST_F(CredentialHelperTest, GetInvalidCredentialArray)
{
boost::json::array credentialsArray = {CredentialID};
auto const info = CreateLedgerHeader(Index1, 30);
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
auto const ret =
credentials::fetchCredentialArray(credentialsArray, GetAccountIDWithString(Account), *backend, info, yield);
ASSERT_FALSE(ret.has_value());
auto const status = ret.error();
EXPECT_EQ(status, RippledError::rpcBAD_CREDENTIALS);
EXPECT_EQ(status.message, "credentials don't exist.");
});
ctx.run();
}
TEST_F(CredentialHelperTest, GetValidCredentialArray)
{
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(Index1, 30);
auto const credLedgerObject = CreateCredentialObject(Account, Account2, CredentialType, true);
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(credLedgerObject.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(1);
boost::json::array credentialsArray = {CredentialID};
ripple::STArray expectedAuthCreds;
ripple::STObject credential(ripple::sfCredential);
credential.setAccountID(ripple::sfIssuer, GetAccountIDWithString(Account2));
credential.setFieldVL(ripple::sfCredentialType, ripple::Blob{std::begin(CredentialType), std::end(CredentialType)});
expectedAuthCreds.push_back(std::move(credential));
boost::asio::spawn(ctx, [&](boost::asio::yield_context yield) {
auto const result = credentials::fetchCredentialArray(
credentialsArray, GetAccountIDWithString(Account), *backend, ledgerHeader, yield
);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value(), expectedAuthCreds);
});
ctx.run();
}

View File

@@ -28,24 +28,34 @@
#include <boost/json/parse.hpp>
#include <fmt/core.h>
#include <fmt/format.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/chrono.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/STArray.h>
#include <chrono>
#include <optional>
#include <ranges>
#include <string>
#include <string_view>
#include <vector>
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 Account = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn";
constexpr static auto Account2 = "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun";
constexpr static auto LedgerHash = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652";
constexpr static auto Index1 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515BC";
constexpr static auto Index2 = "1B8590C01B0006EDFA9ED60296DD052DC5E90F99659B25014D08E1BC983515B1";
constexpr static std::string_view CredentialType = "credType";
constexpr static auto CredentialHash = "F245428267E6177AEEFDD4FEA3533285712A4B1091CF82A7EA7BC39A62C3FB1A";
constexpr static auto RANGEMIN = 10;
constexpr static auto RANGEMAX = 30;
constexpr static auto RangeMin = 10;
constexpr static auto RangeMax = 30;
using namespace rpc;
namespace json = boost::json;
@@ -156,6 +166,38 @@ generateTestValuesForParametersTest()
"invalidParams",
"ledgerIndexMalformed",
},
{
"CredentialsNotArray",
R"({
"source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"credentials": "x"
})",
"invalidParams",
"Invalid parameters.",
},
{
"CredentialsNotStringsInArray",
R"({
"source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
"credentials": [123]
})",
"invalidParams",
"Item is not a valid uint256 type.",
},
{
"CredentialsNotHexedStringInArray",
R"({
"source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"destination_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
"credentials": ["234", "432"]
})",
"invalidParams",
"Item is not a valid uint256 type.",
}
};
}
@@ -184,10 +226,10 @@ TEST_P(DepositAuthorizedParameterTest, InvalidParams)
TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaIntSequence)
{
backend->setRange(RANGEMIN, RANGEMAX);
backend->setRange(RangeMin, RangeMax);
EXPECT_CALL(*backend, fetchLedgerBySequence).Times(1);
ON_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)).WillByDefault(Return(std::nullopt));
ON_CALL(*backend, fetchLedgerBySequence(RangeMax, _)).WillByDefault(Return(std::nullopt));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
@@ -197,9 +239,9 @@ TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaIntSequence)
"destination_account": "{}",
"ledger_index": {}
}})",
ACCOUNT,
ACCOUNT2,
RANGEMAX
Account,
Account2,
RangeMax
));
auto const output = handler.process(req, Context{yield});
@@ -213,10 +255,10 @@ TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaIntSequence)
TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaStringSequence)
{
backend->setRange(RANGEMIN, RANGEMAX);
backend->setRange(RangeMin, RangeMax);
EXPECT_CALL(*backend, fetchLedgerBySequence).Times(1);
ON_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)).WillByDefault(Return(std::nullopt));
ON_CALL(*backend, fetchLedgerBySequence(RangeMax, _)).WillByDefault(Return(std::nullopt));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
@@ -226,9 +268,9 @@ TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaStringSequence)
"destination_account": "{}",
"ledger_index": "{}"
}})",
ACCOUNT,
ACCOUNT2,
RANGEMAX
Account,
Account2,
RangeMax
));
auto const output = handler.process(req, Context{yield});
@@ -242,10 +284,10 @@ TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaStringSequence)
TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaHash)
{
backend->setRange(RANGEMIN, RANGEMAX);
backend->setRange(RangeMin, RangeMax);
EXPECT_CALL(*backend, fetchLedgerByHash).Times(1);
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(std::nullopt));
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillByDefault(Return(std::nullopt));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
@@ -255,9 +297,9 @@ TEST_F(RPCDepositAuthorizedTest, LedgerNotExistViaHash)
"destination_account": "{}",
"ledger_hash": "{}"
}})",
ACCOUNT,
ACCOUNT2,
LEDGERHASH
Account,
Account2,
LedgerHash
));
auto const output = handler.process(req, Context{yield});
@@ -273,9 +315,9 @@ TEST_F(RPCDepositAuthorizedTest, SourceAccountDoesNotExist)
{
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LEDGERHASH, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerHeader));
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillByDefault(Return(ledgerHeader));
EXPECT_CALL(*backend, fetchLedgerByHash).Times(1);
ON_CALL(*backend, doFetchLedgerObject).WillByDefault(Return(std::optional<Blob>{}));
@@ -287,9 +329,9 @@ TEST_F(RPCDepositAuthorizedTest, SourceAccountDoesNotExist)
"destination_account": "{}",
"ledger_hash": "{}"
}})",
ACCOUNT,
ACCOUNT2,
LEDGERHASH
Account,
Account2,
LedgerHash
));
runSpawn([&, this](auto yield) {
@@ -308,14 +350,14 @@ TEST_F(RPCDepositAuthorizedTest, DestinationAccountDoesNotExist)
{
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LEDGERHASH, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerHeader));
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillByDefault(Return(ledgerHeader));
EXPECT_CALL(*backend, fetchLedgerByHash).Times(1);
auto const accountRoot = CreateAccountRootObject(ACCOUNT, 0, 2, 200, 2, INDEX1, 2);
auto const accountRoot = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(accountRoot.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT2)).key, _, _))
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(std::optional<Blob>{}));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(2);
@@ -326,9 +368,9 @@ TEST_F(RPCDepositAuthorizedTest, DestinationAccountDoesNotExist)
"destination_account": "{}",
"ledger_hash": "{}"
}})",
ACCOUNT,
ACCOUNT2,
LEDGERHASH
Account,
Account2,
LedgerHash
));
runSpawn([&, this](auto yield) {
@@ -357,12 +399,12 @@ TEST_F(RPCDepositAuthorizedTest, AccountsAreEqual)
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LEDGERHASH, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerHeader));
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillByDefault(Return(ledgerHeader));
EXPECT_CALL(*backend, fetchLedgerByHash).Times(1);
auto const accountRoot = CreateAccountRootObject(ACCOUNT, 0, 2, 200, 2, INDEX1, 2);
auto const accountRoot = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
ON_CALL(*backend, doFetchLedgerObject).WillByDefault(Return(accountRoot.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(2);
@@ -372,9 +414,9 @@ TEST_F(RPCDepositAuthorizedTest, AccountsAreEqual)
"destination_account": "{}",
"ledger_hash": "{}"
}})",
ACCOUNT,
ACCOUNT,
LEDGERHASH
Account,
Account,
LedgerHash
));
runSpawn([&, this](auto yield) {
@@ -400,17 +442,17 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsNoDepositAuthFlag)
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LEDGERHASH, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerHeader));
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillByDefault(Return(ledgerHeader));
EXPECT_CALL(*backend, 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);
auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
auto const account2Root = CreateAccountRootObject(Account2, 0, 2, 200, 2, Index2, 2);
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key, _, _))
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT2)).key, _, _))
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(2);
@@ -420,9 +462,9 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsNoDepositAuthFlag)
"destination_account": "{}",
"ledger_hash": "{}"
}})",
ACCOUNT,
ACCOUNT2,
LEDGERHASH
Account,
Account2,
LedgerHash
));
runSpawn([&, this](auto yield) {
@@ -448,18 +490,18 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsWithDepositAuthFlagReturnsFals
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LEDGERHASH, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerHeader));
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillByDefault(Return(ledgerHeader));
EXPECT_CALL(*backend, 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);
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(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::nullopt));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key, _, _))
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT2)).key, _, _))
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(3);
@@ -469,9 +511,9 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsWithDepositAuthFlagReturnsFals
"destination_account": "{}",
"ledger_hash": "{}"
}})",
ACCOUNT,
ACCOUNT2,
LEDGERHASH
Account,
Account2,
LedgerHash
));
runSpawn([&, this](auto yield) {
@@ -497,18 +539,18 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsWithDepositAuthFlagReturnsTrue
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LEDGERHASH, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerHeader));
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillByDefault(Return(ledgerHeader));
EXPECT_CALL(*backend, 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);
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(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional<Blob>{{1, 2, 3}}));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT)).key, _, _))
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(ACCOUNT2)).key, _, _))
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(3);
@@ -518,9 +560,9 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsWithDepositAuthFlagReturnsTrue
"destination_account": "{}",
"ledger_hash": "{}"
}})",
ACCOUNT,
ACCOUNT2,
LEDGERHASH
Account,
Account2,
LedgerHash
));
runSpawn([&, this](auto yield) {
@@ -531,3 +573,380 @@ TEST_F(RPCDepositAuthorizedTest, DifferentAccountsWithDepositAuthFlagReturnsTrue
EXPECT_EQ(*output.result, json::parse(expectedOut));
});
}
TEST_F(RPCDepositAuthorizedTest, CredentialAcceptedAndNotExpiredReturnsTrue)
{
static auto const expectedOut = fmt::format(
R"({{
"ledger_hash": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
"ledger_index": 30,
"validated": true,
"deposit_authorized": true,
"source_account": "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
"destination_account": "rLEsXccBGNR3UPuPu2hUXPjziKC3qKSBun",
"credentials": ["{}"]
}})",
CredentialHash // CREDENTIALHASH should match credentialIndex
);
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
EXPECT_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillOnce(Return(ledgerHeader));
auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
auto const account2Root = CreateAccountRootObject(Account2, ripple::lsfDepositAuth, 2, 200, 2, Index2, 2);
auto const credential = CreateCredentialObject(Account, Account2, CredentialType);
auto const credentialIndex = ripple::keylet::credential(
GetAccountIDWithString(Account),
GetAccountIDWithString(Account2),
ripple::Slice(CredentialType.data(), CredentialType.size())
)
.key;
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional<Blob>{{1, 2, 3}}));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(credentialIndex, _, _))
.WillByDefault(Return(credential.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(4);
auto const input = json::parse(fmt::format(
R"({{
"source_account": "{}",
"destination_account": "{}",
"ledger_hash": "{}",
"credentials": ["{}"]
}})",
Account,
Account2,
LedgerHash,
ripple::strHex(credentialIndex)
));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
auto const output = handler.process(input, Context{yield});
ASSERT_TRUE(output);
EXPECT_EQ(*output.result, json::parse(expectedOut));
});
}
TEST_F(RPCDepositAuthorizedTest, CredentialNotAuthorizedReturnsFalse)
{
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
EXPECT_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillOnce(Return(ledgerHeader));
auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
auto const account2Root = CreateAccountRootObject(Account2, ripple::lsfDepositAuth, 2, 200, 2, Index2, 2);
auto const credential = CreateCredentialObject(Account, Account2, CredentialType, false);
auto const credentialIndex = ripple::keylet::credential(
GetAccountIDWithString(Account),
GetAccountIDWithString(Account2),
ripple::Slice(CredentialType.data(), CredentialType.size())
)
.key;
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional<Blob>{{1, 2, 3}}));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(credentialIndex, _, _))
.WillByDefault(Return(credential.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(3);
auto const input = json::parse(fmt::format(
R"({{
"source_account": "{}",
"destination_account": "{}",
"ledger_hash": "{}",
"credentials": ["{}"]
}})",
Account,
Account2,
LedgerHash,
ripple::strHex(credentialIndex)
));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
auto const output = handler.process(input, Context{yield});
ASSERT_FALSE(output);
auto const err = rpc::makeError(output.result.error());
EXPECT_EQ(err.at("error").as_string(), "badCredentials");
EXPECT_EQ(err.at("error_message").as_string(), "credentials aren't accepted");
});
}
TEST_F(RPCDepositAuthorizedTest, CredentialExpiredReturnsFalse)
{
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30, 100);
// set parent close time to 500 seconds
ledgerHeader.parentCloseTime = ripple::NetClock::time_point{std::chrono::seconds{500}};
EXPECT_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillOnce(Return(ledgerHeader));
auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
auto const account2Root = CreateAccountRootObject(Account2, ripple::lsfDepositAuth, 2, 200, 2, Index2, 2);
// credential expire time is 23 seconds, so credential will fail
auto const expiredCredential = CreateCredentialObject(Account, Account2, CredentialType, true, 23);
auto const credentialIndex = ripple::keylet::credential(
GetAccountIDWithString(Account),
GetAccountIDWithString(Account2),
ripple::Slice(CredentialType.data(), CredentialType.size())
)
.key;
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional<Blob>{{1, 2, 3}}));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(credentialIndex, _, _))
.WillByDefault(Return(expiredCredential.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(3);
auto const input = json::parse(fmt::format(
R"({{
"source_account": "{}",
"destination_account": "{}",
"ledger_hash": "{}",
"credentials": ["{}"]
}})",
Account,
Account2,
LedgerHash,
ripple::strHex(credentialIndex)
));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
auto const output = handler.process(input, Context{yield});
ASSERT_FALSE(output);
auto const err = rpc::makeError(output.result.error());
EXPECT_EQ(err.at("error").as_string(), "badCredentials");
EXPECT_EQ(err.at("error_message").as_string(), "credentials are expired");
});
}
TEST_F(RPCDepositAuthorizedTest, DuplicateCredentialsReturnsFalse)
{
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30, 34);
EXPECT_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillOnce(Return(ledgerHeader));
auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
auto const account2Root = CreateAccountRootObject(Account2, ripple::lsfDepositAuth, 2, 200, 2, Index2, 2);
auto const credential = CreateCredentialObject(Account, Account2, CredentialType);
auto const credentialIndex = ripple::keylet::credential(
GetAccountIDWithString(Account),
GetAccountIDWithString(Account2),
ripple::Slice(CredentialType.data(), CredentialType.size())
)
.key;
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional<Blob>{{1, 2, 3}}));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(credentialIndex, _, _))
.WillByDefault(Return(credential.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(3);
auto const input = json::parse(fmt::format(
R"({{
"source_account": "{}",
"destination_account": "{}",
"ledger_hash": "{}",
"credentials": ["{}", "{}"]
}})",
Account,
Account2,
LedgerHash,
ripple::strHex(credentialIndex),
ripple::strHex(credentialIndex)
));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
auto const output = handler.process(input, Context{yield});
ASSERT_FALSE(output);
auto const err = rpc::makeError(output.result.error());
EXPECT_EQ(err.at("error").as_string(), "badCredentials");
EXPECT_EQ(err.at("error_message").as_string(), "duplicates in credentials.");
});
}
TEST_F(RPCDepositAuthorizedTest, NoElementsInCredentialsReturnsFalse)
{
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30, 34);
EXPECT_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillOnce(Return(ledgerHeader));
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(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional<Blob>{{1, 2, 3}}));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(2);
auto const input = json::parse(fmt::format(
R"({{
"source_account": "{}",
"destination_account": "{}",
"ledger_hash": "{}",
"credentials": []
}})",
Account,
Account2,
LedgerHash
));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
auto const output = handler.process(input, Context{yield});
ASSERT_FALSE(output);
auto const err = rpc::makeError(output.result.error());
EXPECT_EQ(err.at("error").as_string(), "invalidParams");
EXPECT_EQ(err.at("error_message").as_string(), "credential array has no elements.");
});
}
TEST_F(RPCDepositAuthorizedTest, MoreThanMaxNumberOfCredentialsReturnsFalse)
{
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30, 34);
EXPECT_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillOnce(Return(ledgerHeader));
auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
auto const account2Root = CreateAccountRootObject(Account2, ripple::lsfDepositAuth, 2, 200, 2, Index2, 2);
auto const credential = CreateCredentialObject(Account, Account2, CredentialType);
auto const credentialIndex = ripple::keylet::credential(
GetAccountIDWithString(Account),
GetAccountIDWithString(Account2),
ripple::Slice(CredentialType.data(), CredentialType.size())
)
.key;
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional<Blob>{{1, 2, 3}}));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(credentialIndex, _, _))
.WillByDefault(Return(credential.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(2);
std::vector<std::string> credentials(9, ripple::strHex(credentialIndex));
auto const input = json::parse(fmt::format(
R"({{
"source_account": "{}",
"destination_account": "{}",
"ledger_hash": "{}",
"credentials": [{}]
}})",
Account,
Account2,
LedgerHash,
fmt::join(
credentials | std::views::transform([](std::string const& cred) { return fmt::format("\"{}\"", cred); }),
", "
)
));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
auto const output = handler.process(input, Context{yield});
ASSERT_FALSE(output);
auto const err = rpc::makeError(output.result.error());
EXPECT_EQ(err.at("error").as_string(), "invalidParams");
EXPECT_EQ(err.at("error_message").as_string(), "credential array too long.");
});
}
TEST_F(RPCDepositAuthorizedTest, DifferenSubjectAccountForCredentialReturnsFalse)
{
backend->setRange(10, 30);
auto ledgerHeader = CreateLedgerHeader(LedgerHash, 30);
EXPECT_CALL(*backend, fetchLedgerByHash(ripple::uint256{LedgerHash}, _)).WillOnce(Return(ledgerHeader));
auto const account1Root = CreateAccountRootObject(Account, 0, 2, 200, 2, Index1, 2);
auto const account2Root = CreateAccountRootObject(Account2, ripple::lsfDepositAuth, 2, 200, 2, Index2, 2);
// reverse the subject and issuer account. Now subject is ACCOUNT2
auto const credential = CreateCredentialObject(Account2, Account, CredentialType);
auto const credentialIndex = ripple::keylet::credential(
GetAccountIDWithString(Account2),
GetAccountIDWithString(Account),
ripple::Slice(CredentialType.data(), CredentialType.size())
)
.key;
ON_CALL(*backend, doFetchLedgerObject(_, _, _)).WillByDefault(Return(std::optional<Blob>{{1, 2, 3}}));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account)).key, _, _))
.WillByDefault(Return(account1Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(ripple::keylet::account(GetAccountIDWithString(Account2)).key, _, _))
.WillByDefault(Return(account2Root.getSerializer().peekData()));
ON_CALL(*backend, doFetchLedgerObject(credentialIndex, _, _))
.WillByDefault(Return(credential.getSerializer().peekData()));
EXPECT_CALL(*backend, doFetchLedgerObject).Times(3);
auto const input = json::parse(fmt::format(
R"({{
"source_account": "{}",
"destination_account": "{}",
"ledger_hash": "{}",
"credentials": ["{}"]
}})",
Account,
Account2,
LedgerHash,
ripple::strHex(credentialIndex)
));
runSpawn([&, this](auto yield) {
auto const handler = AnyHandler{DepositAuthorizedHandler{backend}};
auto const output = handler.process(input, Context{yield});
ASSERT_FALSE(output);
auto const err = rpc::makeError(output.result.error());
EXPECT_EQ(err.at("error").as_string(), "badCredentials");
EXPECT_EQ(err.at("error_message").as_string(), "credentials don't belong to the root account");
});
}

View File

@@ -18,6 +18,7 @@
//==============================================================================
#include "data/Types.hpp"
#include "rpc/CredentialHelpers.hpp"
#include "rpc/Errors.hpp"
#include "rpc/common/AnyHandler.hpp"
#include "rpc/common/Types.hpp"
@@ -29,6 +30,8 @@
#include <boost/asio/executor_work_guard.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/json/array.hpp>
#include <boost/json/object.hpp>
#include <boost/json/parse.hpp>
#include <boost/json/value.hpp>
#include <boost/json/value_to.hpp>
@@ -36,6 +39,8 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <xrpl/basics/Blob.h>
#include <xrpl/basics/Slice.h>
#include <xrpl/basics/StringUtilities.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/basics/strHex.h>
#include <xrpl/protocol/AccountID.h>
@@ -49,6 +54,7 @@
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
@@ -66,6 +72,7 @@ constexpr static auto LEDGERHASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A
constexpr static auto TOKENID = "000827103B94ECBB7BF0A0A6ED62B3607801A27B65F4679F4AD1D4850000C0EA";
constexpr static auto NFTID = "00010000A7CAD27B688D14BA1A9FA5366554D6ADCF9CE0875B974D9F00000004";
constexpr static auto TXNID = "05FB0EB4B899F056FA095537C5817163801F544BAFCEA39C995D76DB4D16F9DD";
constexpr static auto CREDENTIALTYPE = "4B5943";
class RPCLedgerEntryTest : public HandlerBaseTest {};
@@ -200,6 +207,206 @@ generateTestValuesForParametersTest()
"authorizedNotString"
},
ParamTestCaseBundle{
"InvalidDepositPreauthJsonAuthorizeCredentialsNotArray",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": "asdf"
}}
}})",
ACCOUNT
),
"malformedRequest",
"authorized_credentials not array"
},
ParamTestCaseBundle{
"DepositPreauthBothAuthAndAuthCredentialsDoesNotExists",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}"
}}
}})",
ACCOUNT
),
"malformedRequest",
"Must have one of authorized or authorized_credentials."
},
ParamTestCaseBundle{
"DepositPreauthBothAuthAndAuthCredentialsExists",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized": "{}",
"authorized_credentials": [
{{
"issuer": "{}",
"credential_type": "{}"
}}
]
}}
}})",
ACCOUNT,
ACCOUNT2,
ACCOUNT3,
CREDENTIALTYPE
),
"malformedRequest",
"Must have one of authorized or authorized_credentials."
},
ParamTestCaseBundle{
"DepositPreauthEmptyAuthorizeCredentials",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": [
]
}}
}})",
ACCOUNT
),
"malformedAuthorizedCredentials",
"Requires at least one element in authorized_credentials array"
},
ParamTestCaseBundle{
"DepositPreauthAuthorizeCredentialsMissingCredentialType",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": [
{{
"issuer": "{}"
}}
]
}}
}})",
ACCOUNT,
ACCOUNT2
),
"malformedRequest",
"Field 'CredentialType' is required but missing."
},
ParamTestCaseBundle{
"DepositPreauthAuthorizeCredentialsMissingIssuer",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": [
{{
"credential_type": "{}"
}}
]
}}
}})",
ACCOUNT,
CREDENTIALTYPE
),
"malformedRequest",
"Field 'Issuer' is required but missing."
},
ParamTestCaseBundle{
"DepositPreauthAuthorizeCredentialsIncorrectCredentialType",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": [
{{
"issuer": "{}",
"credential_type": 432
}}
]
}}
}})",
ACCOUNT,
ACCOUNT2
),
"invalidParams",
"credential_type NotString"
},
ParamTestCaseBundle{
"DepositPreauthAuthorizeCredentialsCredentialTypeNotHex",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": [
{{
"issuer": "{}",
"credential_type": "hello world"
}}
]
}}
}})",
ACCOUNT,
ACCOUNT2
),
"malformedAuthorizedCredentials",
"credential_type NotHexString"
},
ParamTestCaseBundle{
"DepositPreauthAuthorizeCredentialsCredentialTypeEmpty",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": [
{{
"issuer": "{}",
"credential_type": ""
}}
]
}}
}})",
ACCOUNT,
ACCOUNT2
),
"malformedAuthorizedCredentials",
"credential_type is empty"
},
ParamTestCaseBundle{
"DepositPreauthDuplicateAuthorizeCredentials",
fmt::format(
R"({{
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": [
{{
"issuer": "{}",
"credential_type": "{}"
}},
{{
"issuer": "{}",
"credential_type": "{}"
}}
]
}}
}})",
ACCOUNT,
ACCOUNT2,
CREDENTIALTYPE,
ACCOUNT2,
CREDENTIALTYPE
),
"malformedAuthorizedCredentials",
"duplicates in credentials."
},
ParamTestCaseBundle{
"InvalidTicketType",
R"({
@@ -1759,6 +1966,29 @@ generateTestValuesForParametersTest()
"malformedRequest",
"Malformed request."
},
ParamTestCaseBundle{
"CredentialInvalidSubjectType",
R"({
"credential": {
"subject": 123
}
})",
"malformedAddress",
"Malformed address."
},
ParamTestCaseBundle{
"CredentialInvalidIssuerType",
fmt::format(
R"({{
"credential": {{
"issuer": ["{}"]
}}
}})",
ACCOUNT
),
"malformedRequest",
"Malformed request."
},
ParamTestCaseBundle{
"InvalidMPTIssuanceStringIndex",
R"({
@@ -1806,6 +2036,37 @@ generateTestValuesForParametersTest()
"malformedRequest",
"Malformed request."
},
ParamTestCaseBundle{
"CredentialInvalidCredentialType",
fmt::format(
R"({{
"credential": {{
"subject": "{}",
"issuer": "{}",
"credential_type": 1234
}}
}})",
ACCOUNT,
ACCOUNT2
),
"malformedRequest",
"Malformed request."
},
ParamTestCaseBundle{
"CredentialMissingIssuerField",
fmt::format(
R"({{
"credential": {{
"subject": "{}",
"credential_type": "1234"
}}
}})",
ACCOUNT,
ACCOUNT2
),
"malformedRequest",
"Malformed request."
},
ParamTestCaseBundle{
"InvalidMPTokenAccount",
fmt::format(
@@ -1828,7 +2089,7 @@ generateTestValuesForParametersTest()
),
"malformedRequest",
"Malformed request."
},
}
};
}
@@ -2068,7 +2329,7 @@ generateTestValuesForNormalPathTest()
INDEX1
),
ripple::uint256{INDEX1},
CreateDepositPreauthLedgerObject(ACCOUNT, ACCOUNT2)
CreateDepositPreauthLedgerObjectByAuth(ACCOUNT, ACCOUNT2)
},
NormalPathTestBundle{
"AccountRoot",
@@ -2155,7 +2416,7 @@ generateTestValuesForNormalPathTest()
CreateEscrowLedgerObject(ACCOUNT, ACCOUNT2)
},
NormalPathTestBundle{
"DepositPreauth",
"DepositPreauthByAuth",
fmt::format(
R"({{
"binary": true,
@@ -2168,7 +2429,58 @@ generateTestValuesForNormalPathTest()
ACCOUNT2
),
ripple::keylet::depositPreauth(account1, account2).key,
CreateDepositPreauthLedgerObject(ACCOUNT, ACCOUNT2)
CreateDepositPreauthLedgerObjectByAuth(ACCOUNT, ACCOUNT2)
},
NormalPathTestBundle{
"DepositPreauthByAuthCredentials",
fmt::format(
R"({{
"binary": true,
"deposit_preauth": {{
"owner": "{}",
"authorized_credentials": [
{{
"issuer": "{}",
"credential_type": "{}"
}}
]
}}
}})",
ACCOUNT,
ACCOUNT2,
CREDENTIALTYPE
),
ripple::keylet::depositPreauth(
account1,
credentials::createAuthCredentials(CreateAuthCredentialArray(
std::vector<std::string_view>{ACCOUNT2}, std::vector<std::string_view>{CREDENTIALTYPE}
))
)
.key,
CreateDepositPreauthLedgerObjectByAuthCredentials(ACCOUNT, ACCOUNT2, CREDENTIALTYPE)
},
NormalPathTestBundle{
"Credentials",
fmt::format(
R"({{
"binary": true,
"credential": {{
"subject": "{}",
"issuer": "{}",
"credential_type": "{}"
}}
}})",
ACCOUNT,
ACCOUNT2,
CREDENTIALTYPE
),
ripple::keylet::credential(
account1,
account2,
ripple::Slice(ripple::strUnHex(CREDENTIALTYPE)->data(), ripple::strUnHex(CREDENTIALTYPE)->size())
)
.key,
CreateCredentialObject(ACCOUNT, ACCOUNT2, CREDENTIALTYPE)
},
NormalPathTestBundle{
"RippleState",

View File

@@ -55,6 +55,7 @@ TEST(LedgerUtilsTests, LedgerObjectTypeList)
JS(mpt_issuance),
JS(mptoken),
JS(oracle),
JS(credential),
JS(nunl)
};
@@ -86,6 +87,7 @@ TEST(LedgerUtilsTests, AccountOwnedTypeList)
JS(xchain_owned_create_account_claim_id),
JS(did),
JS(oracle),
JS(credential),
JS(mpt_issuance),
JS(mptoken)
};