feat: Make DynamicMPT opt-in-immutable

This commit is contained in:
yinyiqian1
2026-07-17 14:02:35 -04:00
committed by Ayaz Salikhov
parent bf65e5fa7b
commit 033dca2f0e
23 changed files with 619 additions and 628 deletions

View File

@@ -43,6 +43,7 @@
#include <xrpl/protocol/IOUAmount.h>
#include <xrpl/protocol/Indexes.h>
#include <xrpl/protocol/Issue.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/MPTAmount.h>
#include <xrpl/protocol/MPTIssue.h>
#include <xrpl/protocol/Protocol.h>
@@ -59,6 +60,7 @@
#include <xrpl/protocol/UintTypes.h>
#include <xrpl/protocol/XRPAmount.h>
#include <xrpl/protocol/jss.h>
#include <xrpl/tx/transactors/token/MPTokenIssuanceSet.h>
#include <array>
#include <cstdint>
@@ -602,9 +604,9 @@ class MPToken_test : public beast::unit_test::Suite
mptAlice.authorize({.account = bob, .holderCount = 1});
// test invalid flag - only valid flags are tfMPTLock (1) and Unlock
// (2)
mptAlice.set({.account = alice, .flags = 0x00000008, .err = temINVALID_FLAG});
// test invalid flag - an unrecognized flag bit is always
// rejected, regardless of which amendments are enabled
mptAlice.set({.account = alice, .flags = 0x00001000, .err = temINVALID_FLAG});
if (!features[featureSingleAssetVault] && !features[featureDynamicMPT] &&
!features[featureConfidentialTransfer])
@@ -3393,26 +3395,26 @@ class MPToken_test : public beast::unit_test::Suite
using namespace test::jtx;
Account const alice("alice");
// Can not provide MutableFlags when DynamicMPT amendment is not enabled
// Can not provide ImmutableFlags when DynamicMPT amendment is not enabled
{
Env env{*this, features - featureDynamicMPT};
MPTTester mptAlice(env, alice);
mptAlice.create({.ownerCount = 0, .mutableFlags = 2, .err = temDISABLED});
mptAlice.create({.ownerCount = 0, .mutableFlags = 0, .err = temDISABLED});
mptAlice.create({.ownerCount = 0, .immutableFlags = 2, .err = temDISABLED});
mptAlice.create({.ownerCount = 0, .immutableFlags = 0, .err = temDISABLED});
}
// MutableFlags contains invalid values
// ImmutableFlags contains invalid values
{
Env env{*this, features};
MPTTester mptAlice(env, alice);
// Value 1 is reserved for MPT lock.
mptAlice.create({.ownerCount = 0, .mutableFlags = 1, .err = temINVALID_FLAG});
mptAlice.create({.ownerCount = 0, .mutableFlags = 17, .err = temINVALID_FLAG});
mptAlice.create({.ownerCount = 0, .mutableFlags = 65535, .err = temINVALID_FLAG});
mptAlice.create({.ownerCount = 0, .immutableFlags = 1, .err = temINVALID_FLAG});
mptAlice.create({.ownerCount = 0, .immutableFlags = 17, .err = temINVALID_FLAG});
mptAlice.create({.ownerCount = 0, .immutableFlags = 65535, .err = temINVALID_FLAG});
// MutableFlags can not be 0
mptAlice.create({.ownerCount = 0, .mutableFlags = 0, .err = temINVALID_FLAG});
// ImmutableFlags can not be 0
mptAlice.create({.ownerCount = 0, .immutableFlags = 0, .err = temINVALID_FLAG});
}
}
@@ -3425,16 +3427,16 @@ class MPToken_test : public beast::unit_test::Suite
Account const alice("alice");
Account const bob("bob");
// Can not provide MutableFlags, MPTokenMetadata or TransferFee when
// Can not provide mutate related flags, MPTokenMetadata or TransferFee when
// DynamicMPT amendment is not enabled
{
Env env{*this, features - featureDynamicMPT};
MPTTester mptAlice(env, alice, {.holders = {bob}});
auto const mptID = makeMptID(env.seq(alice), alice);
// MutableFlags is not allowed when DynamicMPT is not enabled
mptAlice.set({.account = alice, .id = mptID, .mutableFlags = 2, .err = temDISABLED});
mptAlice.set({.account = alice, .id = mptID, .mutableFlags = 0, .err = temDISABLED});
// Mutate related flags is not allowed when DynamicMPT is not enabled
mptAlice.set(
{.account = alice, .id = mptID, .flags = tfMPTSetCanLock, .err = temDISABLED});
// MPTokenMetadata is not allowed when DynamicMPT is not enabled
mptAlice.set({.account = alice, .id = mptID, .metadata = "test", .err = temDISABLED});
@@ -3445,19 +3447,19 @@ class MPToken_test : public beast::unit_test::Suite
mptAlice.set({.account = alice, .id = mptID, .transferFee = 0, .err = temDISABLED});
}
// Can not provide holder when MutableFlags, MPTokenMetadata or
// Can not provide holder when mutate related flags, MPTokenMetadata or
// TransferFee is present
{
Env env{*this, features};
MPTTester mptAlice(env, alice, {.holders = {bob}});
auto const mptID = makeMptID(env.seq(alice), alice);
// Holder is not allowed when MutableFlags is present
// Holder is not allowed when mutate related flags is present
mptAlice.set(
{.account = alice,
.holder = bob,
.id = mptID,
.mutableFlags = 2,
.flags = tfMPTSetCanLock,
.err = temMALFORMED});
// Holder is not allowed when MPTokenMetadata is present
@@ -3477,27 +3479,24 @@ class MPToken_test : public beast::unit_test::Suite
.err = temMALFORMED});
}
// Can not set Flags when MutableFlags, MPTokenMetadata or
// Can not lock when mutate related flags, MPTokenMetadata or
// TransferFee is present
{
Env env{*this, features};
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create(
{.ownerCount = 1,
.mutableFlags = tmfMPTCanMutateMetadata | tmfMPTCanEnableCanLock |
tmfMPTCanMutateTransferFee});
mptAlice.create({.ownerCount = 1});
// Setting flags is not allowed when MutableFlags is present
// Lock is not allowed when mutate related flags is present
mptAlice.set(
{.account = alice, .flags = tfMPTCanLock, .mutableFlags = 2, .err = temMALFORMED});
{.account = alice, .flags = tfMPTLock | tfMPTSetCanLock, .err = temMALFORMED});
// Setting flags is not allowed when MPTokenMetadata is present
// Lock is not allowed when MPTokenMetadata is present
mptAlice.set(
{.account = alice, .flags = tfMPTCanLock, .metadata = "test", .err = temMALFORMED});
{.account = alice, .flags = tfMPTLock, .metadata = "test", .err = temMALFORMED});
// setting flags is not allowed when TransferFee is present
// Lock is not allowed when TransferFee is present
mptAlice.set(
{.account = alice, .flags = tfMPTCanLock, .transferFee = 100, .err = temMALFORMED});
{.account = alice, .flags = tfMPTLock, .transferFee = 100, .err = temMALFORMED});
}
// Flags being 0 or tfFullyCanonicalSig is fine
@@ -3509,48 +3508,39 @@ class MPToken_test : public beast::unit_test::Suite
{.transferFee = 10,
.ownerCount = 1,
.flags = tfMPTCanTransfer,
.mutableFlags = tmfMPTCanMutateTransferFee | tmfMPTCanMutateMetadata});
.immutableFlags = tifMPTTransferFee});
mptAlice.set({.account = alice, .flags = 0, .transferFee = 100, .metadata = "test"});
mptAlice.set(
{.account = alice,
.flags = tfFullyCanonicalSig,
.transferFee = 200,
.metadata = "test2"});
mptAlice.set({.account = alice, .flags = 0, .metadata = "test"});
mptAlice.set({.account = alice, .flags = tfFullyCanonicalSig, .metadata = "test2"});
}
// Invalid MutableFlags
// Invalid flags
{
Env env{*this, features};
MPTTester mptAlice(env, alice, {.holders = {bob}});
auto const mptID = makeMptID(env.seq(alice), alice);
for (auto const flags : {10000, 0, 5000})
for (auto const flags : {0x0200u, 0x0800u, 0x2000u, 0x0201u})
{
mptAlice.set(
{.account = alice, .id = mptID, .mutableFlags = flags, .err = temINVALID_FLAG});
{.account = alice, .id = mptID, .flags = flags, .err = temINVALID_FLAG});
}
}
// Can not mutate flag which is not mutable
// Can not set flag which is immutable
{
Env env{*this, features};
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create({.ownerCount = 1});
mptAlice.create(
{.ownerCount = 1,
.immutableFlags = tifMPTCanLock | tifMPTCanTrade | tifMPTCanTransfer |
tifMPTCanClawback | tifMPTCanEscrow | tifMPTRequireAuth |
tifMPTCanHoldConfidentialBalance});
auto const mutableFlags = {
tmfMPTSetCanLock,
tmfMPTSetRequireAuth,
tmfMPTSetCanEscrow,
tmfMPTSetCanTrade,
tmfMPTSetCanTransfer,
tmfMPTSetCanClawback};
for (auto const& mutableFlag : mutableFlags)
for (auto const& f : MPTokenIssuanceSet::flagMapping)
{
mptAlice.set(
{.account = alice, .mutableFlags = mutableFlag, .err = tecNO_PERMISSION});
mptAlice.set({.account = alice, .flags = f.setFlag, .err = tecNO_PERMISSION});
}
}
@@ -3559,18 +3549,18 @@ class MPToken_test : public beast::unit_test::Suite
Env env{*this, features};
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create({.ownerCount = 1, .mutableFlags = tmfMPTCanMutateMetadata});
mptAlice.create({.ownerCount = 1});
std::string const metadata(kMaxMpTokenMetadataLength + 1, 'a');
mptAlice.set({.account = alice, .metadata = metadata, .err = temMALFORMED});
}
// Can not mutate metadata when it is not mutable
// Can not set metadata when it is immutable
{
Env env{*this, features};
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create({.ownerCount = 1});
mptAlice.create({.ownerCount = 1, .immutableFlags = tifMPTMetadata});
mptAlice.set({.account = alice, .metadata = "test", .err = tecNO_PERMISSION});
}
@@ -3580,7 +3570,7 @@ class MPToken_test : public beast::unit_test::Suite
MPTTester mptAlice(env, alice, {.holders = {bob}});
auto const mptID = makeMptID(env.seq(alice), alice);
mptAlice.create({.ownerCount = 1, .mutableFlags = tmfMPTCanMutateTransferFee});
mptAlice.create({.ownerCount = 1});
mptAlice.set(
{.account = alice,
@@ -3594,83 +3584,70 @@ class MPToken_test : public beast::unit_test::Suite
Env env{*this, features};
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create(
{.ownerCount = 1,
.mutableFlags = tmfMPTCanMutateTransferFee | tmfMPTCanEnableCanTransfer});
mptAlice.create({.ownerCount = 1});
// MPTCanTransfer is not set, return tecNO_PERMISSION
mptAlice.set({.account = alice, .transferFee = 100, .err = tecNO_PERMISSION});
// Can not set transfer fee even when trying to set MPTCanTransfer
// at the same time. MPTCanTransfer must be set first, then transfer
// fee can be set in a separate transaction.
mptAlice.set(
{.account = alice,
.mutableFlags = tmfMPTSetCanTransfer,
.transferFee = 100,
.err = tecNO_PERMISSION});
// Setting a non-zero transfer fee is fine if MPTCanTransfer is
// being enabled in the same transaction
mptAlice.set({.account = alice, .flags = tfMPTSetCanTransfer, .transferFee = 100});
BEAST_EXPECT(mptAlice.checkFlags(lsfMPTCanTransfer));
BEAST_EXPECT(mptAlice.checkTransferFee(100));
}
// Can not mutate transfer fee when it is not mutable
// Can not set transfer fee when it is immutable
{
Env env{*this, features};
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create({.transferFee = 10, .ownerCount = 1, .flags = tfMPTCanTransfer});
mptAlice.create(
{.transferFee = 10,
.ownerCount = 1,
.flags = tfMPTCanTransfer,
.immutableFlags = tifMPTTransferFee});
mptAlice.set({.account = alice, .transferFee = 100, .err = tecNO_PERMISSION});
mptAlice.set({.account = alice, .transferFee = 0, .err = tecNO_PERMISSION});
}
// Set some flags mutable. Can not mutate the others
// Set some flags immutable. Others can still be set.
{
Env env{*this, features};
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create(
{.ownerCount = 1,
.mutableFlags = tmfMPTCanEnableCanTrade | tmfMPTCanEnableCanTransfer |
tmfMPTCanMutateMetadata});
.immutableFlags = tifMPTCanTrade | tifMPTCanTransfer | tifMPTMetadata});
// Can not mutate transfer fee
mptAlice.set({.account = alice, .transferFee = 100, .err = tecNO_PERMISSION});
auto const canEnableFlags = {
tfMPTSetCanLock, tfMPTSetRequireAuth, tfMPTSetCanEscrow, tfMPTSetCanClawback};
auto const invalidFlags = {
tmfMPTSetCanLock, tmfMPTSetRequireAuth, tmfMPTSetCanEscrow, tmfMPTSetCanClawback};
// Can not enable immutable flags
mptAlice.set({.account = alice, .flags = tfMPTSetCanTrade, .err = tecNO_PERMISSION});
mptAlice.set({.account = alice, .flags = tfMPTSetCanTransfer, .err = tecNO_PERMISSION});
// Can not mutate flags which are not mutable
for (auto const& mutableFlag : invalidFlags)
// Can enable flags which are not immutable
for (auto const& mutableFlag : canEnableFlags)
{
mptAlice.set(
{.account = alice, .mutableFlags = mutableFlag, .err = tecNO_PERMISSION});
mptAlice.set({.account = alice, .flags = mutableFlag});
}
// Can mutate MPTCanTrade
mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanTrade});
// Can mutate MPTCanTransfer
mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanTransfer});
// Can mutate metadata
mptAlice.set({.account = alice, .metadata = "test"});
mptAlice.set({.account = alice, .metadata = ""});
}
}
void
testMutateMPT(FeatureBitset features)
testSetMPT(FeatureBitset features)
{
testcase("Mutate MPT");
testcase("Set MPT");
using namespace test::jtx;
Account const alice("alice");
// Mutate metadata
// Set metadata
{
Env env{*this, features};
MPTTester mptAlice(env, alice);
mptAlice.create(
{.metadata = "test", .ownerCount = 1, .mutableFlags = tmfMPTCanMutateMetadata});
mptAlice.create({.metadata = "test", .ownerCount = 1});
std::vector<std::string> const metadatas = {
"mutate metadata",
@@ -3691,7 +3668,7 @@ class MPToken_test : public beast::unit_test::Suite
BEAST_EXPECT(!mptAlice.isMetadataPresent());
}
// Mutate transfer fee
// Set transfer fee
{
Env env{*this, features};
MPTTester mptAlice(env, alice);
@@ -3699,8 +3676,7 @@ class MPToken_test : public beast::unit_test::Suite
{.transferFee = 100,
.metadata = "test",
.ownerCount = 1,
.flags = tfMPTCanTransfer,
.mutableFlags = tmfMPTCanMutateTransferFee});
.flags = tfMPTCanTransfer});
for (std::uint16_t const fee :
std::initializer_list<std::uint16_t>{1, 10, 100, 200, 500, 1000, kMaxTransferFee})
@@ -3718,33 +3694,29 @@ class MPToken_test : public beast::unit_test::Suite
BEAST_EXPECT(mptAlice.checkTransferFee(10));
}
// Test mutable flag enablement
// Test setting flags
{
auto testFlagSet = [&](std::uint32_t createFlags, std::uint32_t setFlags) {
auto testFlagSet = [&](std::uint32_t setFlags) {
Env env{*this, features};
MPTTester mptAlice(env, alice);
// Create the MPT object with the specified initial flags
mptAlice.create({.metadata = "test", .ownerCount = 1, .mutableFlags = createFlags});
// Create issuance and the flags can be enabled once by default.
mptAlice.create({.metadata = "test", .ownerCount = 1});
// Setting the same mutable capability more than once is harmless.
mptAlice.set({.account = alice, .mutableFlags = setFlags});
mptAlice.set({.account = alice, .mutableFlags = setFlags});
// Setting the same immutable flag more than once is harmless.
mptAlice.set({.account = alice, .flags = setFlags});
mptAlice.set({.account = alice, .flags = setFlags});
};
testFlagSet(tmfMPTCanEnableCanLock, tmfMPTSetCanLock);
testFlagSet(tmfMPTCanEnableRequireAuth, tmfMPTSetRequireAuth);
testFlagSet(tmfMPTCanEnableCanEscrow, tmfMPTSetCanEscrow);
testFlagSet(tmfMPTCanEnableCanTrade, tmfMPTSetCanTrade);
testFlagSet(tmfMPTCanEnableCanTransfer, tmfMPTSetCanTransfer);
testFlagSet(tmfMPTCanEnableCanClawback, tmfMPTSetCanClawback);
for (auto const& f : MPTokenIssuanceSet::flagMapping)
testFlagSet(f.setFlag);
}
}
void
testMutateCanLock(FeatureBitset features)
testSetCanLock(FeatureBitset features)
{
testcase("Mutate MPTCanLock");
testcase("Set MPTCanLock");
using namespace test::jtx;
Account const alice("alice");
@@ -3754,78 +3726,41 @@ class MPToken_test : public beast::unit_test::Suite
{
Env env{*this, features};
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanLock | tfMPTCanTransfer,
.mutableFlags = tmfMPTCanEnableCanLock | tmfMPTCanEnableCanTrade |
tmfMPTCanMutateTransferFee});
mptAlice.create({.ownerCount = 1, .holderCount = 0});
mptAlice.authorize({.account = bob, .holderCount = 1});
// Lock bob's mptoken
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
// Lock bob's mptoken fails because alice has not enabled MPTCanLock
mptAlice.set(
{.account = alice, .holder = bob, .flags = tfMPTLock, .err = tecNO_PERMISSION});
// Can mutate the mutable flags and fields
mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanLock});
mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanTrade});
mptAlice.set({.account = alice, .transferFee = 200});
// set CanLock
mptAlice.set({.account = alice, .flags = tfMPTSetCanLock});
// Now can lock
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
}
// Global lock
{
Env env{*this, features};
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanLock,
.mutableFlags = tmfMPTCanEnableCanLock | tmfMPTCanEnableCanClawback |
tmfMPTCanMutateMetadata});
mptAlice.create({.ownerCount = 1, .holderCount = 0});
mptAlice.authorize({.account = bob, .holderCount = 1});
// Lock issuance
mptAlice.set({.account = alice, .flags = tfMPTLock});
// Can mutate the mutable flags and fields
mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanLock});
mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanClawback});
mptAlice.set({.account = alice, .metadata = "mutate"});
}
// Test lock and unlock after enabling MPTCanLock
{
Env env{*this, features};
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.mutableFlags = tmfMPTCanEnableCanLock | tmfMPTCanEnableCanClawback |
tmfMPTCanMutateMetadata});
mptAlice.authorize({.account = bob, .holderCount = 1});
// Can not lock or unlock before MPTCanLock is enabled
// Lock issuance fails because alice has not enabled MPTCanLock
mptAlice.set({.account = alice, .flags = tfMPTLock, .err = tecNO_PERMISSION});
mptAlice.set({.account = alice, .flags = tfMPTUnlock, .err = tecNO_PERMISSION});
mptAlice.set(
{.account = alice, .holder = bob, .flags = tfMPTLock, .err = tecNO_PERMISSION});
mptAlice.set(
{.account = alice, .holder = bob, .flags = tfMPTUnlock, .err = tecNO_PERMISSION});
// Set MPTCanLock
mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanLock});
// Can lock and unlock
// Set CanLock
mptAlice.set({.account = alice, .flags = tfMPTSetCanLock});
// Now can lock
mptAlice.set({.account = alice, .flags = tfMPTLock});
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
mptAlice.set({.account = alice, .flags = tfMPTUnlock});
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock});
}
}
void
testMutateRequireAuth(FeatureBitset features)
testSetRequireAuth(FeatureBitset features)
{
testcase("Mutate MPTRequireAuth");
testcase("Set MPTRequireAuth");
using namespace test::jtx;
// test enabling RequireAuth flag on the issuance and its effect on payment
@@ -3835,16 +3770,13 @@ class MPToken_test : public beast::unit_test::Suite
Account const bob("bob");
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create(
{.ownerCount = 1,
.flags = tfMPTCanTransfer,
.mutableFlags = tmfMPTCanEnableRequireAuth});
mptAlice.create({.ownerCount = 1, .flags = tfMPTCanTransfer});
mptAlice.authorize({.account = bob});
mptAlice.pay(alice, bob, 1000);
// Set RequireAuth because it is mutable.
mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetRequireAuth});
// Set RequireAuth
mptAlice.set({.account = alice, .flags = tfMPTSetRequireAuth});
// This should fail because bob is not authorized yet.
mptAlice.pay(alice, bob, 1000, tecNO_AUTH);
@@ -3855,9 +3787,9 @@ class MPToken_test : public beast::unit_test::Suite
}
void
testMutateCanEscrow(FeatureBitset features)
testSetCanEscrow(FeatureBitset features)
{
testcase("Mutate MPTCanEscrow");
testcase("Set MPTCanEscrow");
using namespace test::jtx;
using namespace std::literals;
@@ -3868,11 +3800,7 @@ class MPToken_test : public beast::unit_test::Suite
auto const carol = Account("carol");
MPTTester mptAlice(env, alice, {.holders = {carol, bob}});
mptAlice.create(
{.ownerCount = 1,
.holderCount = 0,
.flags = tfMPTCanTransfer,
.mutableFlags = tmfMPTCanEnableCanEscrow});
mptAlice.create({.ownerCount = 1, .flags = tfMPTCanTransfer});
mptAlice.authorize({.account = carol});
mptAlice.authorize({.account = bob});
@@ -3888,8 +3816,8 @@ class MPToken_test : public beast::unit_test::Suite
Fee(baseFee * 150),
Ter(tecNO_PERMISSION));
// MPTCanEscrow is enabled now
mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanEscrow});
// Set MPTCanEscrow
mptAlice.set({.account = alice, .flags = tfMPTSetCanEscrow});
env(escrow::create(carol, bob, mpt(3)),
escrow::kCondition(escrow::kCb1),
escrow::kFinishTime(env.now() + 1s),
@@ -3897,9 +3825,9 @@ class MPToken_test : public beast::unit_test::Suite
}
void
testMutateCanTransfer(FeatureBitset features)
testSetCanTransfer(FeatureBitset features)
{
testcase("Mutate MPTCanTransfer");
testcase("Set MPTCanTransfer");
using namespace test::jtx;
Account const alice("alice");
@@ -3910,9 +3838,7 @@ class MPToken_test : public beast::unit_test::Suite
Env env{*this, features};
MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
mptAlice.create(
{.ownerCount = 1,
.mutableFlags = tmfMPTCanEnableCanTransfer | tmfMPTCanMutateTransferFee});
mptAlice.create({.ownerCount = 1});
mptAlice.authorize({.account = bob});
mptAlice.authorize({.account = carol});
@@ -3926,20 +3852,10 @@ class MPToken_test : public beast::unit_test::Suite
// Can not set non-zero transfer fee when MPTCanTransfer is not set
mptAlice.set({.account = alice, .transferFee = 100, .err = tecNO_PERMISSION});
// Can not set non-zero transfer fee even when trying to set
// MPTCanTransfer at the same time
mptAlice.set(
{.account = alice,
.mutableFlags = tmfMPTSetCanTransfer,
.transferFee = 100,
.err = tecNO_PERMISSION});
// Alice sets MPTCanTransfer
mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanTransfer});
// Can set transfer fee now
// Set MPTCanTransfer
BEAST_EXPECT(!mptAlice.isTransferFeePresent());
mptAlice.set({.account = alice, .transferFee = 100});
mptAlice.set({.account = alice, .flags = tfMPTSetCanTransfer, .transferFee = 100});
BEAST_EXPECT(mptAlice.checkFlags(lsfMPTCanTransfer));
BEAST_EXPECT(mptAlice.isTransferFeePresent());
// Bob can pay carol
@@ -3958,16 +3874,13 @@ class MPToken_test : public beast::unit_test::Suite
}
}
// Can set transfer fee to zero when tmfMPTCanMutateTransferFee is set.
// Can set transfer fee to zero when transfer fee is mutable (i.e.
// tifMPTTransferFee is not set).
{
Env env{*this, features};
MPTTester mptAlice(env, alice, {.holders = {bob, carol}});
mptAlice.create(
{.transferFee = 100,
.ownerCount = 1,
.flags = tfMPTCanTransfer,
.mutableFlags = tmfMPTCanMutateTransferFee});
mptAlice.create({.transferFee = 100, .ownerCount = 1, .flags = tfMPTCanTransfer});
BEAST_EXPECT(mptAlice.checkTransferFee(100));
@@ -3978,9 +3891,9 @@ class MPToken_test : public beast::unit_test::Suite
}
void
testMutateCanClawback(FeatureBitset features)
testSetCanClawback(FeatureBitset features)
{
testcase("Mutate MPTCanClawback");
testcase("Set MPTCanClawback");
using namespace test::jtx;
Env env(*this, features);
@@ -3989,8 +3902,7 @@ class MPToken_test : public beast::unit_test::Suite
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create(
{.ownerCount = 1, .holderCount = 0, .mutableFlags = tmfMPTCanEnableCanClawback});
mptAlice.create({.ownerCount = 1, .holderCount = 0});
// Bob creates an MPToken
mptAlice.authorize({.account = bob});
@@ -4001,13 +3913,117 @@ class MPToken_test : public beast::unit_test::Suite
// MPTCanClawback is not enabled
mptAlice.claw(alice, bob, 1, tecNO_PERMISSION);
// Enable MPTCanClawback
mptAlice.set({.account = alice, .mutableFlags = tmfMPTSetCanClawback});
// Set MPTCanClawback
mptAlice.set({.account = alice, .flags = tfMPTSetCanClawback});
// Can clawback now
mptAlice.claw(alice, bob, 1);
}
void
testSetImmutableFlags(FeatureBitset features)
{
testcase("Set MPT ImmutableFlags via MPTokenIssuanceSet");
using namespace test::jtx;
Account const alice{"alice"};
Account const bob{"bob"};
// ImmutableFlags requires featureDynamicMPT.
{
Env env(*this, features - featureDynamicMPT);
MPTTester mptAlice(env, alice);
mptAlice.create({.ownerCount = 1});
mptAlice.set(
{.account = alice, .immutableFlags = tifMPTCanClawback, .err = temDISABLED});
}
// ImmutableFlags containing tifMPTCanHoldConfidentialBalance requires
// featureConfidentialTransfer.
{
Env env(*this, features - featureConfidentialTransfer);
MPTTester mptAlice(env, alice);
mptAlice.create({.ownerCount = 1});
mptAlice.set(
{.account = alice,
.immutableFlags = tifMPTCanHoldConfidentialBalance,
.err = temDISABLED});
}
// ImmutableFlags of 0, or containing unknown bits, is rejected.
{
Env env(*this, features);
MPTTester mptAlice(env, alice);
mptAlice.create({.ownerCount = 1});
mptAlice.set({.account = alice, .immutableFlags = 0, .err = temINVALID_FLAG});
mptAlice.set({.account = alice, .immutableFlags = 1, .err = temINVALID_FLAG});
}
// Holder is not allowed alongside ImmutableFlags, and ImmutableFlags
// can not be combined with Lock/Unlock in the same transaction.
{
Env env(*this, features);
MPTTester mptAlice(env, alice, {.holders = {bob}});
mptAlice.create({.ownerCount = 1});
mptAlice.set(
{.account = alice,
.holder = bob,
.immutableFlags = tifMPTCanClawback,
.err = temMALFORMED});
mptAlice.set(
{.account = alice,
.flags = tfMPTLock,
.immutableFlags = tifMPTCanClawback,
.err = temMALFORMED});
}
// Can sets ImmutableFlags and the capability flags in the same transaction
{
Env env(*this, features);
MPTTester mptAlice(env, alice);
mptAlice.create({.ownerCount = 1});
mptAlice.set(
{.account = alice,
.flags = tfMPTSetCanClawback,
.immutableFlags = tifMPTCanClawback});
mptAlice.set(
{.account = alice,
.flags = tfMPTSetCanTransfer | tfMPTSetRequireAuth,
.immutableFlags = tifMPTCanTrade});
}
// Setting ImmutableFlags persists to the ledger, permanently blocks
// enabling the corresponding capability, and merges (rather than
// overwrites) across multiple transactions.
{
Env env(*this, features);
MPTTester mptAlice(env, alice);
mptAlice.create({.ownerCount = 1});
mptAlice.set({.account = alice, .immutableFlags = tifMPTCanClawback});
BEAST_EXPECT(mptAlice.checkImmutableFlags(tifMPTCanClawback));
// The CanClawback can no longer be enabled.
mptAlice.set({.account = alice, .flags = tfMPTSetCanClawback, .err = tecNO_PERMISSION});
// A distinct bit merges with the first rather than overwriting it.
// Both CanClawback and CanTrade are now immutable.
mptAlice.set({.account = alice, .immutableFlags = tifMPTCanTrade});
BEAST_EXPECT(mptAlice.checkImmutableFlags(tifMPTCanClawback | tifMPTCanTrade));
// Setting the same bit again is a harmless no-op.
mptAlice.set({.account = alice, .immutableFlags = tifMPTCanClawback});
BEAST_EXPECT(mptAlice.checkImmutableFlags(tifMPTCanClawback | tifMPTCanTrade));
}
}
void
testMultiSendMaximumAmount(FeatureBitset features)
{
@@ -4398,14 +4414,14 @@ class MPToken_test : public beast::unit_test::Suite
.holders = {alice, carol},
.pay = 100,
.flags = tfMPTCanTransfer,
.mutableFlags = tmfMPTCanEnableCanTrade});
.immutableFlags = tifMPTCanTrade});
MPTTester const eth(
{.env = env,
.issuer = gw,
.holders = {alice, carol},
.pay = 100,
.flags = tfMPTCanTrade,
.mutableFlags = tmfMPTCanEnableCanTrade});
.immutableFlags = tifMPTCanTrade});
// Can't create
env(offer(gw, eth(10), btc(10)), Ter(tecNO_PERMISSION));
@@ -4641,30 +4657,25 @@ class MPToken_test : public beast::unit_test::Suite
.issuer = gw,
.holders = {alice, carol, bob},
.pay = 1'000,
.flags = tfMPTCanLock | kMptDexFlags,
.mutableFlags = tmfMPTCanEnableRequireAuth | tmfMPTCanEnableCanTrade |
tmfMPTCanEnableCanTransfer});
.flags = tfMPTCanLock | kMptDexFlags});
MPTTester eth(
{.env = env,
.issuer = gw,
.holders = {alice, carol, bob},
.pay = 1'000,
.flags = tfMPTCanLock | kMptDexFlags,
.mutableFlags = tmfMPTCanEnableCanTransfer});
.flags = tfMPTCanLock | kMptDexFlags});
MPTTester const usd(
{.env = env,
.issuer = gw,
.holders = {alice, carol, bob},
.pay = 1'000,
.flags = kMptDexFlags | tfMPTCanLock,
.mutableFlags = tmfMPTCanEnableCanTransfer});
.flags = kMptDexFlags | tfMPTCanLock});
MPTTester const cad(
{.env = env,
.issuer = gw,
.holders = {alice, carol, bob},
.pay = 1'000,
.flags = kMptDexFlags | tfMPTCanLock,
.mutableFlags = tmfMPTCanEnableCanTransfer});
.flags = kMptDexFlags | tfMPTCanLock});
env(offer(bob, eth(1'000), btc(1'000)), Txflags(tfPassive));
env.close();
@@ -4694,7 +4705,7 @@ class MPToken_test : public beast::unit_test::Suite
env(pay(gw, ed, eth(100)));
env(pay(gw, ed, btc(100)));
env.close();
btc.set({.mutableFlags = tmfMPTSetRequireAuth});
btc.set({.flags = tfMPTSetRequireAuth});
// authorize bob to enable the offers trading
btc.authorize({.account = gw, .holder = bob});
env.close();
@@ -4932,8 +4943,7 @@ class MPToken_test : public beast::unit_test::Suite
.issuer = gw,
.holders = {alice, carol, bob},
.pay = 1'000,
.flags = tfMPTCanTransfer,
.mutableFlags = tmfMPTCanEnableCanTrade});
.flags = tfMPTCanTransfer});
MPTTester const eth(
{.env = env,
.issuer = gw,
@@ -4952,7 +4962,7 @@ class MPToken_test : public beast::unit_test::Suite
env.close();
// Enable MPTCanTrade so BTC can be crossed through offers.
btc.set({.mutableFlags = tmfMPTSetCanTrade});
btc.set({.flags = tfMPTSetCanTrade});
env(offer(bob, XRP(1), btc(1)));
env(offer(bob, btc(1), eth(1)));
env(offer(bob, eth(1), usd(1)));
@@ -6753,11 +6763,7 @@ class MPToken_test : public beast::unit_test::Suite
env.close();
MPTTester mpt(
{.env = env,
.issuer = gw,
.holders = {alice, carol},
.flags = tfMPTCanTrade,
.mutableFlags = tmfMPTCanEnableCanTransfer});
{.env = env, .issuer = gw, .holders = {alice, carol}, .flags = tfMPTCanTrade});
// src is issuer
uint256 checkId{keylet::check(gw, env.seq(gw)).key};
@@ -6793,7 +6799,7 @@ class MPToken_test : public beast::unit_test::Suite
env.close();
// can create now
mpt.set({.account = gw, .mutableFlags = tmfMPTSetCanTransfer});
mpt.set({.account = gw, .flags = tfMPTSetCanTransfer});
checkId = keylet::check(alice, env.seq(alice)).key;
env(check::create(alice, carol, mpt(100)));
env.close();
@@ -7223,37 +7229,26 @@ class MPToken_test : public beast::unit_test::Suite
auto const txfee = Fee(drops(increment));
auto const badMPT = MPT(gw, 1'000);
auto const makeMPT = [&](std::uint32_t const flags,
Holders holders = {},
std::uint64_t const pay = 0,
std::optional<std::uint32_t> const mutableFlags =
std::nullopt) {
return MPTTester(
{.env = env,
.issuer = gw,
.holders = holders,
.pay = pay ? std::optional<std::uint64_t>{pay} : std::nullopt,
.flags = flags,
.mutableFlags = mutableFlags});
};
auto const makeMPT =
[&](std::uint32_t const flags, Holders holders = {}, std::uint64_t const pay = 0) {
return MPTTester(
{.env = env,
.issuer = gw,
.holders = holders,
.pay = pay ? std::optional<std::uint64_t>{pay} : std::nullopt,
.flags = flags});
};
auto const makeDexMPT = [&](Holders holders = {}, std::uint64_t const pay = 0) {
return makeMPT(
tfMPTCanLock | kMptDexFlags,
holders,
pay,
tmfMPTCanEnableRequireAuth | tmfMPTCanEnableCanTransfer |
tmfMPTCanEnableCanTrade);
return makeMPT(tfMPTCanLock | kMptDexFlags, holders, pay);
};
auto const makeNoTransferMPT = [&](Holders holders = {}, std::uint64_t const pay = 0) {
return makeMPT(
tfMPTCanLock | tfMPTCanTrade, holders, pay, tmfMPTCanEnableCanTransfer);
return makeMPT(tfMPTCanLock | tfMPTCanTrade, holders, pay);
};
auto const makeNoTradeMPT = [&](Holders holders = {}, std::uint64_t const pay = 0) {
return makeMPT(
tfMPTCanLock | tfMPTCanTransfer, holders, pay, tmfMPTCanEnableCanTrade);
return makeMPT(tfMPTCanLock | tfMPTCanTransfer, holders, pay);
};
// AMMCreate
@@ -7299,7 +7294,7 @@ class MPToken_test : public beast::unit_test::Suite
// MPTRequireAuth is set
// alice is not authorized
usd.set({.flags = tfMPTUnlock});
usd.set({.mutableFlags = tmfMPTSetRequireAuth});
usd.set({.flags = tfMPTSetRequireAuth});
createFail(usd, alice, tecNO_AUTH);
// issuer can create
createDeleteAMM(usd, gw);
@@ -7316,7 +7311,7 @@ class MPToken_test : public beast::unit_test::Suite
createFail(usd2, alice, tecNO_AUTH);
// issuer can create
createDeleteAMM(usd2, gw);
usd2.set({.mutableFlags = tmfMPTSetCanTransfer});
usd2.set({.flags = tfMPTSetCanTransfer});
// alice can create
createDeleteAMM(usd2, alice);
}
@@ -7328,7 +7323,7 @@ class MPToken_test : public beast::unit_test::Suite
// alice and issuer can't create
createFail(usd3, alice, tecNO_PERMISSION);
createFail(usd3, gw, tecNO_PERMISSION);
usd3.set({.mutableFlags = tmfMPTSetCanTrade});
usd3.set({.flags = tfMPTSetCanTrade});
// alice can create
createDeleteAMM(usd3, alice);
}
@@ -7383,7 +7378,7 @@ class MPToken_test : public beast::unit_test::Suite
// MPTRequireAuth is set
// carol is not authorized by the issuer
usd.set({.mutableFlags = tmfMPTSetRequireAuth});
usd.set({.flags = tfMPTSetRequireAuth});
env.close();
amm.deposit(
{.account = carol,
@@ -7429,7 +7424,7 @@ class MPToken_test : public beast::unit_test::Suite
.err = Ter(tecNO_AUTH)});
// issuer can deposit
amm2.deposit({.account = gw, .tokens = 1'000});
usd2.set({.mutableFlags = tmfMPTSetCanTransfer});
usd2.set({.flags = tfMPTSetCanTransfer});
// carol can deposit
amm2.deposit({.account = carol, .tokens = 1'000});
}
@@ -7499,7 +7494,7 @@ class MPToken_test : public beast::unit_test::Suite
usd.set({.flags = tfMPTUnlock});
// MPTRequireAuth is set
usd.set({.mutableFlags = tmfMPTSetRequireAuth});
usd.set({.flags = tfMPTSetRequireAuth});
usd.authorize({.account = gw, .holder = carol, .flags = tfMPTUnauthorize});
// carol can't withdraw
amm.withdraw(
@@ -7543,7 +7538,7 @@ class MPToken_test : public beast::unit_test::Suite
usd2.authorize({.account = bob, .flags = tfMPTUnauthorize});
// Can redeem
env(pay(carol, gw, usd2(1)));
usd2.set({.mutableFlags = tmfMPTSetCanTransfer});
usd2.set({.flags = tfMPTSetCanTransfer});
// carol can withdraw
amm2.withdraw({.account = carol, .asset1Out = usd2(1), .asset2Out = eur(1)});
}
@@ -7739,13 +7734,14 @@ public:
// Dynamic MPT
testInvalidCreateDynamic(all);
testInvalidSetDynamic(all);
testMutateMPT(all);
testMutateCanLock(all);
testMutateRequireAuth(all);
testMutateCanEscrow(all);
testMutateCanTransfer(all);
testMutateCanTransfer(all - featureMPTokensV2);
testMutateCanClawback(all);
testSetMPT(all);
testSetCanLock(all);
testSetRequireAuth(all);
testSetCanEscrow(all);
testSetCanTransfer(all);
testSetCanTransfer(all - featureMPTokensV2);
testSetCanClawback(all);
testSetImmutableFlags(all);
// Test offer crossing
testOfferCrossing(all);