From 7c0bd419a4b9ab160788beb84038682ace14794a Mon Sep 17 00:00:00 2001 From: yinyiqian1 Date: Wed, 10 Dec 2025 17:10:33 -0500 Subject: [PATCH] support mutability for MPTPrivacy (#6137) Update lsfMPTNoConfidentialTransfer to lsfMPTPrivacy Add flag lsmfMPTPrivacy to control the mutability of lsfMPTPrivacy. disallow mutating lsfMPTPrivacy when lsfMPTPrivacy is not set. disallow mutating lsfMPTPrivacy when there's confidential outstanding amount. --- include/xrpl/protocol/LedgerFormats.h | 4 +- include/xrpl/protocol/TxFlags.h | 14 +- src/test/app/ConfidentialTransfer_test.cpp | 312 +++++++++++++++--- src/test/app/MPToken_test.cpp | 1 + src/test/jtx/impl/mpt.cpp | 5 + .../app/tx/detail/ConfidentialConvert.cpp | 2 +- .../app/tx/detail/ConfidentialConvertBack.cpp | 2 +- .../app/tx/detail/ConfidentialMergeInbox.cpp | 2 +- src/xrpld/app/tx/detail/ConfidentialSend.cpp | 2 +- .../app/tx/detail/MPTokenIssuanceCreate.cpp | 8 +- .../app/tx/detail/MPTokenIssuanceSet.cpp | 94 ++++-- 11 files changed, 360 insertions(+), 86 deletions(-) diff --git a/include/xrpl/protocol/LedgerFormats.h b/include/xrpl/protocol/LedgerFormats.h index fbd321fb5e..d711dfbe92 100644 --- a/include/xrpl/protocol/LedgerFormats.h +++ b/include/xrpl/protocol/LedgerFormats.h @@ -168,7 +168,7 @@ enum LedgerSpecificFlags { lsfMPTCanTrade = 0x00000010, lsfMPTCanTransfer = 0x00000020, lsfMPTCanClawback = 0x00000040, - lsfMPTNoConfidentialTransfer = 0x00000080, + lsfMPTCanPrivacy = 0x00000080, lsmfMPTCanMutateCanLock = 0x00000002, lsmfMPTCanMutateRequireAuth = 0x00000004, @@ -178,6 +178,8 @@ enum LedgerSpecificFlags { lsmfMPTCanMutateCanClawback = 0x00000040, lsmfMPTCanMutateMetadata = 0x00010000, lsmfMPTCanMutateTransferFee = 0x00020000, + // if set, lsfMPTCanPrivacy can not be mutated + lsmfMPTCannotMutatePrivacy = 0x00040000, // ltMPTOKEN lsfMPTAuthorized = 0x00000002, diff --git a/include/xrpl/protocol/TxFlags.h b/include/xrpl/protocol/TxFlags.h index d5d3ffa3d1..a3e063cf22 100644 --- a/include/xrpl/protocol/TxFlags.h +++ b/include/xrpl/protocol/TxFlags.h @@ -132,9 +132,9 @@ constexpr std::uint32_t const tfMPTCanEscrow = lsfMPTCanEscrow; constexpr std::uint32_t const tfMPTCanTrade = lsfMPTCanTrade; constexpr std::uint32_t const tfMPTCanTransfer = lsfMPTCanTransfer; constexpr std::uint32_t const tfMPTCanClawback = lsfMPTCanClawback; -constexpr std::uint32_t const tfMPTNoConfidentialTransfer = lsfMPTNoConfidentialTransfer; +constexpr std::uint32_t const tfMPTCanPrivacy = lsfMPTCanPrivacy; constexpr std::uint32_t const tfMPTokenIssuanceCreateMask = - ~(tfUniversal | tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow | tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback | tfMPTNoConfidentialTransfer); + ~(tfUniversal | tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow | tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback | tfMPTCanPrivacy); // MPTokenIssuanceCreate MutableFlags: // Indicating specific fields or flags may be changed after issuance. @@ -146,9 +146,13 @@ constexpr std::uint32_t const tmfMPTCanMutateCanTransfer = lsmfMPTCanMutateCanTr constexpr std::uint32_t const tmfMPTCanMutateCanClawback = lsmfMPTCanMutateCanClawback; constexpr std::uint32_t const tmfMPTCanMutateMetadata = lsmfMPTCanMutateMetadata; constexpr std::uint32_t const tmfMPTCanMutateTransferFee = lsmfMPTCanMutateTransferFee; + +// Issuer can mutate lsfMPTPrivacy by default unless lsmfMPTCannotMutatePrivacy is set. +constexpr std::uint32_t const tmfMPTCannotMutatePrivacy = lsmfMPTCannotMutatePrivacy; constexpr std::uint32_t const tmfMPTokenIssuanceCreateMutableMask = ~(tmfMPTCanMutateCanLock | tmfMPTCanMutateRequireAuth | tmfMPTCanMutateCanEscrow | tmfMPTCanMutateCanTrade - | tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanClawback | tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee); + | tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanClawback | tmfMPTCanMutateMetadata | tmfMPTCanMutateTransferFee + | tmfMPTCannotMutatePrivacy); // MPTokenAuthorize flags: constexpr std::uint32_t const tfMPTUnauthorize = 0x00000001; @@ -174,10 +178,12 @@ constexpr std::uint32_t const tmfMPTSetCanTransfer = 0x00000100; constexpr std::uint32_t const tmfMPTClearCanTransfer = 0x00000200; constexpr std::uint32_t const tmfMPTSetCanClawback = 0x00000400; constexpr std::uint32_t const tmfMPTClearCanClawback = 0x00000800; +constexpr std::uint32_t const tmfMPTSetPrivacy = 0x00001000; +constexpr std::uint32_t const tmfMPTClearPrivacy = 0x00002000; constexpr std::uint32_t const tmfMPTokenIssuanceSetMutableMask = ~(tmfMPTSetCanLock | tmfMPTClearCanLock | tmfMPTSetRequireAuth | tmfMPTClearRequireAuth | tmfMPTSetCanEscrow | tmfMPTClearCanEscrow | tmfMPTSetCanTrade | tmfMPTClearCanTrade | tmfMPTSetCanTransfer | tmfMPTClearCanTransfer | - tmfMPTSetCanClawback | tmfMPTClearCanClawback); + tmfMPTSetCanClawback | tmfMPTClearCanClawback | tmfMPTSetPrivacy | tmfMPTClearPrivacy); // MPTokenIssuanceDestroy flags: constexpr std::uint32_t const tfMPTokenIssuanceDestroyMask = ~tfUniversal; diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index bbb5092f76..f62d6de042 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -31,7 +31,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); env.close(); @@ -217,7 +217,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); env.close(); @@ -238,11 +238,11 @@ class ConfidentialTransfer_test : public beast::unit_test::suite Account const bob("bob"); MPTTester mptAlice(env, alice, {.holders = {bob}}); + // no tfMPTCanPrivacy flag enabled mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock | - tfMPTNoConfidentialTransfer}); + .flags = tfMPTCanTransfer | tfMPTCanLock}); mptAlice.authorize({.account = bob}); env.close(); @@ -265,7 +265,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite testcase("Convert preclaim"); using namespace test::jtx; - // tfMPTNoConfidentialTransfer is set on issuance + // tfMPTCanPrivacy is not set on issuance { Env env{*this, features}; Account const alice("alice"); @@ -275,8 +275,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock | - tfMPTNoConfidentialTransfer}); + .flags = tfMPTCanTransfer | tfMPTCanLock}); mptAlice.authorize({.account = bob}); env.close(); @@ -304,7 +303,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); env.close(); @@ -332,7 +331,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.generateKeyPair(alice); @@ -361,7 +360,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(bob); @@ -387,7 +386,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); env.close(); @@ -419,7 +418,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); env.close(); @@ -458,7 +457,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); env.close(); @@ -502,7 +501,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTRequireAuth}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTRequireAuth | + tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = alice, .holder = bob}); @@ -558,7 +558,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); env.close(); @@ -596,7 +596,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); env.close(); @@ -640,7 +640,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.generateKeyPair(alice); @@ -654,7 +654,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.mergeInbox({.account = bob, .err = tecOBJECT_NOT_FOUND}); } - // tfMPTNoConfidentialTransfer is set on issuance + // tfMPTCanPrivacy is not set on issuance { Env env{*this, features}; Account const alice("alice"); @@ -664,8 +664,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock | - tfMPTNoConfidentialTransfer}); + .flags = tfMPTCanTransfer | tfMPTCanLock}); mptAlice.authorize({.account = bob}); env.close(); @@ -688,7 +687,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.generateKeyPair(alice); @@ -708,7 +707,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); env.close(); @@ -740,7 +739,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = carol}); @@ -871,7 +870,11 @@ class ConfidentialTransfer_test : public beast::unit_test::suite Account const carol("carol"); MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); - mptAlice.create(); + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanPrivacy}); + mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = carol}); mptAlice.generateKeyPair(alice); @@ -1004,7 +1007,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // authorize bob, carol, dave (not eve) mptAlice.create( - {.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTRequireAuth}); + {.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTRequireAuth | + tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = alice, .holder = bob}); mptAlice.authorize({.account = carol}); @@ -1056,7 +1060,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite Account const carol("carol"); MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); - mptAlice.create({.flags = tfMPTCanTransfer}); + mptAlice.create({.flags = tfMPTCanTransfer | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = carol}); mptAlice.generateKeyPair(alice); @@ -1227,7 +1231,9 @@ class ConfidentialTransfer_test : public beast::unit_test::suite MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); mptAlice.create( - {.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock}); + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = carol}); @@ -1293,7 +1299,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); env.close(); @@ -1331,7 +1337,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = carol}); @@ -1378,7 +1384,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -1415,7 +1421,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); @@ -1458,7 +1464,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); env.close(); @@ -1536,7 +1542,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); env.close(); @@ -1627,7 +1633,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.generateKeyPair(alice); @@ -1645,7 +1651,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite .err = tecOBJECT_NOT_FOUND}); } - // tfMPTNoConfidentialTransfer is set on issuance + // tfMPTCanPrivacy is not set on issuance { Env env{*this, features}; Account const alice("alice"); @@ -1655,8 +1661,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock | - tfMPTNoConfidentialTransfer}); + .flags = tfMPTCanTransfer | tfMPTCanLock}); mptAlice.authorize({.account = bob}); env.close(); @@ -1683,7 +1688,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.generateKeyPair(alice); mptAlice.generateKeyPair(bob); @@ -1708,7 +1713,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); env.close(); @@ -1740,7 +1745,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = carol}); @@ -1792,7 +1797,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTRequireAuth}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTRequireAuth | + tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = alice, .holder = bob}); @@ -1865,7 +1871,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.ownerCount = 1, .holderCount = 0, - .flags = tfMPTCanTransfer | tfMPTCanLock}); + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = carol}); @@ -1993,7 +1999,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite MPTTester mptAlice(env, alice, {.holders = {bob, carol, dave}}); mptAlice.create( - {.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanClawback}); + {.flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanClawback | + tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.pay(alice, bob, 100); mptAlice.authorize({.account = carol}); @@ -2106,7 +2113,11 @@ class ConfidentialTransfer_test : public beast::unit_test::suite Account const carol("carol"); MPTTester mptAlice(env, alice, {.holders = {bob, carol}}); - mptAlice.create(); + mptAlice.create( + {.ownerCount = 1, + .holderCount = 0, + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanPrivacy}); + mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = carol}); mptAlice.generateKeyPair(alice); @@ -2180,8 +2191,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite MPTTester mptAlice(env, alice, {.holders = {bob, carol, dave}}); mptAlice.create( - {.flags = - tfMPTCanTransfer | tfMPTCanClawback | tfMPTRequireAuth}); + {.flags = tfMPTCanTransfer | tfMPTCanClawback | + tfMPTRequireAuth | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = alice, .holder = bob}); mptAlice.authorize({.account = carol}); @@ -2244,7 +2255,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite Account const bob("bob"); MPTTester mptAlice(env, alice, {.holders = {bob}}); - mptAlice.create({.flags = tfMPTCanTransfer}); + mptAlice.create({.flags = tfMPTCanTransfer | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.generateKeyPair(alice); mptAlice.set( @@ -2265,7 +2276,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite Account const alice("alice"); Account const bob("bob"); MPTTester mptAlice(env, alice, {.holders = {bob}}); - mptAlice.create({.flags = tfMPTCanClawback}); + mptAlice.create({.flags = tfMPTCanClawback | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.generateKeyPair(alice); env.close(); @@ -2284,7 +2295,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite Account const alice("alice"); Account const bob("bob"); MPTTester mptAlice(env, alice, {.holders = {bob}}); - mptAlice.create({.flags = tfMPTCanClawback}); + mptAlice.create({.flags = tfMPTCanClawback | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.generateKeyPair(alice); mptAlice.set( @@ -2314,7 +2325,7 @@ class ConfidentialTransfer_test : public beast::unit_test::suite mptAlice.create( {.flags = tfMPTCanTransfer | tfMPTCanClawback | - tfMPTRequireAuth | tfMPTCanLock}); + tfMPTRequireAuth | tfMPTCanLock | tfMPTCanPrivacy}); mptAlice.authorize({.account = bob}); mptAlice.authorize({.account = alice, .holder = bob}); mptAlice.pay(alice, bob, 100); @@ -2378,6 +2389,205 @@ class ConfidentialTransfer_test : public beast::unit_test::suite // todo: test zkp verification failure } + void + testMutatePrivacy(FeatureBitset features) + { + testcase("mutate lsfMPTCanPrivacy"); + using namespace test::jtx; + + // can not create mpt issuance with tmfMPTCannotMutatePrivacy + // when featureDynamicMPT is disabled + { + Env env{*this, features - featureDynamicMPT}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 0, + .mutableFlags = tmfMPTCannotMutatePrivacy, + .err = temDISABLED}); + } + + // can not create mpt issuance with tmfMPTCannotMutatePrivacy when + // featureConfidentialTransfer is disabled + { + Env env{*this, features - featureConfidentialTransfer}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 0, + .mutableFlags = tmfMPTCannotMutatePrivacy, + .err = temDISABLED}); + } + + // if lsmfMPTCannotMutatePrivacy is set, can not set/clear + // lsfMPTCanPrivacy + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .flags = tfMPTCanTransfer, + .mutableFlags = tmfMPTCannotMutatePrivacy}); + + mptAlice.set( + {.account = alice, + .mutableFlags = tmfMPTSetPrivacy, + .err = tecNO_PERMISSION}); + + mptAlice.set( + {.account = alice, + .mutableFlags = tmfMPTClearPrivacy, + .err = tecNO_PERMISSION}); + } + + // Toggle lsfMPTCanPrivacy + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + mptAlice.create( + {.ownerCount = 1, + .flags = tfMPTCanTransfer | tfMPTCanPrivacy, + .mutableFlags = tmfMPTCanMutateCanLock}); + + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 100); + + mptAlice.generateKeyPair(alice); + mptAlice.generateKeyPair(bob); + mptAlice.set( + {.account = alice, .pubKey = mptAlice.getPubKey(alice)}); + + auto holderPubKeySet = false; + auto verifyToggle = [&](TER expectedResult, uint64_t amt) { + if (!holderPubKeySet) + mptAlice.convert( + {.account = bob, + .amt = amt, + .proof = "123", + .holderPubKey = mptAlice.getPubKey(bob), + .err = expectedResult}); + else + mptAlice.convert({ + .account = bob, + .amt = amt, + .proof = "123", + .err = expectedResult, + }); + + if (expectedResult == tesSUCCESS) + { + holderPubKeySet = true; + mptAlice.mergeInbox({ + .account = bob, + }); + + // make sure there's no confidential outstanding balance + // for the next toggle test + mptAlice.convertBack( + {.account = bob, .amt = amt, .proof = "123"}); + } + }; + + // set lsfMPTCanPrivacy, but no effect because lsfMPTCanPrivacy was + // already set + mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetPrivacy}); + verifyToggle(tesSUCCESS, 10); + + // clear lsfMPTCanPrivacy + mptAlice.set( + {.account = alice, .mutableFlags = tmfMPTClearPrivacy}); + verifyToggle(tecNO_PERMISSION, 10); + + // can clear lsfMPTCanPrivacy again but has no effect + // for privacy settings + mptAlice.set( + {.account = alice, + .mutableFlags = tmfMPTClearPrivacy | tmfMPTSetCanLock}); + verifyToggle(tecNO_PERMISSION, 20); + + // set lsfMPTCanPrivacy again + mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetPrivacy}); + verifyToggle(tesSUCCESS, 30); + } + + // can not mutate lsfPrivacy when there's confidential + // outstanding amount + { + Env env{*this, features}; + Account const alice("alice"); + Account const bob("bob"); + MPTTester mptAlice(env, alice, {.holders = {bob}}); + + // lsmfMPTCannotMutatePrivacy is false by default, + // so that lsfMPTCanPrivacy can be mutated + mptAlice.create( + {.ownerCount = 1, .flags = tfMPTCanTransfer | tfMPTCanPrivacy}); + + mptAlice.authorize({.account = bob}); + mptAlice.pay(alice, bob, 100); + + mptAlice.generateKeyPair(alice); + mptAlice.generateKeyPair(bob); + mptAlice.set( + {.account = alice, .pubKey = mptAlice.getPubKey(alice)}); + + // bob convert 50 to confidential + mptAlice.convert( + {.account = bob, + .amt = 50, + .proof = "123", + .holderPubKey = mptAlice.getPubKey(bob)}); + + // set or clear lsfMPTCanPrivacy should fail because of + // confidential outstanding balance + mptAlice.set( + {.account = alice, + .mutableFlags = tmfMPTSetPrivacy, + .err = tecNO_PERMISSION}); + mptAlice.set( + {.account = alice, + .mutableFlags = tmfMPTClearPrivacy, + .err = tecNO_PERMISSION}); + + // bob merge inbox + mptAlice.mergeInbox({ + .account = bob, + }); + + // bob convert back all confidential balance + mptAlice.convertBack({.account = bob, .amt = 50, .proof = "123"}); + + // now clear lsfMPTCanPrivacy should succeed, + // because there's no confidential outstanding balance + mptAlice.set( + {.account = alice, .mutableFlags = tmfMPTClearPrivacy}); + + // bob can not convert because lsfMPTCanPrivacy was cleared + // successfully + mptAlice.convert( + {.account = bob, + .amt = 10, + .proof = "123", + .holderPubKey = mptAlice.getPubKey(bob), + .err = tecNO_PERMISSION}); + + // can set lsfMPTCanPrivacy again when there's no confidential + // outstanding balance + mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetPrivacy}); + mptAlice.convert({.account = bob, .amt = 10, .proof = "123"}); + } + } + void testWithFeats(FeatureBitset features) { @@ -2407,6 +2617,8 @@ class ConfidentialTransfer_test : public beast::unit_test::suite testConvertBack(features); testConvertBackPreflight(features); testConvertBackPreclaim(features); + + testMutatePrivacy(features); } public: diff --git a/src/test/app/MPToken_test.cpp b/src/test/app/MPToken_test.cpp index a10de70f13..95c46b6326 100644 --- a/src/test/app/MPToken_test.cpp +++ b/src/test/app/MPToken_test.cpp @@ -2931,6 +2931,7 @@ class MPToken_test : public beast::unit_test::suite tmfMPTSetCanTrade | tmfMPTClearCanTrade, tmfMPTSetCanTransfer | tmfMPTClearCanTransfer, tmfMPTSetCanClawback | tmfMPTClearCanClawback, + tmfMPTSetPrivacy | tmfMPTClearPrivacy, tmfMPTSetCanLock | tmfMPTClearCanLock | tmfMPTClearCanTrade, tmfMPTSetCanTransfer | tmfMPTClearCanTransfer | tmfMPTSetCanEscrow | tmfMPTClearCanClawback}; diff --git a/src/test/jtx/impl/mpt.cpp b/src/test/jtx/impl/mpt.cpp index 1cc2fcba40..c38f27a13d 100644 --- a/src/test/jtx/impl/mpt.cpp +++ b/src/test/jtx/impl/mpt.cpp @@ -443,6 +443,11 @@ MPTTester::set(MPTSet const& arg) flags |= lsfMPTCanTransfer; else if (*arg.mutableFlags & tmfMPTClearCanTransfer) flags &= ~lsfMPTCanTransfer; + + if (*arg.mutableFlags & tmfMPTSetPrivacy) + flags |= lsfMPTCanPrivacy; + else if (*arg.mutableFlags & tmfMPTClearPrivacy) + flags &= ~lsfMPTCanPrivacy; } } env_.require(mptflags(*this, flags, holder)); diff --git a/src/xrpld/app/tx/detail/ConfidentialConvert.cpp b/src/xrpld/app/tx/detail/ConfidentialConvert.cpp index 063196adcc..29b066b2ab 100644 --- a/src/xrpld/app/tx/detail/ConfidentialConvert.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialConvert.cpp @@ -52,7 +52,7 @@ ConfidentialConvert::preclaim(PreclaimContext const& ctx) if (!sleIssuance) return tecOBJECT_NOT_FOUND; - if (sleIssuance->isFlag(lsfMPTNoConfidentialTransfer)) + if (!sleIssuance->isFlag(lsfMPTCanPrivacy)) return tecNO_PERMISSION; // already checked in preflight, but should also check that issuer on the diff --git a/src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp b/src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp index 8645a6b7be..11244bb5cc 100644 --- a/src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialConvertBack.cpp @@ -49,7 +49,7 @@ ConfidentialConvertBack::preclaim(PreclaimContext const& ctx) if (!sleIssuance) return tecOBJECT_NOT_FOUND; - if (sleIssuance->isFlag(lsfMPTNoConfidentialTransfer)) + if (!sleIssuance->isFlag(lsfMPTCanPrivacy)) return tecNO_PERMISSION; // already checked in preflight, but should also check that issuer on the diff --git a/src/xrpld/app/tx/detail/ConfidentialMergeInbox.cpp b/src/xrpld/app/tx/detail/ConfidentialMergeInbox.cpp index 4a4cdf001f..a19747a3e0 100644 --- a/src/xrpld/app/tx/detail/ConfidentialMergeInbox.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialMergeInbox.cpp @@ -30,7 +30,7 @@ ConfidentialMergeInbox::preclaim(PreclaimContext const& ctx) if (!sleIssuance) return tecOBJECT_NOT_FOUND; - if (sleIssuance->isFlag(lsfMPTNoConfidentialTransfer)) + if (!sleIssuance->isFlag(lsfMPTCanPrivacy)) return tecNO_PERMISSION; // already checked in preflight, but should also check that issuer on the diff --git a/src/xrpld/app/tx/detail/ConfidentialSend.cpp b/src/xrpld/app/tx/detail/ConfidentialSend.cpp index 128cbc8b17..93985b6181 100644 --- a/src/xrpld/app/tx/detail/ConfidentialSend.cpp +++ b/src/xrpld/app/tx/detail/ConfidentialSend.cpp @@ -72,7 +72,7 @@ ConfidentialSend::preclaim(PreclaimContext const& ctx) return tecNO_AUTH; // Check if issuance allows confidential transfer - if (sleIssuance->isFlag(lsfMPTNoConfidentialTransfer)) + if (!sleIssuance->isFlag(lsfMPTCanPrivacy)) return tecNO_PERMISSION; // Check if issuance has issuer ElGamal public key diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp index e153a7c9bf..d04198fa3c 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceCreate.cpp @@ -18,7 +18,13 @@ MPTokenIssuanceCreate::checkExtraFeatures(PreflightContext const& ctx) !ctx.rules.enabled(featureDynamicMPT)) return false; - if (ctx.tx.getFlags() & tfMPTNoConfidentialTransfer && + if (ctx.tx.isFlag(tfMPTCanPrivacy) && + !ctx.rules.enabled(featureConfidentialTransfer)) + return false; + + // can not set tmfMPTCannotMutatePrivacy without featureConfidentialTransfer + auto const mutableFlags = ctx.tx[~sfMutableFlags]; + if (mutableFlags && (*mutableFlags & tmfMPTCannotMutatePrivacy) && !ctx.rules.enabled(featureConfidentialTransfer)) return false; diff --git a/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp index 2cf00b0886..1b9115adf1 100644 --- a/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp +++ b/src/xrpld/app/tx/detail/MPTokenIssuanceSet.cpp @@ -28,22 +28,41 @@ struct MPTMutabilityFlags { std::uint32_t setFlag; std::uint32_t clearFlag; - std::uint32_t canMutateFlag; + std::uint32_t mutabilityFlag; + std::uint32_t targetFlag; + bool isCannotMutate = false; // if true, cannot mutate by default. }; -static constexpr std::array mptMutabilityFlags = { - {{tmfMPTSetCanLock, tmfMPTClearCanLock, lsmfMPTCanMutateCanLock}, +static constexpr std::array mptMutabilityFlags = { + {{tmfMPTSetCanLock, + tmfMPTClearCanLock, + lsmfMPTCanMutateCanLock, + lsfMPTCanLock}, {tmfMPTSetRequireAuth, tmfMPTClearRequireAuth, - lsmfMPTCanMutateRequireAuth}, - {tmfMPTSetCanEscrow, tmfMPTClearCanEscrow, lsmfMPTCanMutateCanEscrow}, - {tmfMPTSetCanTrade, tmfMPTClearCanTrade, lsmfMPTCanMutateCanTrade}, + lsmfMPTCanMutateRequireAuth, + lsfMPTRequireAuth}, + {tmfMPTSetCanEscrow, + tmfMPTClearCanEscrow, + lsmfMPTCanMutateCanEscrow, + lsfMPTCanEscrow}, + {tmfMPTSetCanTrade, + tmfMPTClearCanTrade, + lsmfMPTCanMutateCanTrade, + lsfMPTCanTrade}, {tmfMPTSetCanTransfer, tmfMPTClearCanTransfer, - lsmfMPTCanMutateCanTransfer}, + lsmfMPTCanMutateCanTransfer, + lsfMPTCanTransfer}, {tmfMPTSetCanClawback, tmfMPTClearCanClawback, - lsmfMPTCanMutateCanClawback}}}; + lsmfMPTCanMutateCanClawback, + lsfMPTCanClawback}, + {tmfMPTSetPrivacy, + tmfMPTClearPrivacy, + lsmfMPTCannotMutatePrivacy, + lsfMPTCanPrivacy, + true}}}; NotTEC MPTokenIssuanceSet::preflight(PreflightContext const& ctx) @@ -52,27 +71,35 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx) auto const metadata = ctx.tx[~sfMPTokenMetadata]; auto const transferFee = ctx.tx[~sfTransferFee]; auto const isMutate = mutableFlags || metadata || transferFee; + auto const hasElGamalKey = ctx.tx.isFieldPresent(sfIssuerElGamalPublicKey); + auto const txFlags = ctx.tx.getFlags(); + + auto const mutatePrivacy = mutableFlags && + ((*mutableFlags & (tmfMPTSetPrivacy | tmfMPTClearPrivacy))); + + auto const hasDomain = ctx.tx.isFieldPresent(sfDomainID); + auto const hasHolder = ctx.tx.isFieldPresent(sfHolder); if (isMutate && !ctx.rules.enabled(featureDynamicMPT)) return temDISABLED; - if (ctx.tx.isFieldPresent(sfDomainID) && ctx.tx.isFieldPresent(sfHolder)) - return temMALFORMED; - - if (!ctx.rules.enabled(featureConfidentialTransfer) && - ctx.tx.isFieldPresent(sfIssuerElGamalPublicKey)) + if ((hasElGamalKey || mutatePrivacy) && + !ctx.rules.enabled(featureConfidentialTransfer)) return temDISABLED; - if (ctx.tx.isFieldPresent(sfIssuerElGamalPublicKey) && - ctx.tx.isFieldPresent(sfHolder)) + if (hasDomain && hasHolder) return temMALFORMED; - if (ctx.tx.isFieldPresent(sfIssuerElGamalPublicKey) && + if (mutatePrivacy && hasHolder) + return temMALFORMED; + + if (hasElGamalKey && hasHolder) + return temMALFORMED; + + if (hasElGamalKey && ctx.tx[sfIssuerElGamalPublicKey].length() != ecPubKeyLength) return temMALFORMED; - auto const txFlags = ctx.tx.getFlags(); - // fails if both flags are set if ((txFlags & tfMPTLock) && (txFlags & tfMPTUnlock)) return temINVALID_FLAG; @@ -87,8 +114,7 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx) ctx.rules.enabled(featureConfidentialTransfer)) { // Is this transaction actually changing anything ? - if (txFlags == 0 && !ctx.tx.isFieldPresent(sfDomainID) && - !ctx.tx.isFieldPresent(sfIssuerElGamalPublicKey) && !isMutate) + if (txFlags == 0 && !hasDomain && !hasElGamalKey && !isMutate) return temMALFORMED; } @@ -230,16 +256,32 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) return currentMutableFlags & mutableFlag; }; - if (auto const mutableFlags = ctx.tx[~sfMutableFlags]) + auto const mutableFlags = ctx.tx[~sfMutableFlags]; + if (mutableFlags) { if (std::any_of( mptMutabilityFlags.begin(), mptMutabilityFlags.end(), [mutableFlags, &isMutableFlag](auto const& f) { - return !isMutableFlag(f.canMutateFlag) && - ((*mutableFlags & (f.setFlag | f.clearFlag))); + bool const canMutate = f.isCannotMutate + ? isMutableFlag(f.mutabilityFlag) + : !isMutableFlag(f.mutabilityFlag); + return canMutate && + (*mutableFlags & (f.setFlag | f.clearFlag)); })) return tecNO_PERMISSION; + + if ((*mutableFlags & tmfMPTSetPrivacy) || + (*mutableFlags & tmfMPTClearPrivacy)) + { + std::uint64_t const confidentialOA = + (*sleMptIssuance)[~sfConfidentialOutstandingAmount].value_or(0); + + // If there's any confidential outstanding amount, disallow toggling + // the lsfMPTCanPrivacy flag + if (confidentialOA > 0) + return tecNO_PERMISSION; + } } if (!isMutableFlag(lsmfMPTCanMutateMetadata) && @@ -267,7 +309,7 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) } if (ctx.tx.isFieldPresent(sfIssuerElGamalPublicKey) && - sleMptIssuance->isFlag(tfMPTNoConfidentialTransfer)) + !sleMptIssuance->isFlag(lsfMPTCanPrivacy)) { return tecNO_PERMISSION; } @@ -305,9 +347,9 @@ MPTokenIssuanceSet::doApply() for (auto const& f : mptMutabilityFlags) { if (mutableFlags & f.setFlag) - flagsOut |= f.canMutateFlag; + flagsOut |= f.targetFlag; else if (mutableFlags & f.clearFlag) - flagsOut &= ~f.canMutateFlag; + flagsOut &= ~f.targetFlag; } if (mutableFlags & tmfMPTClearCanTransfer)