diff --git a/src/rpc/handlers/AccountMPTokenIssuances.cpp b/src/rpc/handlers/AccountMPTokenIssuances.cpp index 5870adf67..bd9309e30 100644 --- a/src/rpc/handlers/AccountMPTokenIssuances.cpp +++ b/src/rpc/handlers/AccountMPTokenIssuances.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -38,11 +39,13 @@ AccountMPTokenIssuancesHandler::addMPTokenIssuance( { MPTokenIssuanceResponse issuance; - issuance.mpTokenIssuanceId = xrpl::strHex(sle.key()); - issuance.issuer = xrpl::to_string(account); - issuance.sequence = sle.getFieldU32(xrpl::sfSequence); + auto const sequence = sle.getFieldU32(xrpl::sfSequence); auto const flags = sle.getFieldU32(xrpl::sfFlags); + issuance.mpTokenIssuanceId = xrpl::to_string(xrpl::makeMptID(sequence, account)); + issuance.issuer = xrpl::to_string(account); + issuance.sequence = sequence; + auto const setFlag = [&](std::optional& field, std::uint32_t mask) { if ((flags & mask) != 0u) field = true; diff --git a/tests/unit/rpc/handlers/AccountMPTokenIssuancesTests.cpp b/tests/unit/rpc/handlers/AccountMPTokenIssuancesTests.cpp index a472c5d05..002c137c9 100644 --- a/tests/unit/rpc/handlers/AccountMPTokenIssuancesTests.cpp +++ b/tests/unit/rpc/handlers/AccountMPTokenIssuancesTests.cpp @@ -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(50, xrpl::uint256{kIssuanceIndeX1}); + auto const indexes = std::vector(50, xrpl::uint256{kIssuanceIndex1}); auto const bbs = [&]() { std::vector 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(10, xrpl::uint256{kIssuanceIndeX1}); + auto const indexes = std::vector(10, xrpl::uint256{kIssuanceIndex1}); auto const bbs = [&]() { std::vector 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(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(kLimit, xrpl::uint256{kIssuanceIndeX1}); + auto const indexes = std::vector(kLimit, xrpl::uint256{kIssuanceIndex1}); auto const bbs = [&]() { std::vector 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{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()));