Enforce zero transfer rate for confidential MPT (#7106)

This commit is contained in:
yinyiqian1
2026-05-11 11:35:44 -04:00
committed by GitHub
parent c5cfeefc44
commit d7158abb63
4 changed files with 120 additions and 1 deletions

View File

@@ -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;

View File

@@ -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])

View File

@@ -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;
}

View File

@@ -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);