fix: Output format for MPT Issuance ID (#3178)

This commit is contained in:
Alex Kremer
2026-08-24 15:15:50 +01:00
committed by GitHub
parent bdfbb78115
commit 3ad2ccddaa
2 changed files with 86 additions and 28 deletions

View File

@@ -2,6 +2,7 @@
#include "rpc/Errors.hpp"
#include "rpc/common/AnyHandler.hpp"
#include "rpc/common/Types.hpp"
#include "rpc/common/Validators.hpp"
#include "rpc/handlers/AccountMPTokenIssuances.hpp"
#include "util/HandlerBaseTestFixture.hpp"
#include "util/NameGenerator.hpp"
@@ -38,8 +39,8 @@ namespace {
constexpr auto kLedgerHash = "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A652";
constexpr auto kAccount = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn";
constexpr auto kIssuanceIndeX1 = "A6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321";
constexpr auto kIssuanceIndeX2 = "B6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC322";
constexpr auto kIssuanceIndex1 = "A6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321";
constexpr auto kIssuanceIndex2 = "B6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC322";
// unique values for issuance1
constexpr uint64_t kIssuancE1MaxAmount = 10000;
@@ -57,6 +58,12 @@ constexpr auto kIssuancE2MetadataHex = "746573742D6D657461";
constexpr auto kIssuancE2DomainIdHex =
"E6DBAFC99223B42257915A63DFC6B0C032D4070F9A574B255AD97466726FC321";
[[nodiscard]] std::string
expectedMptIssuanceId(std::uint32_t sequence)
{
return xrpl::to_string(xrpl::makeMptID(sequence, getAccountIdWithString(kAccount)));
}
// define expected JSON for mpt issuances
auto const kIssuanceOuT1 = fmt::format(
R"JSON({{
@@ -71,7 +78,7 @@ auto const kIssuanceOuT1 = fmt::format(
"mpt_require_auth": true,
"mpt_can_transfer": true
}})JSON",
kIssuanceIndeX1,
expectedMptIssuanceId(1),
kAccount,
kIssuancE1MaxAmount,
kIssuancE1OutstandingAmount,
@@ -93,7 +100,7 @@ auto const kIssuanceOuT2 = fmt::format(
"mpt_locked": true,
"mpt_can_clawback": true
}})JSON",
kIssuanceIndeX2,
expectedMptIssuanceId(2),
kAccount,
kIssuancE2MaxAmount,
kIssuancE2OutstandingAmount,
@@ -379,7 +386,7 @@ TEST_F(RPCAccountMPTokenIssuancesHandlerTest, DefaultParameters)
// return two mptoken issuance objects
xrpl::STObject const ownerDir = createOwnerDirLedgerObject(
{xrpl::uint256{kIssuanceIndeX1}, xrpl::uint256{kIssuanceIndeX2}}, kIssuanceIndeX1
{xrpl::uint256{kIssuanceIndex1}, xrpl::uint256{kIssuanceIndex2}}, kIssuanceIndex1
);
ON_CALL(*backend_, doFetchLedgerObject(ownerDirKk, _, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
@@ -459,7 +466,7 @@ TEST_F(RPCAccountMPTokenIssuancesHandlerTest, UseLimit)
ON_CALL(*backend_, doFetchLedgerObject(accountKk, _, _))
.WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
auto const indexes = std::vector<xrpl::uint256>(50, xrpl::uint256{kIssuanceIndeX1});
auto const indexes = std::vector<xrpl::uint256>(50, xrpl::uint256{kIssuanceIndex1});
auto const bbs = [&]() {
std::vector<Blob> v;
v.reserve(50);
@@ -469,7 +476,7 @@ TEST_F(RPCAccountMPTokenIssuancesHandlerTest, UseLimit)
return v;
}();
xrpl::STObject ownerDir = createOwnerDirLedgerObject(indexes, kIssuanceIndeX1);
xrpl::STObject ownerDir = createOwnerDirLedgerObject(indexes, kIssuanceIndex1);
ownerDir.setFieldU64(xrpl::sfIndexNext, 99);
ON_CALL(*backend_, doFetchLedgerObject(ownerDirKk, _, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
@@ -556,7 +563,7 @@ TEST_F(RPCAccountMPTokenIssuancesHandlerTest, MarkerOutput)
.WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
EXPECT_CALL(*backend_, doFetchLedgerObject).Times(3);
auto const indexes = std::vector<xrpl::uint256>(10, xrpl::uint256{kIssuanceIndeX1});
auto const indexes = std::vector<xrpl::uint256>(10, xrpl::uint256{kIssuanceIndex1});
auto const bbs = [&]() {
std::vector<Blob> v;
v.reserve(kLimit);
@@ -568,13 +575,13 @@ TEST_F(RPCAccountMPTokenIssuancesHandlerTest, MarkerOutput)
EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs));
// mock the first directory page
xrpl::STObject ownerDir1 = createOwnerDirLedgerObject(indexes, kIssuanceIndeX1);
xrpl::STObject ownerDir1 = createOwnerDirLedgerObject(indexes, kIssuanceIndex1);
ownerDir1.setFieldU64(xrpl::sfIndexNext, kNextPage);
ON_CALL(*backend_, doFetchLedgerObject(ownerDirKk, _, _))
.WillByDefault(Return(ownerDir1.getSerializer().peekData()));
// mock the second directory page
xrpl::STObject ownerDir2 = createOwnerDirLedgerObject(indexes, kIssuanceIndeX2);
xrpl::STObject ownerDir2 = createOwnerDirLedgerObject(indexes, kIssuanceIndex2);
ownerDir2.setFieldU64(xrpl::sfIndexNext, 0);
ON_CALL(*backend_, doFetchLedgerObject(ownerDir2Kk, _, _))
.WillByDefault(Return(ownerDir2.getSerializer().peekData()));
@@ -596,7 +603,7 @@ TEST_F(RPCAccountMPTokenIssuancesHandlerTest, MarkerOutput)
auto const& resultJson = output.result->as_object();
EXPECT_EQ(
boost::json::value_to<std::string>(resultJson.at("marker")),
fmt::format("{},{}", kIssuanceIndeX1, kNextPage)
fmt::format("{},{}", kIssuanceIndex1, kNextPage)
);
EXPECT_EQ(resultJson.at("mpt_issuances").as_array().size(), kLimit);
});
@@ -617,7 +624,7 @@ TEST_F(RPCAccountMPTokenIssuancesHandlerTest, MarkerInput)
.WillByDefault(Return(Blob{'f', 'a', 'k', 'e'}));
EXPECT_CALL(*backend_, doFetchLedgerObject).Times(3);
auto const indexes = std::vector<xrpl::uint256>(kLimit, xrpl::uint256{kIssuanceIndeX1});
auto const indexes = std::vector<xrpl::uint256>(kLimit, xrpl::uint256{kIssuanceIndex1});
auto const bbs = [&]() {
std::vector<Blob> v;
v.reserve(kLimit);
@@ -627,7 +634,7 @@ TEST_F(RPCAccountMPTokenIssuancesHandlerTest, MarkerInput)
return v;
}();
xrpl::STObject ownerDir = createOwnerDirLedgerObject(indexes, kIssuanceIndeX1);
xrpl::STObject ownerDir = createOwnerDirLedgerObject(indexes, kIssuanceIndex1);
ownerDir.setFieldU64(xrpl::sfIndexNext, 0);
ON_CALL(*backend_, doFetchLedgerObject(ownerDirKk, _, _))
.WillByDefault(Return(ownerDir.getSerializer().peekData()));
@@ -644,7 +651,7 @@ TEST_F(RPCAccountMPTokenIssuancesHandlerTest, MarkerInput)
}})JSON",
kAccount,
kLimit,
kIssuanceIndeX1,
kIssuanceIndex1,
kNextPage
)
);
@@ -670,7 +677,7 @@ TEST_F(RPCAccountMPTokenIssuancesHandlerTest, LimitLessThanMin)
.WillOnce(Return(Blob{'f', 'a', 'k', 'e'}));
xrpl::STObject const ownerDir = createOwnerDirLedgerObject(
{xrpl::uint256{kIssuanceIndeX1}, xrpl::uint256{kIssuanceIndeX2}}, kIssuanceIndeX1
{xrpl::uint256{kIssuanceIndex1}, xrpl::uint256{kIssuanceIndex2}}, kIssuanceIndex1
);
EXPECT_CALL(*backend_, doFetchLedgerObject(ownerDirKk, _, _))
.WillOnce(Return(ownerDir.getSerializer().peekData()));
@@ -758,7 +765,7 @@ TEST_F(RPCAccountMPTokenIssuancesHandlerTest, LimitMoreThanMax)
.WillOnce(Return(Blob{'f', 'a', 'k', 'e'}));
xrpl::STObject const ownerDir = createOwnerDirLedgerObject(
{xrpl::uint256{kIssuanceIndeX1}, xrpl::uint256{kIssuanceIndeX2}}, kIssuanceIndeX1
{xrpl::uint256{kIssuanceIndex1}, xrpl::uint256{kIssuanceIndex2}}, kIssuanceIndex1
);
EXPECT_CALL(*backend_, doFetchLedgerObject(ownerDirKk, _, _))
.WillOnce(Return(ownerDir.getSerializer().peekData()));
@@ -845,7 +852,7 @@ TEST_F(RPCAccountMPTokenIssuancesHandlerTest, EmptyResult)
EXPECT_CALL(*backend_, doFetchLedgerObject(accountKk, _, _))
.WillOnce(Return(Blob{'f', 'a', 'k', 'e'}));
xrpl::STObject const ownerDir = createOwnerDirLedgerObject({}, kIssuanceIndeX1);
xrpl::STObject const ownerDir = createOwnerDirLedgerObject({}, kIssuanceIndex1);
EXPECT_CALL(*backend_, doFetchLedgerObject(ownerDirKk, _, _))
.WillOnce(Return(ownerDir.getSerializer().peekData()));
@@ -913,7 +920,7 @@ TEST_P(AccountMPTokenIssuancesAmountSerializationTest, SerializedAsStrings)
.WillOnce(Return(Blob{'f', 'a', 'k', 'e'}));
xrpl::STObject const ownerDir =
createOwnerDirLedgerObject({xrpl::uint256{kIssuanceIndeX1}}, kIssuanceIndeX1);
createOwnerDirLedgerObject({xrpl::uint256{kIssuanceIndex1}}, kIssuanceIndex1);
EXPECT_CALL(*backend_, doFetchLedgerObject(ownerDirKk, _, _))
.WillOnce(Return(ownerDir.getSerializer().peekData()));
@@ -974,7 +981,7 @@ TEST_F(RPCAccountMPTokenIssuancesHandlerTest, ImmutableFlags)
.WillOnce(Return(Blob{'f', 'a', 'k', 'e'}));
xrpl::STObject const ownerDir = createOwnerDirLedgerObject(
{xrpl::uint256{kIssuanceIndeX1}, xrpl::uint256{kIssuanceIndeX2}}, kIssuanceIndeX1
{xrpl::uint256{kIssuanceIndex1}, xrpl::uint256{kIssuanceIndex2}}, kIssuanceIndex1
);
EXPECT_CALL(*backend_, doFetchLedgerObject(ownerDirKk, _, _))
.WillOnce(Return(ownerDir.getSerializer().peekData()));
@@ -1062,11 +1069,11 @@ TEST_F(RPCAccountMPTokenIssuancesHandlerTest, ImmutableFlags)
}})JSON",
kAccount,
kLedgerHash,
kIssuanceIndeX1,
expectedMptIssuanceId(3),
kAccount,
kIssuancE1OutstandingAmount,
kIssuancE1TransferFee,
kIssuanceIndeX2,
expectedMptIssuanceId(5),
kAccount,
kIssuancE2OutstandingAmount,
kIssuancE2TransferFee,
@@ -1096,7 +1103,7 @@ TEST_F(RPCAccountMPTokenIssuancesHandlerTest, ConfidentialFields)
.WillOnce(Return(Blob{'f', 'a', 'k', 'e'}));
xrpl::STObject const ownerDir =
createOwnerDirLedgerObject({xrpl::uint256{kIssuanceIndeX1}}, kIssuanceIndeX1);
createOwnerDirLedgerObject({xrpl::uint256{kIssuanceIndex1}}, kIssuanceIndex1);
EXPECT_CALL(*backend_, doFetchLedgerObject(ownerDirKk, _, _))
.WillOnce(Return(ownerDir.getSerializer().peekData()));
@@ -1151,7 +1158,7 @@ TEST_F(RPCAccountMPTokenIssuancesHandlerTest, ConfidentialFields)
}})JSON",
kAccount,
kLedgerHash,
kIssuanceIndeX1,
expectedMptIssuanceId(1),
kAccount,
kIssuancE1OutstandingAmount,
kConfidentialOutstandingAmount,
@@ -1166,6 +1173,54 @@ TEST_F(RPCAccountMPTokenIssuancesHandlerTest, ConfidentialFields)
});
}
TEST_F(RPCAccountMPTokenIssuancesHandlerTest, MPTokenIssuanceIdIsDerivedIdNotLedgerKey)
{
constexpr std::uint32_t kSequence = 7;
auto const ledgerHeader = createLedgerHeader(kLedgerHash, 30);
EXPECT_CALL(*backend_, fetchLedgerBySequence).WillOnce(Return(ledgerHeader));
auto const account = getAccountIdWithString(kAccount);
auto const accountKk = xrpl::keylet::account(account).key;
auto const ownerDirKk = xrpl::keylet::ownerDir(account).key;
EXPECT_CALL(*backend_, doFetchLedgerObject(accountKk, _, _))
.WillOnce(Return(Blob{'f', 'a', 'k', 'e'}));
xrpl::STObject const ownerDir =
createOwnerDirLedgerObject({xrpl::uint256{kIssuanceIndex1}}, kIssuanceIndex1);
EXPECT_CALL(*backend_, doFetchLedgerObject(ownerDirKk, _, _))
.WillOnce(Return(ownerDir.getSerializer().peekData()));
auto const bbs =
std::vector<Blob>{createMptIssuanceObject(kAccount, kSequence).getSerializer().peekData()};
EXPECT_CALL(*backend_, doFetchLedgerObjects).WillOnce(Return(bbs));
runSpawn([this](auto yield) {
auto const input =
boost::json::parse(fmt::format(R"JSON({{"account": "{}"}})JSON", kAccount));
auto const handler = AnyHandler{AccountMPTokenIssuancesHandler{this->backend_}};
auto const output = handler.process(input, Context{yield});
ASSERT_TRUE(output);
auto const& issuances = output.result->as_object().at("mpt_issuances").as_array();
ASSERT_EQ(issuances.size(), 1);
auto const& issuance = issuances[0].as_object();
ASSERT_TRUE(issuance.at("mpt_issuance_id").is_string());
auto const mptIssuanceId = issuance.at("mpt_issuance_id").as_string();
EXPECT_EQ(mptIssuanceId.size(), 48);
EXPECT_EQ(mptIssuanceId, expectedMptIssuanceId(kSequence));
EXPECT_NE(mptIssuanceId, kIssuanceIndex1);
auto const asOuterObject = boost::json::value{{"mpt_issuance_id", mptIssuanceId}};
auto const validated = validation::CustomValidators::uint192HexStringValidator.verify(
asOuterObject, "mpt_issuance_id"
);
EXPECT_TRUE(validated.has_value());
});
}
struct SingleFlagTest {
std::string testName;
uint32_t flag;
@@ -1218,7 +1273,7 @@ TEST_P(AccountMPTokenIssuancesLedgerFlagsTest, SingleFlag)
.WillOnce(Return(Blob{'f', 'a', 'k', 'e'}));
xrpl::STObject const ownerDir =
createOwnerDirLedgerObject({xrpl::uint256{kIssuanceIndeX1}}, kIssuanceIndeX1);
createOwnerDirLedgerObject({xrpl::uint256{kIssuanceIndex1}}, kIssuanceIndex1);
EXPECT_CALL(*backend_, doFetchLedgerObject(ownerDirKk, _, _))
.WillOnce(Return(ownerDir.getSerializer().peekData()));
@@ -1316,7 +1371,7 @@ TEST_P(AccountMPTokenIssuancesImmutableFlagsTest, SingleImmutableFlag)
.WillOnce(Return(Blob{'f', 'a', 'k', 'e'}));
xrpl::STObject const ownerDir =
createOwnerDirLedgerObject({xrpl::uint256{kIssuanceIndeX1}}, kIssuanceIndeX1);
createOwnerDirLedgerObject({xrpl::uint256{kIssuanceIndex1}}, kIssuanceIndex1);
EXPECT_CALL(*backend_, doFetchLedgerObject(ownerDirKk, _, _))
.WillOnce(Return(ownerDir.getSerializer().peekData()));