mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-06 17:27:58 +00:00
feat: Implement MPT changes (#1147)
Implements https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0033d-multi-purpose-tokens
This commit is contained in:
@@ -77,6 +77,7 @@ target_sources(
|
||||
rpc/handlers/LedgerIndexTests.cpp
|
||||
rpc/handlers/LedgerRangeTests.cpp
|
||||
rpc/handlers/LedgerTests.cpp
|
||||
rpc/handlers/MPTHoldersTests.cpp
|
||||
rpc/handlers/NFTBuyOffersTests.cpp
|
||||
rpc/handlers/NFTHistoryTests.cpp
|
||||
rpc/handlers/NFTInfoTests.cpp
|
||||
|
||||
@@ -515,6 +515,40 @@ TEST_F(RPCBaseTest, AccountMarkerValidator)
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, Uint160HexStringValidator)
|
||||
{
|
||||
auto const spec = RpcSpec{{"marker", CustomValidators::Uint160HexStringValidator}};
|
||||
auto passingInput = json::parse(R"({ "marker": "F609A18102218C75767209946A77523CBD97E225"})");
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto failingInput = json::parse(R"({ "marker": 160})");
|
||||
auto err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
ASSERT_EQ(err.error().message, "markerNotString");
|
||||
|
||||
failingInput = json::parse(R"({ "marker": "F609A18102218C75767209946A77523CBD97E2253515BC"})");
|
||||
err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
ASSERT_EQ(err.error().message, "markerMalformed");
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, Uint192HexStringValidator)
|
||||
{
|
||||
auto const spec = RpcSpec{{"mpt_issuance_id", CustomValidators::Uint192HexStringValidator}};
|
||||
auto passingInput = json::parse(R"({ "mpt_issuance_id": "0000012F27A9DE73EAA1E8831FA253E19030A17E2D038198"})");
|
||||
ASSERT_TRUE(spec.process(passingInput));
|
||||
|
||||
auto failingInput = json::parse(R"({ "mpt_issuance_id": 192})");
|
||||
auto err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
ASSERT_EQ(err.error().message, "mpt_issuance_idNotString");
|
||||
|
||||
failingInput = json::parse(R"({ "mpt_issuance_id": "0000012F27A9DE73EAA1E8831FA253E19030A17E2D038198983515BC"})");
|
||||
err = spec.process(failingInput);
|
||||
ASSERT_FALSE(err);
|
||||
ASSERT_EQ(err.error().message, "mpt_issuance_idMalformed");
|
||||
}
|
||||
|
||||
TEST_F(RPCBaseTest, Uint256HexStringValidator)
|
||||
{
|
||||
auto const spec = RpcSpec{{"transaction", CustomValidators::Uint256HexStringValidator}};
|
||||
|
||||
@@ -1626,3 +1626,95 @@ TEST_F(RPCAccountObjectsHandlerTest, LimitMoreThanMax)
|
||||
EXPECT_EQ(*output.result, json::parse(expectedOut));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCAccountObjectsHandlerTest, TypeFilterMPTIssuanceType)
|
||||
{
|
||||
backend->setRange(MINSEQ, MAXSEQ);
|
||||
auto const ledgerinfo = CreateLedgerHeader(LEDGERHASH, MAXSEQ);
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(ledgerinfo));
|
||||
|
||||
auto const account = GetAccountIDWithString(ACCOUNT);
|
||||
auto const accountKk = ripple::keylet::account(account).key;
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillOnce(Return(Blob{'f', 'a', 'k', 'e'}));
|
||||
|
||||
auto const ownerDir = CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}}, INDEX1);
|
||||
auto const ownerDirKk = ripple::keylet::ownerDir(account).key;
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(ownerDirKk, 30, _)).WillOnce(Return(ownerDir.getSerializer().peekData()));
|
||||
|
||||
// nft null
|
||||
auto const nftMaxKK = ripple::keylet::nftpage_max(account).key;
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(nftMaxKK, 30, _)).WillOnce(Return(std::nullopt));
|
||||
|
||||
std::vector<Blob> bbs;
|
||||
// put 1 mpt issuance
|
||||
auto const issuanceObject = CreateMPTIssuanceObject(ACCOUNT, 2, "metadata");
|
||||
bbs.push_back(issuanceObject.getSerializer().peekData());
|
||||
|
||||
EXPECT_CALL(*backend, doFetchLedgerObjects).WillOnce(Return(bbs));
|
||||
|
||||
auto static const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"account": "{}",
|
||||
"type": "mpt_issuance"
|
||||
}})",
|
||||
ACCOUNT
|
||||
));
|
||||
|
||||
auto const handler = AnyHandler{AccountObjectsHandler{backend}};
|
||||
runSpawn([&](auto yield) {
|
||||
auto const output = handler.process(input, Context{yield});
|
||||
ASSERT_TRUE(output);
|
||||
auto const& accountObjects = output.result->as_object().at("account_objects").as_array();
|
||||
ASSERT_EQ(accountObjects.size(), 1);
|
||||
EXPECT_EQ(accountObjects.front().at("LedgerEntryType").as_string(), "MPTokenIssuance");
|
||||
|
||||
// make sure mptID is synethetically parsed if object is mptIssuance
|
||||
EXPECT_EQ(
|
||||
accountObjects.front().at("mpt_issuance_id").as_string(),
|
||||
ripple::to_string(ripple::makeMptID(2, GetAccountIDWithString(ACCOUNT)))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCAccountObjectsHandlerTest, TypeFilterMPTokenType)
|
||||
{
|
||||
backend->setRange(MINSEQ, MAXSEQ);
|
||||
auto const ledgerinfo = CreateLedgerHeader(LEDGERHASH, MAXSEQ);
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(ledgerinfo));
|
||||
|
||||
auto const account = GetAccountIDWithString(ACCOUNT);
|
||||
auto const accountKk = ripple::keylet::account(account).key;
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(accountKk, MAXSEQ, _)).WillOnce(Return(Blob{'f', 'a', 'k', 'e'}));
|
||||
|
||||
auto const ownerDir = CreateOwnerDirLedgerObject({ripple::uint256{INDEX1}}, INDEX1);
|
||||
auto const ownerDirKk = ripple::keylet::ownerDir(account).key;
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(ownerDirKk, 30, _)).WillOnce(Return(ownerDir.getSerializer().peekData()));
|
||||
|
||||
// nft null
|
||||
auto const nftMaxKK = ripple::keylet::nftpage_max(account).key;
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(nftMaxKK, 30, _)).WillOnce(Return(std::nullopt));
|
||||
|
||||
std::vector<Blob> bbs;
|
||||
// put 1 mpt issuance
|
||||
auto const mptokenObject = CreateMPTokenObject(ACCOUNT, ripple::makeMptID(2, GetAccountIDWithString(ACCOUNT)));
|
||||
bbs.push_back(mptokenObject.getSerializer().peekData());
|
||||
|
||||
EXPECT_CALL(*backend, doFetchLedgerObjects).WillOnce(Return(bbs));
|
||||
|
||||
auto static const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"account": "{}",
|
||||
"type": "mptoken"
|
||||
}})",
|
||||
ACCOUNT
|
||||
));
|
||||
|
||||
auto const handler = AnyHandler{AccountObjectsHandler{backend}};
|
||||
runSpawn([&](auto yield) {
|
||||
auto const output = handler.process(input, Context{yield});
|
||||
ASSERT_TRUE(output);
|
||||
auto const& accountObjects = output.result->as_object().at("account_objects").as_array();
|
||||
ASSERT_EQ(accountObjects.size(), 1);
|
||||
EXPECT_EQ(accountObjects.front().at("LedgerEntryType").as_string(), "MPToken");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -729,6 +729,88 @@ TEST_F(RPCLedgerDataHandlerTest, JsonLimitMoreThanMax)
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCLedgerDataHandlerTest, TypeFilterMPTIssuance)
|
||||
{
|
||||
backend->setRange(RANGEMIN, RANGEMAX);
|
||||
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence).Times(1);
|
||||
ON_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _))
|
||||
.WillByDefault(Return(CreateLedgerHeader(LEDGERHASH, RANGEMAX)));
|
||||
|
||||
std::vector<Blob> bbs;
|
||||
EXPECT_CALL(*backend, doFetchSuccessorKey).Times(1);
|
||||
ON_CALL(*backend, doFetchSuccessorKey(_, RANGEMAX, _)).WillByDefault(Return(ripple::uint256{INDEX2}));
|
||||
|
||||
auto const issuance = CreateMPTIssuanceObject(ACCOUNT, 2, "metadata");
|
||||
bbs.push_back(issuance.getSerializer().peekData());
|
||||
|
||||
ON_CALL(*backend, doFetchLedgerObjects).WillByDefault(Return(bbs));
|
||||
EXPECT_CALL(*backend, doFetchLedgerObjects).Times(1);
|
||||
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{LedgerDataHandler{backend}};
|
||||
auto const req = json::parse(R"({
|
||||
"limit":1,
|
||||
"type":"mpt_issuance"
|
||||
})");
|
||||
|
||||
auto output = handler.process(req, Context{yield});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_TRUE(output.result->as_object().contains("ledger"));
|
||||
EXPECT_EQ(output.result->as_object().at("state").as_array().size(), 1);
|
||||
EXPECT_EQ(output.result->as_object().at("marker").as_string(), INDEX2);
|
||||
EXPECT_EQ(output.result->as_object().at("ledger_hash").as_string(), LEDGERHASH);
|
||||
EXPECT_EQ(output.result->as_object().at("ledger_index").as_uint64(), RANGEMAX);
|
||||
|
||||
auto const& objects = output.result->as_object().at("state").as_array();
|
||||
EXPECT_EQ(objects.front().at("LedgerEntryType").as_string(), "MPTokenIssuance");
|
||||
|
||||
// make sure mptID is synethetically parsed if object is mptIssuance
|
||||
EXPECT_EQ(
|
||||
objects.front().at("mpt_issuance_id").as_string(),
|
||||
ripple::to_string(ripple::makeMptID(2, GetAccountIDWithString(ACCOUNT)))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCLedgerDataHandlerTest, TypeFilterMPToken)
|
||||
{
|
||||
backend->setRange(RANGEMIN, RANGEMAX);
|
||||
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence).Times(1);
|
||||
ON_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _))
|
||||
.WillByDefault(Return(CreateLedgerHeader(LEDGERHASH, RANGEMAX)));
|
||||
|
||||
std::vector<Blob> bbs;
|
||||
EXPECT_CALL(*backend, doFetchSuccessorKey).Times(1);
|
||||
ON_CALL(*backend, doFetchSuccessorKey(_, RANGEMAX, _)).WillByDefault(Return(ripple::uint256{INDEX2}));
|
||||
|
||||
auto const mptoken = CreateMPTokenObject(ACCOUNT, ripple::makeMptID(2, GetAccountIDWithString(ACCOUNT)));
|
||||
bbs.push_back(mptoken.getSerializer().peekData());
|
||||
|
||||
ON_CALL(*backend, doFetchLedgerObjects).WillByDefault(Return(bbs));
|
||||
EXPECT_CALL(*backend, doFetchLedgerObjects).Times(1);
|
||||
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{LedgerDataHandler{backend}};
|
||||
auto const req = json::parse(R"({
|
||||
"limit":1,
|
||||
"type":"mptoken"
|
||||
})");
|
||||
|
||||
auto output = handler.process(req, Context{yield});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_TRUE(output.result->as_object().contains("ledger"));
|
||||
EXPECT_EQ(output.result->as_object().at("state").as_array().size(), 1);
|
||||
EXPECT_EQ(output.result->as_object().at("marker").as_string(), INDEX2);
|
||||
EXPECT_EQ(output.result->as_object().at("ledger_hash").as_string(), LEDGERHASH);
|
||||
EXPECT_EQ(output.result->as_object().at("ledger_index").as_uint64(), RANGEMAX);
|
||||
|
||||
auto const& objects = output.result->as_object().at("state").as_array();
|
||||
EXPECT_EQ(objects.front().at("LedgerEntryType").as_string(), "MPToken");
|
||||
});
|
||||
}
|
||||
|
||||
TEST(RPCLedgerDataHandlerSpecTest, DeprecatedFields)
|
||||
{
|
||||
boost::json::value const json{
|
||||
|
||||
@@ -1759,6 +1759,76 @@ generateTestValuesForParametersTest()
|
||||
"malformedRequest",
|
||||
"Malformed request."
|
||||
},
|
||||
ParamTestCaseBundle{
|
||||
"InvalidMPTIssuanceStringIndex",
|
||||
R"({
|
||||
"mpt_issuance": "invalid"
|
||||
})",
|
||||
"malformedRequest",
|
||||
"Malformed request."
|
||||
},
|
||||
ParamTestCaseBundle{
|
||||
"InvalidMPTIssuanceType",
|
||||
R"({
|
||||
"mpt_issuance": 0
|
||||
})",
|
||||
"malformedRequest",
|
||||
"Malformed request."
|
||||
},
|
||||
ParamTestCaseBundle{
|
||||
"InvalidMPTokenStringIndex",
|
||||
R"({
|
||||
"mptoken": "invalid"
|
||||
})",
|
||||
"malformedRequest",
|
||||
"Malformed request."
|
||||
},
|
||||
ParamTestCaseBundle{
|
||||
"InvalidMPTokenObject",
|
||||
fmt::format(
|
||||
R"({{
|
||||
"mptoken": {{}}
|
||||
}})"
|
||||
),
|
||||
"malformedRequest",
|
||||
"Malformed request."
|
||||
},
|
||||
ParamTestCaseBundle{
|
||||
"MissingMPTokenID",
|
||||
fmt::format(
|
||||
R"({{
|
||||
"mptoken": {{
|
||||
"account": "{}"
|
||||
}}
|
||||
}})",
|
||||
ACCOUNT
|
||||
),
|
||||
"malformedRequest",
|
||||
"Malformed request."
|
||||
},
|
||||
ParamTestCaseBundle{
|
||||
"InvalidMPTokenAccount",
|
||||
fmt::format(
|
||||
R"({{
|
||||
"mptoken": {{
|
||||
"mpt_issuance_id": "0000019315EABA24E6135A4B5CE2899E0DA791206413B33D",
|
||||
"account": 1
|
||||
}}
|
||||
}})"
|
||||
),
|
||||
"malformedAddress",
|
||||
"Malformed address."
|
||||
},
|
||||
ParamTestCaseBundle{
|
||||
"InvalidMPTokenType",
|
||||
fmt::format(
|
||||
R"({{
|
||||
"mptoken": 0
|
||||
}})"
|
||||
),
|
||||
"malformedRequest",
|
||||
"Malformed request."
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2397,6 +2467,46 @@ generateTestValuesForNormalPathTest()
|
||||
)
|
||||
)
|
||||
},
|
||||
NormalPathTestBundle{
|
||||
"MPTIssuance",
|
||||
fmt::format(
|
||||
R"({{
|
||||
"binary": true,
|
||||
"mpt_issuance": "{}"
|
||||
}})",
|
||||
ripple::to_string(ripple::makeMptID(2, account1))
|
||||
),
|
||||
ripple::keylet::mptIssuance(ripple::makeMptID(2, account1)).key,
|
||||
CreateMPTIssuanceObject(ACCOUNT, 2, "metadata")
|
||||
},
|
||||
NormalPathTestBundle{
|
||||
"MPTokenViaIndex",
|
||||
fmt::format(
|
||||
R"({{
|
||||
"binary": true,
|
||||
"mptoken": "{}"
|
||||
}})",
|
||||
INDEX1
|
||||
),
|
||||
ripple::uint256{INDEX1},
|
||||
CreateMPTokenObject(ACCOUNT, ripple::makeMptID(2, account1))
|
||||
},
|
||||
NormalPathTestBundle{
|
||||
"MPTokenViaObject",
|
||||
fmt::format(
|
||||
R"({{
|
||||
"binary": true,
|
||||
"mptoken": {{
|
||||
"account": "{}",
|
||||
"mpt_issuance_id": "{}"
|
||||
}}
|
||||
}})",
|
||||
ACCOUNT,
|
||||
ripple::to_string(ripple::makeMptID(2, account1))
|
||||
),
|
||||
ripple::keylet::mptoken(ripple::makeMptID(2, account1), account1).key,
|
||||
CreateMPTokenObject(ACCOUNT, ripple::makeMptID(2, account1))
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2944,6 +3054,56 @@ TEST_F(RPCLedgerEntryTest, ObjectSeqNotExist)
|
||||
});
|
||||
}
|
||||
|
||||
// this testcase will test the if response includes synthetic mpt_issuance_id
|
||||
TEST_F(RPCLedgerEntryTest, SyntheticMPTIssuanceID)
|
||||
{
|
||||
static auto constexpr OUT = R"({
|
||||
"ledger_hash":"4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652",
|
||||
"ledger_index":30,
|
||||
"validated":true,
|
||||
"index":"FD7E7EFAE2A20E75850D0E0590B205E2F74DC472281768CD6E03988069816336",
|
||||
"node":{
|
||||
"Flags":0,
|
||||
"Issuer":"rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn",
|
||||
"LedgerEntryType":"MPTokenIssuance",
|
||||
"MPTokenMetadata":"6D65746164617461",
|
||||
"MaximumAmount":"0",
|
||||
"OutstandingAmount":"0",
|
||||
"OwnerNode":"0",
|
||||
"PreviousTxnID":"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"PreviousTxnLgrSeq":0,
|
||||
"Sequence":2,
|
||||
"index":"FD7E7EFAE2A20E75850D0E0590B205E2F74DC472281768CD6E03988069816336",
|
||||
"mpt_issuance_id":"000000024B4E9C06F24296074F7BC48F92A97916C6DC5EA9"
|
||||
}
|
||||
})";
|
||||
|
||||
auto const mptId = ripple::makeMptID(2, GetAccountIDWithString(ACCOUNT));
|
||||
|
||||
backend->setRange(RANGEMIN, RANGEMAX);
|
||||
// return valid ledgerHeader
|
||||
auto const ledgerHeader = CreateLedgerHeader(LEDGERHASH, RANGEMAX);
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence(RANGEMAX, _)).WillRepeatedly(Return(ledgerHeader));
|
||||
|
||||
// return valid ledger entry which can be deserialized
|
||||
auto const ledgerEntry = CreateMPTIssuanceObject(ACCOUNT, 2, "metadata");
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject(ripple::keylet::mptIssuance(mptId).key, RANGEMAX, _))
|
||||
.WillRepeatedly(Return(ledgerEntry.getSerializer().peekData()));
|
||||
|
||||
runSpawn([&, this](auto yield) {
|
||||
auto const handler = AnyHandler{LedgerEntryHandler{backend}};
|
||||
auto const req = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance": "{}"
|
||||
}})",
|
||||
ripple::to_string(mptId)
|
||||
));
|
||||
auto const output = handler.process(req, Context{yield});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(*output.result, json::parse(OUT));
|
||||
});
|
||||
}
|
||||
|
||||
using RPCLedgerEntryDeathTest = RPCLedgerEntryTest;
|
||||
|
||||
TEST_F(RPCLedgerEntryDeathTest, RangeNotAvailable)
|
||||
|
||||
651
tests/unit/rpc/handlers/MPTHoldersTests.cpp
Normal file
651
tests/unit/rpc/handlers/MPTHoldersTests.cpp
Normal file
@@ -0,0 +1,651 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 "data/Types.hpp"
|
||||
#include "rpc/Errors.hpp"
|
||||
#include "rpc/common/AnyHandler.hpp"
|
||||
#include "rpc/common/Types.hpp"
|
||||
#include "rpc/handlers/MPTHolders.hpp"
|
||||
#include "util/HandlerBaseTestFixture.hpp"
|
||||
#include "util/TestObject.hpp"
|
||||
|
||||
#include <boost/asio/spawn.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <ripple/basics/base_uint.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/protocol/LedgerHeader.h>
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace rpc;
|
||||
namespace json = boost::json;
|
||||
using namespace testing;
|
||||
|
||||
// constexpr static auto ISSUER_ACCOUNT = "rsS8ju2jYabSKJ6uzLarAS1gEzvRQ6JAiF";
|
||||
constexpr static auto HOLDER1_ACCOUNT = "rrnAZCqMahreZrKMcZU3t2DZ6yUndT4ubN";
|
||||
constexpr static auto HOLDER2_ACCOUNT = "rEiNkzogdHEzUxPfsri5XSMqtXUixf2Yx";
|
||||
constexpr static auto LEDGERHASH = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652";
|
||||
constexpr static auto MPTID = "000004C463C52827307480341125DA0577DEFC38405B0E3E";
|
||||
|
||||
static std::string MPTOUT1 =
|
||||
R"({
|
||||
"account": "rrnAZCqMahreZrKMcZU3t2DZ6yUndT4ubN",
|
||||
"flags": 0,
|
||||
"mpt_amount": "1",
|
||||
"mptoken_index": "D137F2E5A5767A06CB7A8F060ADE442A30CFF95028E1AF4B8767E3A56877205A"
|
||||
})";
|
||||
|
||||
static std::string MPTOUT2 =
|
||||
R"({
|
||||
"account": "rEiNkzogdHEzUxPfsri5XSMqtXUixf2Yx",
|
||||
"flags": 0,
|
||||
"mpt_amount": "1",
|
||||
"mptoken_index": "36D91DEE5EFE4A93119A8B84C944A528F2B444329F3846E49FE921040DE17E65"
|
||||
})";
|
||||
|
||||
class RPCMPTHoldersHandlerTest : public HandlerBaseTest {};
|
||||
|
||||
TEST_F(RPCMPTHoldersHandlerTest, NonHexLedgerHash)
|
||||
{
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
auto const handler = AnyHandler{MPTHoldersHandler{backend}};
|
||||
auto const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"ledger_hash": "xxx"
|
||||
}})",
|
||||
MPTID
|
||||
));
|
||||
auto const output = handler.process(input, Context{std::ref(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(), "ledger_hashMalformed");
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCMPTHoldersHandlerTest, NonStringLedgerHash)
|
||||
{
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
auto const handler = AnyHandler{MPTHoldersHandler{backend}};
|
||||
auto const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"ledger_hash": 123
|
||||
}})",
|
||||
MPTID
|
||||
));
|
||||
auto const output = handler.process(input, Context{std::ref(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(), "ledger_hashNotString");
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCMPTHoldersHandlerTest, InvalidLedgerIndexString)
|
||||
{
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
auto const handler = AnyHandler{MPTHoldersHandler{backend}};
|
||||
auto const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"ledger_index": "notvalidated"
|
||||
}})",
|
||||
MPTID
|
||||
));
|
||||
auto const output = handler.process(input, Context{std::ref(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(), "ledgerIndexMalformed");
|
||||
});
|
||||
}
|
||||
|
||||
// error case: issuer invalid format, length is incorrect
|
||||
TEST_F(RPCMPTHoldersHandlerTest, MPTIDInvalidFormat)
|
||||
{
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
auto const handler = AnyHandler{MPTHoldersHandler{backend}};
|
||||
auto const input = json::parse(R"({
|
||||
"mpt_issuance_id": "xxx"
|
||||
})");
|
||||
auto const output = handler.process(input, Context{std::ref(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(), "mpt_issuance_idMalformed");
|
||||
});
|
||||
}
|
||||
|
||||
// error case: issuer missing
|
||||
TEST_F(RPCMPTHoldersHandlerTest, MPTIDMissing)
|
||||
{
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
auto const handler = AnyHandler{MPTHoldersHandler{backend}};
|
||||
auto const input = json::parse(R"({})");
|
||||
auto const output = handler.process(input, Context{std::ref(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(), "Required field 'mpt_issuance_id' missing");
|
||||
});
|
||||
}
|
||||
|
||||
// error case: issuer invalid format
|
||||
TEST_F(RPCMPTHoldersHandlerTest, MPTIDNotString)
|
||||
{
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
auto const handler = AnyHandler{MPTHoldersHandler{backend}};
|
||||
auto const input = json::parse(R"({
|
||||
"mpt_issuance_id": 12
|
||||
})");
|
||||
auto const output = handler.process(input, Context{std::ref(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(), "mpt_issuance_idNotString");
|
||||
});
|
||||
}
|
||||
|
||||
// error case: invalid marker format
|
||||
TEST_F(RPCMPTHoldersHandlerTest, MarkerInvalidFormat)
|
||||
{
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
auto const handler = AnyHandler{MPTHoldersHandler{backend}};
|
||||
auto const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"marker": "xxx"
|
||||
}})",
|
||||
MPTID
|
||||
));
|
||||
auto const output = handler.process(input, Context{std::ref(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(), "markerMalformed");
|
||||
});
|
||||
}
|
||||
|
||||
// error case: invalid marker type
|
||||
TEST_F(RPCMPTHoldersHandlerTest, MarkerNotString)
|
||||
{
|
||||
runSpawn([this](boost::asio::yield_context yield) {
|
||||
auto const handler = AnyHandler{MPTHoldersHandler{backend}};
|
||||
auto const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"marker": 1
|
||||
}})",
|
||||
MPTID
|
||||
));
|
||||
auto const output = handler.process(input, Context{std::ref(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(), "markerNotString");
|
||||
});
|
||||
}
|
||||
|
||||
// error case ledger non exist via hash
|
||||
TEST_F(RPCMPTHoldersHandlerTest, NonExistLedgerViaLedgerHash)
|
||||
{
|
||||
// mock fetchLedgerByHash return empty
|
||||
EXPECT_CALL(*backend, fetchLedgerByHash).Times(1);
|
||||
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _))
|
||||
.WillByDefault(Return(std::optional<ripple::LedgerInfo>{}));
|
||||
|
||||
auto const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"ledger_hash": "{}"
|
||||
}})",
|
||||
MPTID,
|
||||
LEDGERHASH
|
||||
));
|
||||
runSpawn([&, this](boost::asio::yield_context yield) {
|
||||
auto const handler = AnyHandler{MPTHoldersHandler{backend}};
|
||||
auto const output = handler.process(input, Context{std::ref(yield)});
|
||||
ASSERT_FALSE(output);
|
||||
|
||||
auto const err = rpc::makeError(output.result.error());
|
||||
EXPECT_EQ(err.at("error").as_string(), "lgrNotFound");
|
||||
EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound");
|
||||
});
|
||||
}
|
||||
|
||||
// error case ledger non exist via index
|
||||
TEST_F(RPCMPTHoldersHandlerTest, NonExistLedgerViaLedgerStringIndex)
|
||||
{
|
||||
backend->setRange(10, 30);
|
||||
// mock fetchLedgerBySequence return empty
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(std::optional<ripple::LedgerInfo>{}));
|
||||
auto const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"ledger_index": "4"
|
||||
}})",
|
||||
MPTID
|
||||
));
|
||||
runSpawn([&, this](boost::asio::yield_context yield) {
|
||||
auto const handler = AnyHandler{MPTHoldersHandler{backend}};
|
||||
auto const output = handler.process(input, Context{std::ref(yield)});
|
||||
ASSERT_FALSE(output);
|
||||
auto const err = rpc::makeError(output.result.error());
|
||||
EXPECT_EQ(err.at("error").as_string(), "lgrNotFound");
|
||||
EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound");
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCMPTHoldersHandlerTest, NonExistLedgerViaLedgerIntIndex)
|
||||
{
|
||||
backend->setRange(10, 30);
|
||||
// mock fetchLedgerBySequence return empty
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(std::optional<ripple::LedgerInfo>{}));
|
||||
auto const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"ledger_index": 4
|
||||
}})",
|
||||
MPTID
|
||||
));
|
||||
runSpawn([&, this](boost::asio::yield_context yield) {
|
||||
auto const handler = AnyHandler{MPTHoldersHandler{backend}};
|
||||
auto const output = handler.process(input, Context{std::ref(yield)});
|
||||
ASSERT_FALSE(output);
|
||||
auto const err = rpc::makeError(output.result.error());
|
||||
EXPECT_EQ(err.at("error").as_string(), "lgrNotFound");
|
||||
EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound");
|
||||
});
|
||||
}
|
||||
|
||||
// error case ledger > max seq via hash
|
||||
// idk why this case will happen in reality
|
||||
TEST_F(RPCMPTHoldersHandlerTest, NonExistLedgerViaLedgerHash2)
|
||||
{
|
||||
backend->setRange(10, 30);
|
||||
// mock fetchLedgerByHash return ledger but seq is 31 > 30
|
||||
auto ledgerinfo = CreateLedgerHeader(LEDGERHASH, 31);
|
||||
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerinfo));
|
||||
EXPECT_CALL(*backend, fetchLedgerByHash).Times(1);
|
||||
auto const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"ledger_hash": "{}"
|
||||
}})",
|
||||
MPTID,
|
||||
LEDGERHASH
|
||||
));
|
||||
runSpawn([&, this](boost::asio::yield_context yield) {
|
||||
auto const handler = AnyHandler{MPTHoldersHandler{backend}};
|
||||
auto const output = handler.process(input, Context{std::ref(yield)});
|
||||
ASSERT_FALSE(output);
|
||||
auto const err = rpc::makeError(output.result.error());
|
||||
EXPECT_EQ(err.at("error").as_string(), "lgrNotFound");
|
||||
EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound");
|
||||
});
|
||||
}
|
||||
|
||||
// error case ledger > max seq via index
|
||||
TEST_F(RPCMPTHoldersHandlerTest, NonExistLedgerViaLedgerIndex2)
|
||||
{
|
||||
backend->setRange(10, 30);
|
||||
// no need to check from db,call fetchLedgerBySequence 0 time
|
||||
// differ from previous logic
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence).Times(0);
|
||||
auto const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"ledger_index": "31"
|
||||
}})",
|
||||
MPTID
|
||||
));
|
||||
runSpawn([&, this](boost::asio::yield_context yield) {
|
||||
auto const handler = AnyHandler{MPTHoldersHandler{backend}};
|
||||
auto const output = handler.process(input, Context{std::ref(yield)});
|
||||
ASSERT_FALSE(output);
|
||||
auto const err = rpc::makeError(output.result.error());
|
||||
EXPECT_EQ(err.at("error").as_string(), "lgrNotFound");
|
||||
EXPECT_EQ(err.at("error_message").as_string(), "ledgerNotFound");
|
||||
});
|
||||
}
|
||||
|
||||
// normal case when MPT does not exist
|
||||
TEST_F(RPCMPTHoldersHandlerTest, MPTNotFound)
|
||||
{
|
||||
backend->setRange(10, 30);
|
||||
auto ledgerinfo = CreateLedgerHeader(LEDGERHASH, 30);
|
||||
ON_CALL(*backend, fetchLedgerByHash(ripple::uint256{LEDGERHASH}, _)).WillByDefault(Return(ledgerinfo));
|
||||
EXPECT_CALL(*backend, fetchLedgerByHash).Times(1);
|
||||
ON_CALL(*backend, doFetchLedgerObject).WillByDefault(Return(std::optional<Blob>{}));
|
||||
EXPECT_CALL(*backend, doFetchLedgerObject).Times(1);
|
||||
|
||||
auto const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"ledger_hash": "{}"
|
||||
}})",
|
||||
MPTID,
|
||||
LEDGERHASH
|
||||
));
|
||||
runSpawn([&, this](boost::asio::yield_context yield) {
|
||||
auto handler = AnyHandler{MPTHoldersHandler{this->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(), "objectNotFound");
|
||||
EXPECT_EQ(err.at("error_message").as_string(), "objectNotFound");
|
||||
});
|
||||
}
|
||||
|
||||
// normal case when mpt has one holder
|
||||
TEST_F(RPCMPTHoldersHandlerTest, DefaultParameters)
|
||||
{
|
||||
auto const currentOutput = fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"limit":50,
|
||||
"ledger_index": 30,
|
||||
"mptokens": [{}],
|
||||
"validated": true
|
||||
}})",
|
||||
MPTID,
|
||||
MPTOUT1
|
||||
);
|
||||
|
||||
backend->setRange(10, 30);
|
||||
auto ledgerInfo = CreateLedgerHeader(LEDGERHASH, 30);
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(ledgerInfo));
|
||||
auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key;
|
||||
ON_CALL(*backend, doFetchLedgerObject(issuanceKk, 30, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
|
||||
|
||||
auto const mptoken = CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID));
|
||||
std::vector<Blob> const mpts = {mptoken.getSerializer().peekData()};
|
||||
ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, {}}));
|
||||
EXPECT_CALL(
|
||||
*backend, fetchMPTHolders(ripple::uint192(MPTID), testing::_, testing::Eq(std::nullopt), Const(30), testing::_)
|
||||
)
|
||||
.Times(1);
|
||||
|
||||
auto const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}"
|
||||
}})",
|
||||
MPTID
|
||||
));
|
||||
runSpawn([&, this](auto& yield) {
|
||||
auto handler = AnyHandler{MPTHoldersHandler{this->backend}};
|
||||
auto const output = handler.process(input, Context{yield});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(json::parse(currentOutput), *output.result);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCMPTHoldersHandlerTest, CustomAmounts)
|
||||
{
|
||||
// it's not possible to have locked_amount to be greater than mpt_amount,
|
||||
// we are simply testing the response parsing of the api
|
||||
auto const currentOutput = fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"limit":50,
|
||||
"ledger_index": 30,
|
||||
"mptokens": [{{
|
||||
"account": "rrnAZCqMahreZrKMcZU3t2DZ6yUndT4ubN",
|
||||
"flags": 0,
|
||||
"mpt_amount": "0",
|
||||
"mptoken_index": "D137F2E5A5767A06CB7A8F060ADE442A30CFF95028E1AF4B8767E3A56877205A"
|
||||
}}],
|
||||
"validated": true
|
||||
}})",
|
||||
MPTID
|
||||
);
|
||||
|
||||
backend->setRange(10, 30);
|
||||
auto ledgerInfo = CreateLedgerHeader(LEDGERHASH, 30);
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(ledgerInfo));
|
||||
auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key;
|
||||
ON_CALL(*backend, doFetchLedgerObject(issuanceKk, 30, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
|
||||
|
||||
auto const mptoken = CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID), 0);
|
||||
std::vector<Blob> const mpts = {mptoken.getSerializer().peekData()};
|
||||
ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, {}}));
|
||||
EXPECT_CALL(
|
||||
*backend, fetchMPTHolders(ripple::uint192(MPTID), testing::_, testing::Eq(std::nullopt), Const(30), testing::_)
|
||||
)
|
||||
.Times(1);
|
||||
|
||||
auto const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}"
|
||||
}})",
|
||||
MPTID
|
||||
));
|
||||
runSpawn([&, this](auto& yield) {
|
||||
auto handler = AnyHandler{MPTHoldersHandler{this->backend}};
|
||||
auto const output = handler.process(input, Context{yield});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(json::parse(currentOutput), *output.result);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCMPTHoldersHandlerTest, SpecificLedgerIndex)
|
||||
{
|
||||
auto const specificLedger = 20;
|
||||
auto const currentOutput = fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"limit":50,
|
||||
"ledger_index": {},
|
||||
"mptokens": [{}],
|
||||
"validated": true
|
||||
}})",
|
||||
MPTID,
|
||||
specificLedger,
|
||||
MPTOUT1
|
||||
);
|
||||
|
||||
backend->setRange(10, 30);
|
||||
auto ledgerInfo = CreateLedgerHeader(LEDGERHASH, specificLedger);
|
||||
ON_CALL(*backend, fetchLedgerBySequence(specificLedger, _)).WillByDefault(Return(ledgerInfo));
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence).Times(1);
|
||||
auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key;
|
||||
ON_CALL(*backend, doFetchLedgerObject(issuanceKk, specificLedger, _))
|
||||
.WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
|
||||
|
||||
auto const mptoken = CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID));
|
||||
std::vector<Blob> const mpts = {mptoken.getSerializer().peekData()};
|
||||
ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, {}}));
|
||||
EXPECT_CALL(
|
||||
*backend,
|
||||
fetchMPTHolders(
|
||||
ripple::uint192(MPTID), testing::_, testing::Eq(std::nullopt), Const(specificLedger), testing::_
|
||||
)
|
||||
)
|
||||
.Times(1);
|
||||
|
||||
auto const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"ledger_index": {}
|
||||
}})",
|
||||
MPTID,
|
||||
specificLedger
|
||||
));
|
||||
runSpawn([&, this](auto& yield) {
|
||||
auto handler = AnyHandler{MPTHoldersHandler{this->backend}};
|
||||
auto const output = handler.process(input, Context{yield});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(json::parse(currentOutput), *output.result);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCMPTHoldersHandlerTest, MarkerParameter)
|
||||
{
|
||||
auto const currentOutput = fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"limit":50,
|
||||
"ledger_index": 30,
|
||||
"mptokens": [{}],
|
||||
"validated": true,
|
||||
"marker": "{}"
|
||||
}})",
|
||||
MPTID,
|
||||
MPTOUT2,
|
||||
ripple::strHex(GetAccountIDWithString(HOLDER1_ACCOUNT))
|
||||
);
|
||||
|
||||
backend->setRange(10, 30);
|
||||
auto ledgerInfo = CreateLedgerHeader(LEDGERHASH, 30);
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(ledgerInfo));
|
||||
auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key;
|
||||
ON_CALL(*backend, doFetchLedgerObject(issuanceKk, 30, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
|
||||
|
||||
auto const mptoken = CreateMPTokenObject(HOLDER2_ACCOUNT, ripple::uint192(MPTID));
|
||||
std::vector<Blob> const mpts = {mptoken.getSerializer().peekData()};
|
||||
auto const marker = GetAccountIDWithString(HOLDER1_ACCOUNT);
|
||||
ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, marker}));
|
||||
EXPECT_CALL(
|
||||
*backend, fetchMPTHolders(ripple::uint192(MPTID), testing::_, testing::Eq(marker), Const(30), testing::_)
|
||||
)
|
||||
.Times(1);
|
||||
|
||||
auto const HOLDER1_ACCOUNTID = ripple::strHex(GetAccountIDWithString(HOLDER1_ACCOUNT));
|
||||
auto const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"marker": "{}"
|
||||
}})",
|
||||
MPTID,
|
||||
HOLDER1_ACCOUNTID
|
||||
));
|
||||
runSpawn([&, this](auto& yield) {
|
||||
auto handler = AnyHandler{MPTHoldersHandler{this->backend}};
|
||||
auto const output = handler.process(input, Context{yield});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(json::parse(currentOutput), *output.result);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCMPTHoldersHandlerTest, MultipleMPTs)
|
||||
{
|
||||
auto const currentOutput = fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"limit":50,
|
||||
"ledger_index": 30,
|
||||
"mptokens": [{}, {}],
|
||||
"validated": true
|
||||
}})",
|
||||
MPTID,
|
||||
MPTOUT1,
|
||||
MPTOUT2
|
||||
);
|
||||
|
||||
backend->setRange(10, 30);
|
||||
auto ledgerInfo = CreateLedgerHeader(LEDGERHASH, 30);
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(ledgerInfo));
|
||||
auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key;
|
||||
ON_CALL(*backend, doFetchLedgerObject(issuanceKk, 30, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
|
||||
|
||||
auto const mptoken1 = CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID));
|
||||
auto const mptoken2 = CreateMPTokenObject(HOLDER2_ACCOUNT, ripple::uint192(MPTID));
|
||||
std::vector<Blob> const mpts = {mptoken1.getSerializer().peekData(), mptoken2.getSerializer().peekData()};
|
||||
ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, {}}));
|
||||
EXPECT_CALL(
|
||||
*backend, fetchMPTHolders(ripple::uint192(MPTID), testing::_, testing::Eq(std::nullopt), Const(30), testing::_)
|
||||
)
|
||||
.Times(1);
|
||||
|
||||
auto const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}"
|
||||
}})",
|
||||
MPTID
|
||||
));
|
||||
runSpawn([&, this](auto& yield) {
|
||||
auto handler = AnyHandler{MPTHoldersHandler{this->backend}};
|
||||
auto const output = handler.process(input, Context{yield});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(json::parse(currentOutput), *output.result);
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(RPCMPTHoldersHandlerTest, LimitMoreThanMAx)
|
||||
{
|
||||
auto const currentOutput = fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"limit":100,
|
||||
"ledger_index": 30,
|
||||
"mptokens": [{}],
|
||||
"validated": true
|
||||
}})",
|
||||
MPTID,
|
||||
MPTOUT1
|
||||
);
|
||||
|
||||
backend->setRange(10, 30);
|
||||
auto ledgerInfo = CreateLedgerHeader(LEDGERHASH, 30);
|
||||
EXPECT_CALL(*backend, fetchLedgerBySequence).WillOnce(Return(ledgerInfo));
|
||||
auto const issuanceKk = ripple::keylet::mptIssuance(ripple::uint192(MPTID)).key;
|
||||
ON_CALL(*backend, doFetchLedgerObject(issuanceKk, 30, _)).WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
|
||||
|
||||
auto const mptoken = CreateMPTokenObject(HOLDER1_ACCOUNT, ripple::uint192(MPTID));
|
||||
std::vector<Blob> const mpts = {mptoken.getSerializer().peekData()};
|
||||
ON_CALL(*backend, fetchMPTHolders).WillByDefault(Return(MPTHoldersAndCursor{mpts, {}}));
|
||||
EXPECT_CALL(
|
||||
*backend,
|
||||
fetchMPTHolders(
|
||||
ripple::uint192(MPTID),
|
||||
Const(MPTHoldersHandler::LIMIT_MAX),
|
||||
testing::Eq(std::nullopt),
|
||||
Const(30),
|
||||
testing::_
|
||||
)
|
||||
)
|
||||
.Times(1);
|
||||
|
||||
auto const input = json::parse(fmt::format(
|
||||
R"({{
|
||||
"mpt_issuance_id": "{}",
|
||||
"limit": {}
|
||||
}})",
|
||||
MPTID,
|
||||
MPTHoldersHandler::LIMIT_MAX + 1
|
||||
));
|
||||
runSpawn([&, this](auto& yield) {
|
||||
auto handler = AnyHandler{MPTHoldersHandler{this->backend}};
|
||||
auto const output = handler.process(input, Context{yield});
|
||||
ASSERT_TRUE(output);
|
||||
EXPECT_EQ(json::parse(currentOutput), *output.result);
|
||||
});
|
||||
}
|
||||
@@ -52,6 +52,8 @@ TEST(LedgerUtilsTests, LedgerObjectTypeList)
|
||||
JS(xchain_owned_claim_id),
|
||||
JS(xchain_owned_create_account_claim_id),
|
||||
JS(did),
|
||||
JS(mpt_issuance),
|
||||
JS(mptoken),
|
||||
JS(oracle),
|
||||
JS(nunl)
|
||||
};
|
||||
@@ -83,7 +85,9 @@ TEST(LedgerUtilsTests, AccountOwnedTypeList)
|
||||
JS(xchain_owned_claim_id),
|
||||
JS(xchain_owned_create_account_claim_id),
|
||||
JS(did),
|
||||
JS(oracle)
|
||||
JS(oracle),
|
||||
JS(mpt_issuance),
|
||||
JS(mptoken)
|
||||
};
|
||||
|
||||
static_assert(std::size(correctTypes) == accountOwned.size());
|
||||
@@ -121,7 +125,9 @@ TEST(LedgerUtilsTests, DeletionBlockerTypes)
|
||||
ripple::ltRIPPLE_STATE,
|
||||
ripple::ltXCHAIN_OWNED_CLAIM_ID,
|
||||
ripple::ltXCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID,
|
||||
ripple::ltBRIDGE
|
||||
ripple::ltBRIDGE,
|
||||
ripple::ltMPTOKEN_ISSUANCE,
|
||||
ripple::ltMPTOKEN
|
||||
};
|
||||
|
||||
static_assert(std::size(deletionBlockers) == testedTypes.size());
|
||||
|
||||
Reference in New Issue
Block a user