diff --git a/src/libxrpl/tx/transactors/token/ConfidentialMPTSend.cpp b/src/libxrpl/tx/transactors/token/ConfidentialMPTSend.cpp index 72b358790d..a2a48649f7 100644 --- a/src/libxrpl/tx/transactors/token/ConfidentialMPTSend.cpp +++ b/src/libxrpl/tx/transactors/token/ConfidentialMPTSend.cpp @@ -177,6 +177,12 @@ ConfidentialMPTSend::preclaim(PreclaimContext const& ctx) if (!sleIssuance->isFlag(lsfMPTCanConfidentialAmount)) return tecNO_PERMISSION; + // Sanity check: transfer rate must be 0 for confidential MPTs. + // This is unreachable since it is already enforced during MPTokenIssuanceCreate and + // MPTokenIssuanceSet. + if ((*sleIssuance)[~sfTransferFee].value_or(0) > 0) + return tecNO_PERMISSION; // LCOV_EXCL_LINE + // Check if issuance has issuer ElGamal public key if (!sleIssuance->isFieldPresent(sfIssuerEncryptionKey)) return tecNO_PERMISSION; diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp index adf343182b..b38296f63b 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceCreate.cpp @@ -71,6 +71,10 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx) // must also be set. if (fee > 0u && !ctx.tx.isFlag(tfMPTCanTransfer)) return temMALFORMED; + + // Confidential amounts are encrypted so transfer rate is disallowed. + if (fee > 0u && ctx.tx.isFlag(tfMPTCanConfidentialAmount)) + return temMALFORMED; } if (auto const domain = ctx.tx[~sfDomainID]) diff --git a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp index 9b511ecbcf..b42e200010 100644 --- a/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp +++ b/src/libxrpl/tx/transactors/token/MPTokenIssuanceSet.cpp @@ -166,6 +166,12 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx) // in the same transaction is not allowed. if ((transferFee.value_or(0) != 0u) && ((*mutableFlags & tmfMPTClearCanTransfer) != 0u)) return temMALFORMED; + + // Enabling confidential transfer and setting a non-zero TransferFee + // in the same transaction is not allowed. + if ((transferFee.value_or(0) > 0u) && + ((*mutableFlags & tmfMPTSetCanConfidentialAmount) != 0u)) + return temMALFORMED; } } @@ -309,6 +315,12 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) // the lsfMPTCanConfidentialAmount flag if (confidentialOA > 0) return tecNO_PERMISSION; + + // Cannot enable confidential transfer on an issuance that has a + // non-zero transfer fee + if (((*mutableFlags & tmfMPTSetCanConfidentialAmount) != 0u) && + (*sleMptIssuance)[~sfTransferFee].value_or(0) > 0) + return tecNO_PERMISSION; } } @@ -324,6 +336,11 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx) if (fee > 0u && !sleMptIssuance->isFlag(lsfMPTCanTransfer)) return tecNO_PERMISSION; + // Cannot set a non-zero TransferFee on an issuance that has confidential + // transfer enabled + if (fee > 0u && sleMptIssuance->isFlag(lsfMPTCanConfidentialAmount)) + return tecNO_PERMISSION; + if (!isMutableFlag(lsmfMPTCanMutateTransferFee)) return tecNO_PERMISSION; } diff --git a/src/test/app/ConfidentialTransfer_test.cpp b/src/test/app/ConfidentialTransfer_test.cpp index 688b4931ba..75c4f0b2cb 100644 --- a/src/test/app/ConfidentialTransfer_test.cpp +++ b/src/test/app/ConfidentialTransfer_test.cpp @@ -1163,6 +1163,95 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite } } + void + testTransferFee(FeatureBitset features) + { + testcase("test transfer fee"); + using namespace test::jtx; + + // MPTokenIssuanceCreate: cannot create with both TransferFee > 0 and + // tfMPTCanConfidentialAmount + { + Env env{*this, features}; + Account const alice("alice"); + MPTTester mptAlice(env, alice, {.holders = {}}); + + mptAlice.create({ + .transferFee = 100, + .flags = tfMPTCanTransfer | tfMPTCanConfidentialAmount, + .err = temMALFORMED, + }); + + // transferFee being 0 is allowed, even with tfMPTCanConfidentialAmount + mptAlice.create({ + .transferFee = 0, + .flags = tfMPTCanTransfer | tfMPTCanConfidentialAmount, + }); + } + + // MPTokenIssuanceSet (preflight): cannot enable confidential amounts and + // set TransferFee > 0 in the same transaction + { + Env env{*this, features}; + Account const alice("alice"); + MPTTester mptAlice(env, alice, {.holders = {}}); + + mptAlice.create({ + .ownerCount = 1, + .flags = tfMPTCanTransfer | tfMPTCanLock, + .mutableFlags = tmfMPTCanMutateTransferFee, + }); + + mptAlice.set({ + .account = alice, + .mutableFlags = tmfMPTSetCanConfidentialAmount, + .transferFee = 100, + .err = temMALFORMED, + }); + } + + // MPTokenIssuanceSet (preclaim): cannot enable confidential amounts on + // an issuance that already has a non-zero TransferFee + { + Env env{*this, features}; + Account const alice("alice"); + MPTTester mptAlice(env, alice, {.holders = {}}); + + mptAlice.create({ + .transferFee = 100, + .ownerCount = 1, + .flags = tfMPTCanTransfer | tfMPTCanLock, + .mutableFlags = tmfMPTCanMutateTransferFee, + }); + + mptAlice.set({ + .account = alice, + .mutableFlags = tmfMPTSetCanConfidentialAmount, + .err = tecNO_PERMISSION, + }); + } + + // MPTokenIssuanceSet (preclaim): cannot set TransferFee > 0 on an + // issuance that already has lsfMPTCanConfidentialAmount + { + Env env{*this, features}; + Account const alice("alice"); + MPTTester mptAlice(env, alice, {.holders = {}}); + + mptAlice.create({ + .ownerCount = 1, + .flags = tfMPTCanTransfer | tfMPTCanLock | tfMPTCanConfidentialAmount, + .mutableFlags = tmfMPTCanMutateTransferFee, + }); + + mptAlice.set({ + .account = alice, + .transferFee = 100, + .err = tecNO_PERMISSION, + }); + } + } + void testConvertPreclaim(FeatureBitset features) { @@ -10030,9 +10119,12 @@ class ConfidentialTransfer_test : public beast::unit_test::Suite // Crafted-proof Tests testSendSharedRandomnessViolation(features); - // Fee Tests + // Transaction Fee Tests testConfidentialMPTBaseFee(features); + // TransferFee (transfer rate) Tests + testTransferFee(features); + // Ticket Tests testWithTickets(features); testConvertTicketProofBinding(features);