Compare commits

...

1 Commits

Author SHA1 Message Date
Bronek Kozicki
c365da1208 WIP 2025-07-17 11:57:18 +01:00
9 changed files with 68 additions and 1 deletions

View File

@@ -187,6 +187,7 @@ enum LedgerSpecificFlags {
lsfMPTCanTrade = 0x00000010, lsfMPTCanTrade = 0x00000010,
lsfMPTCanTransfer = 0x00000020, lsfMPTCanTransfer = 0x00000020,
lsfMPTCanClawback = 0x00000040, lsfMPTCanClawback = 0x00000040,
lsfMPTMutableMeta = 0x00000080,
// ltMPTOKEN // ltMPTOKEN
lsfMPTAuthorized = 0x00000002, lsfMPTAuthorized = 0x00000002,

View File

@@ -148,8 +148,9 @@ constexpr std::uint32_t const tfMPTCanEscrow = lsfMPTCanEscrow;
constexpr std::uint32_t const tfMPTCanTrade = lsfMPTCanTrade; constexpr std::uint32_t const tfMPTCanTrade = lsfMPTCanTrade;
constexpr std::uint32_t const tfMPTCanTransfer = lsfMPTCanTransfer; constexpr std::uint32_t const tfMPTCanTransfer = lsfMPTCanTransfer;
constexpr std::uint32_t const tfMPTCanClawback = lsfMPTCanClawback; constexpr std::uint32_t const tfMPTCanClawback = lsfMPTCanClawback;
constexpr std::uint32_t const tfMPTMutableMeta = lsfMPTMutableMeta;
constexpr std::uint32_t const tfMPTokenIssuanceCreateMask = constexpr std::uint32_t const tfMPTokenIssuanceCreateMask =
~(tfUniversal | tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow | tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback); ~(tfUniversal | tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow | tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback | tfMPTMutableMeta);
// MPTokenAuthorize flags: // MPTokenAuthorize flags:
constexpr std::uint32_t const tfMPTUnauthorize = 0x00000001; constexpr std::uint32_t const tfMPTUnauthorize = 0x00000001;

View File

@@ -35,6 +35,7 @@
// If you add an amendment here, then do not forget to increment `numFeatures` // If you add an amendment here, then do not forget to increment `numFeatures`
// in include/xrpl/protocol/Feature.h. // in include/xrpl/protocol/Feature.h.
XRPL_FEATURE(MPTMutableMeta, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (AMMClawbackRounding, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (AMMClawbackRounding, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo)

View File

@@ -420,6 +420,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_DESTROY, 55, MPTokenIssuanceDestroy, Delegation::
TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, Delegation::delegatable, ({ TRANSACTION(ttMPTOKEN_ISSUANCE_SET, 56, MPTokenIssuanceSet, Delegation::delegatable, ({
{sfMPTokenIssuanceID, soeREQUIRED}, {sfMPTokenIssuanceID, soeREQUIRED},
{sfHolder, soeOPTIONAL}, {sfHolder, soeOPTIONAL},
{sfMPTokenMetadata, soeOPTIONAL},
})) }))
/** This transaction type authorizes a MPToken instance */ /** This transaction type authorizes a MPToken instance */

View File

@@ -22,6 +22,7 @@
#include <test/jtx/xchain_bridge.h> #include <test/jtx/xchain_bridge.h>
#include <xrpl/protocol/Feature.h> #include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/TxFlags.h>
#include <xrpl/protocol/jss.h> #include <xrpl/protocol/jss.h>
namespace ripple { namespace ripple {
@@ -86,6 +87,14 @@ class MPToken_test : public beast::unit_test::suite
.metadata = "", .metadata = "",
.err = temMALFORMED}); .err = temMALFORMED});
// oversized metadata returns error
mptAlice.create(
{.maxAmt = 100,
.assetScale = 0,
.transferFee = 0,
.metadata = std::string(2050, 'B'),
.err = temMALFORMED});
// MaximumAmout of 0 returns error // MaximumAmout of 0 returns error
mptAlice.create( mptAlice.create(
{.maxAmt = 0, {.maxAmt = 0,
@@ -108,6 +117,29 @@ class MPToken_test : public beast::unit_test::suite
.metadata = "test", .metadata = "test",
.err = temMALFORMED}); .err = temMALFORMED});
} }
// test preflight tfMPTMutableMeta when feature is disabled
{
Env env{*this, features - featureMPTMutableMeta};
MPTTester mptAlice(env, alice);
mptAlice.create(
{.maxAmt = 100,
.assetScale = 0,
.transferFee = 0,
.flags = tfMPTMutableMeta,
.err = temDISABLED}
);
mptAlice.create(
{.maxAmt = 100,
.assetScale = 0,
.transferFee = 0,
.metadata = "abc",
.flags = tfMPTMutableMeta,
.err = temDISABLED}
);
}
} }
void void
@@ -512,6 +544,14 @@ class MPToken_test : public beast::unit_test::suite
.holder = alice, .holder = alice,
.flags = tfMPTLock, .flags = tfMPTLock,
.err = temMALFORMED}); .err = temMALFORMED});
// Oversized metadata
mptAlice.set(
{.account = alice,
.holder = alice,
.metadata = std::string(2050, 'B'),
.flags = tfMPTLock,
.err = temMALFORMED});
} }
// Validate fields in MPTokenIssuanceSet (preclaim) // Validate fields in MPTokenIssuanceSet (preclaim)

View File

@@ -19,6 +19,7 @@
#include <test/jtx.h> #include <test/jtx.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/jss.h> #include <xrpl/protocol/jss.h>
namespace ripple { namespace ripple {
@@ -231,6 +232,8 @@ MPTTester::set(MPTSet const& arg)
Throw<std::runtime_error>("MPT has not been created"); Throw<std::runtime_error>("MPT has not been created");
jv[sfMPTokenIssuanceID] = to_string(*id_); jv[sfMPTokenIssuanceID] = to_string(*id_);
} }
if (arg.metadata)
jv[sfMPTokenMetadata] = strHex(*arg.metadata);
if (arg.holder) if (arg.holder)
jv[sfHolder] = arg.holder->human(); jv[sfHolder] = arg.holder->human();
if (arg.delegate) if (arg.delegate)

View File

@@ -135,6 +135,7 @@ struct MPTSet
std::optional<Account> account = std::nullopt; std::optional<Account> account = std::nullopt;
std::optional<Account> holder = std::nullopt; std::optional<Account> holder = std::nullopt;
std::optional<MPTID> id = std::nullopt; std::optional<MPTID> id = std::nullopt;
std::optional<std::string> metadata = std::nullopt;
std::optional<std::uint32_t> ownerCount = std::nullopt; std::optional<std::uint32_t> ownerCount = std::nullopt;
std::optional<std::uint32_t> holderCount = std::nullopt; std::optional<std::uint32_t> holderCount = std::nullopt;
std::optional<std::uint32_t> flags = std::nullopt; std::optional<std::uint32_t> flags = std::nullopt;

View File

@@ -30,6 +30,8 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx)
{ {
if (!ctx.rules.enabled(featureMPTokensV1)) if (!ctx.rules.enabled(featureMPTokensV1))
return temDISABLED; return temDISABLED;
if (ctx.tx.isFlag(tfMPTMutableMeta) && !ctx.rules.enabled(featureMPTMutableMeta))
return temDISABLED;
if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret; return ret;

View File

@@ -21,6 +21,9 @@
#include <xrpld/app/tx/detail/MPTokenIssuanceSet.h> #include <xrpld/app/tx/detail/MPTokenIssuanceSet.h>
#include <xrpl/protocol/Feature.h> #include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/LedgerFormats.h>
#include <xrpl/protocol/SField.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/TxFlags.h> #include <xrpl/protocol/TxFlags.h>
namespace ripple { namespace ripple {
@@ -30,6 +33,8 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
{ {
if (!ctx.rules.enabled(featureMPTokensV1)) if (!ctx.rules.enabled(featureMPTokensV1))
return temDISABLED; return temDISABLED;
if (ctx.tx.isFieldPresent(sfMPTokenMetadata) && !ctx.rules.enabled(featureMPTMutableMeta))
return temDISABLED;
if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
return ret; return ret;
@@ -43,6 +48,13 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
else if ((txFlags & tfMPTLock) && (txFlags & tfMPTUnlock)) else if ((txFlags & tfMPTLock) && (txFlags & tfMPTUnlock))
return temINVALID_FLAG; return temINVALID_FLAG;
if (auto const metadata = ctx.tx[~sfMPTokenMetadata])
{
// Note: metadata->length() == 0 is valid here, will erase metatdata
if (metadata->length() > maxMPTokenMetadataLength)
return temMALFORMED;
}
auto const accountID = ctx.tx[sfAccount]; auto const accountID = ctx.tx[sfAccount];
auto const holderID = ctx.tx[~sfHolder]; auto const holderID = ctx.tx[~sfHolder];
if (holderID && accountID == holderID) if (holderID && accountID == holderID)
@@ -97,6 +109,11 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
if (!sleMptIssuance) if (!sleMptIssuance)
return tecOBJECT_NOT_FOUND; return tecOBJECT_NOT_FOUND;
if (ctx.tx.isFieldPresent(sfMPTokenMetadata) && !sleMptIssuance->isFlag(lsfMPTMutableMeta))
return tecNO_PERMISSION;
// TODO check below is invalid if we are trying to update metadata
// if the mpt has disabled locking // if the mpt has disabled locking
if (!((*sleMptIssuance)[sfFlags] & lsfMPTCanLock)) if (!((*sleMptIssuance)[sfFlags] & lsfMPTCanLock))
return tecNO_PERMISSION; return tecNO_PERMISSION;