mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 11:05:54 +00:00
Add support for DomainID in MPTokenIssuance transactions (#5509)
This change adds support for `DomainID` to existing transactions `MPTokenIssuanceCreate` and `MPTokenIssuanceSet`. In #5224 `DomainID` was added as an access control mechanism for `SingleAssetVault`. The actual implementation of this feature lies in `MPToken` and `MPTokenIssuance`, hence it makes sense to enable the use of `DomainID` also in `MPTokenIssuanceCreate` and `MPTokenIssuanceSet`, following same rules as in Vault: * `MPTokenIssuanceCreate` and `MPTokenIssuanceSet` can only set `DomainID` if flag `MPTRequireAuth` is set. * `MPTokenIssuanceCreate` requires that `DomainID` be a non-zero, uint256 number. * `MPTokenIssuanceSet` allows `DomainID` to be zero (or empty) in which case it will remove `DomainID` from the `MPTokenIssuance` object. The change is amendment-gated by `SingleAssetVault`. This is a non-breaking change because `SingleAssetVault` amendment is `Supported::no`, i.e. at this moment considered a work in progress, which cannot be enabled on the network.
This commit is contained in:
@@ -482,8 +482,7 @@ LEDGER_ENTRY(ltDELEGATE, 0x0083, Delegate, delegate, ({
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
/** A ledger object representing a single asset vault.
|
/** A ledger object representing a single asset vault.
|
||||||
|
\sa keylet::vault
|
||||||
\sa keylet::mptoken
|
|
||||||
*/
|
*/
|
||||||
LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
|
LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
|
||||||
{sfPreviousTxnID, soeREQUIRED},
|
{sfPreviousTxnID, soeREQUIRED},
|
||||||
|
|||||||
@@ -409,6 +409,7 @@ TRANSACTION(ttMPTOKEN_ISSUANCE_CREATE, 54, MPTokenIssuanceCreate, Delegation::de
|
|||||||
{sfTransferFee, soeOPTIONAL},
|
{sfTransferFee, soeOPTIONAL},
|
||||||
{sfMaximumAmount, soeOPTIONAL},
|
{sfMaximumAmount, soeOPTIONAL},
|
||||||
{sfMPTokenMetadata, soeOPTIONAL},
|
{sfMPTokenMetadata, soeOPTIONAL},
|
||||||
|
{sfDomainID, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type destroys a MPTokensIssuance instance */
|
/** This transaction type destroys a MPTokensIssuance instance */
|
||||||
@@ -420,6 +421,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},
|
||||||
|
{sfDomainID, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction type authorizes a MPToken instance */
|
/** This transaction type authorizes a MPToken instance */
|
||||||
@@ -478,7 +480,7 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, Delegation::delegatable, ({
|
|||||||
{sfAsset, soeREQUIRED, soeMPTSupported},
|
{sfAsset, soeREQUIRED, soeMPTSupported},
|
||||||
{sfAssetsMaximum, soeOPTIONAL},
|
{sfAssetsMaximum, soeOPTIONAL},
|
||||||
{sfMPTokenMetadata, soeOPTIONAL},
|
{sfMPTokenMetadata, soeOPTIONAL},
|
||||||
{sfDomainID, soeOPTIONAL}, // PermissionedDomainID
|
{sfDomainID, soeOPTIONAL},
|
||||||
{sfWithdrawalPolicy, soeOPTIONAL},
|
{sfWithdrawalPolicy, soeOPTIONAL},
|
||||||
{sfData, soeOPTIONAL},
|
{sfData, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
@@ -487,7 +489,7 @@ TRANSACTION(ttVAULT_CREATE, 65, VaultCreate, Delegation::delegatable, ({
|
|||||||
TRANSACTION(ttVAULT_SET, 66, VaultSet, Delegation::delegatable, ({
|
TRANSACTION(ttVAULT_SET, 66, VaultSet, Delegation::delegatable, ({
|
||||||
{sfVaultID, soeREQUIRED},
|
{sfVaultID, soeREQUIRED},
|
||||||
{sfAssetsMaximum, soeOPTIONAL},
|
{sfAssetsMaximum, soeOPTIONAL},
|
||||||
{sfDomainID, soeOPTIONAL}, // PermissionedDomainID
|
{sfDomainID, soeOPTIONAL},
|
||||||
{sfData, soeOPTIONAL},
|
{sfData, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|||||||
@@ -18,10 +18,16 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#include <test/jtx.h>
|
#include <test/jtx.h>
|
||||||
|
#include <test/jtx/credentials.h>
|
||||||
|
#include <test/jtx/permissioned_domains.h>
|
||||||
#include <test/jtx/trust.h>
|
#include <test/jtx/trust.h>
|
||||||
#include <test/jtx/xchain_bridge.h>
|
#include <test/jtx/xchain_bridge.h>
|
||||||
|
|
||||||
|
#include <xrpl/basics/base_uint.h>
|
||||||
|
#include <xrpl/beast/utility/Zero.h>
|
||||||
#include <xrpl/protocol/Feature.h>
|
#include <xrpl/protocol/Feature.h>
|
||||||
|
#include <xrpl/protocol/TER.h>
|
||||||
|
#include <xrpl/protocol/TxFlags.h>
|
||||||
#include <xrpl/protocol/jss.h>
|
#include <xrpl/protocol/jss.h>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
@@ -61,6 +67,48 @@ class MPToken_test : public beast::unit_test::suite
|
|||||||
.metadata = "test",
|
.metadata = "test",
|
||||||
.err = temMALFORMED});
|
.err = temMALFORMED});
|
||||||
|
|
||||||
|
if (!features[featureSingleAssetVault])
|
||||||
|
{
|
||||||
|
// tries to set DomainID when SAV is disabled
|
||||||
|
mptAlice.create(
|
||||||
|
{.maxAmt = 100,
|
||||||
|
.assetScale = 0,
|
||||||
|
.metadata = "test",
|
||||||
|
.flags = tfMPTRequireAuth,
|
||||||
|
.domainID = uint256(42),
|
||||||
|
.err = temDISABLED});
|
||||||
|
}
|
||||||
|
else if (!features[featurePermissionedDomains])
|
||||||
|
{
|
||||||
|
// tries to set DomainID when PD is disabled
|
||||||
|
mptAlice.create(
|
||||||
|
{.maxAmt = 100,
|
||||||
|
.assetScale = 0,
|
||||||
|
.metadata = "test",
|
||||||
|
.flags = tfMPTRequireAuth,
|
||||||
|
.domainID = uint256(42),
|
||||||
|
.err = temDISABLED});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// tries to set DomainID when RequireAuth is not set
|
||||||
|
mptAlice.create(
|
||||||
|
{.maxAmt = 100,
|
||||||
|
.assetScale = 0,
|
||||||
|
.metadata = "test",
|
||||||
|
.domainID = uint256(42),
|
||||||
|
.err = temMALFORMED});
|
||||||
|
|
||||||
|
// tries to set zero DomainID
|
||||||
|
mptAlice.create(
|
||||||
|
{.maxAmt = 100,
|
||||||
|
.assetScale = 0,
|
||||||
|
.metadata = "test",
|
||||||
|
.flags = tfMPTRequireAuth,
|
||||||
|
.domainID = beast::zero,
|
||||||
|
.err = temMALFORMED});
|
||||||
|
}
|
||||||
|
|
||||||
// tries to set a txfee greater than max
|
// tries to set a txfee greater than max
|
||||||
mptAlice.create(
|
mptAlice.create(
|
||||||
{.maxAmt = 100,
|
{.maxAmt = 100,
|
||||||
@@ -140,6 +188,48 @@ class MPToken_test : public beast::unit_test::suite
|
|||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
result[sfMaximumAmount.getJsonName()] == "9223372036854775807");
|
result[sfMaximumAmount.getJsonName()] == "9223372036854775807");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (features[featureSingleAssetVault])
|
||||||
|
{
|
||||||
|
// Add permissioned domain
|
||||||
|
Account const credIssuer1{"credIssuer1"};
|
||||||
|
std::string const credType = "credential";
|
||||||
|
|
||||||
|
pdomain::Credentials const credentials1{
|
||||||
|
{.issuer = credIssuer1, .credType = credType}};
|
||||||
|
|
||||||
|
{
|
||||||
|
Env env{*this, features};
|
||||||
|
env.fund(XRP(1000), credIssuer1);
|
||||||
|
|
||||||
|
env(pdomain::setTx(credIssuer1, credentials1));
|
||||||
|
auto const domainId1 = [&]() {
|
||||||
|
auto tx = env.tx()->getJson(JsonOptions::none);
|
||||||
|
return pdomain::getNewDomain(env.meta());
|
||||||
|
}();
|
||||||
|
|
||||||
|
MPTTester mptAlice(env, alice);
|
||||||
|
mptAlice.create({
|
||||||
|
.maxAmt = maxMPTokenAmount, // 9'223'372'036'854'775'807
|
||||||
|
.assetScale = 1,
|
||||||
|
.transferFee = 10,
|
||||||
|
.metadata = "123",
|
||||||
|
.ownerCount = 1,
|
||||||
|
.flags = tfMPTCanLock | tfMPTRequireAuth | tfMPTCanEscrow |
|
||||||
|
tfMPTCanTrade | tfMPTCanTransfer | tfMPTCanClawback,
|
||||||
|
.domainID = domainId1,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get the hash for the most recent transaction.
|
||||||
|
std::string const txHash{
|
||||||
|
env.tx()->getJson(JsonOptions::none)[jss::hash].asString()};
|
||||||
|
|
||||||
|
Json::Value const result = env.rpc("tx", txHash)[jss::result];
|
||||||
|
BEAST_EXPECT(
|
||||||
|
result[sfMaximumAmount.getJsonName()] ==
|
||||||
|
"9223372036854775807");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -499,6 +589,59 @@ class MPToken_test : public beast::unit_test::suite
|
|||||||
.flags = 0x00000008,
|
.flags = 0x00000008,
|
||||||
.err = temINVALID_FLAG});
|
.err = temINVALID_FLAG});
|
||||||
|
|
||||||
|
if (!features[featureSingleAssetVault])
|
||||||
|
{
|
||||||
|
// test invalid flags - nothing is being changed
|
||||||
|
mptAlice.set(
|
||||||
|
{.account = alice,
|
||||||
|
.flags = 0x00000000,
|
||||||
|
.err = tecNO_PERMISSION});
|
||||||
|
|
||||||
|
mptAlice.set(
|
||||||
|
{.account = alice,
|
||||||
|
.holder = bob,
|
||||||
|
.flags = 0x00000000,
|
||||||
|
.err = tecNO_PERMISSION});
|
||||||
|
|
||||||
|
// cannot set DomainID since SAV is not enabled
|
||||||
|
mptAlice.set(
|
||||||
|
{.account = alice,
|
||||||
|
.domainID = uint256(42),
|
||||||
|
.err = temDISABLED});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// test invalid flags - nothing is being changed
|
||||||
|
mptAlice.set(
|
||||||
|
{.account = alice,
|
||||||
|
.flags = 0x00000000,
|
||||||
|
.err = temMALFORMED});
|
||||||
|
|
||||||
|
mptAlice.set(
|
||||||
|
{.account = alice,
|
||||||
|
.holder = bob,
|
||||||
|
.flags = 0x00000000,
|
||||||
|
.err = temMALFORMED});
|
||||||
|
|
||||||
|
if (!features[featurePermissionedDomains])
|
||||||
|
{
|
||||||
|
// cannot set DomainID since PD is not enabled
|
||||||
|
mptAlice.set(
|
||||||
|
{.account = alice,
|
||||||
|
.domainID = uint256(42),
|
||||||
|
.err = temDISABLED});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// cannot set DomainID since Holder is set
|
||||||
|
mptAlice.set(
|
||||||
|
{.account = alice,
|
||||||
|
.holder = bob,
|
||||||
|
.domainID = uint256(42),
|
||||||
|
.err = temMALFORMED});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// set both lock and unlock flags at the same time will fail
|
// set both lock and unlock flags at the same time will fail
|
||||||
mptAlice.set(
|
mptAlice.set(
|
||||||
{.account = alice,
|
{.account = alice,
|
||||||
@@ -582,6 +725,53 @@ class MPToken_test : public beast::unit_test::suite
|
|||||||
mptAlice.set(
|
mptAlice.set(
|
||||||
{.holder = cindy, .flags = tfMPTLock, .err = tecNO_DST});
|
{.holder = cindy, .flags = tfMPTLock, .err = tecNO_DST});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (features[featureSingleAssetVault] &&
|
||||||
|
features[featurePermissionedDomains])
|
||||||
|
{
|
||||||
|
// Add permissioned domain
|
||||||
|
Account const credIssuer1{"credIssuer1"};
|
||||||
|
std::string const credType = "credential";
|
||||||
|
|
||||||
|
pdomain::Credentials const credentials1{
|
||||||
|
{.issuer = credIssuer1, .credType = credType}};
|
||||||
|
|
||||||
|
{
|
||||||
|
Env env{*this, features};
|
||||||
|
|
||||||
|
MPTTester mptAlice(env, alice);
|
||||||
|
mptAlice.create({});
|
||||||
|
|
||||||
|
// Trying to set DomainID on a public MPTokenIssuance
|
||||||
|
mptAlice.set(
|
||||||
|
{.domainID = uint256(42), .err = tecNO_PERMISSION});
|
||||||
|
|
||||||
|
mptAlice.set(
|
||||||
|
{.domainID = beast::zero, .err = tecNO_PERMISSION});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Env env{*this, features};
|
||||||
|
|
||||||
|
MPTTester mptAlice(env, alice);
|
||||||
|
mptAlice.create({.flags = tfMPTRequireAuth});
|
||||||
|
|
||||||
|
// Trying to set non-existing DomainID
|
||||||
|
mptAlice.set(
|
||||||
|
{.domainID = uint256(42), .err = tecOBJECT_NOT_FOUND});
|
||||||
|
|
||||||
|
// Trying to lock but locking is disabled
|
||||||
|
mptAlice.set(
|
||||||
|
{.flags = tfMPTUnlock,
|
||||||
|
.domainID = uint256(42),
|
||||||
|
.err = tecNO_PERMISSION});
|
||||||
|
|
||||||
|
mptAlice.set(
|
||||||
|
{.flags = tfMPTUnlock,
|
||||||
|
.domainID = beast::zero,
|
||||||
|
.err = tecNO_PERMISSION});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -590,71 +780,136 @@ class MPToken_test : public beast::unit_test::suite
|
|||||||
testcase("Enabled set transaction");
|
testcase("Enabled set transaction");
|
||||||
|
|
||||||
using namespace test::jtx;
|
using namespace test::jtx;
|
||||||
|
|
||||||
// Test locking and unlocking
|
|
||||||
Env env{*this, features};
|
|
||||||
Account const alice("alice"); // issuer
|
Account const alice("alice"); // issuer
|
||||||
Account const bob("bob"); // holder
|
Account const bob("bob"); // holder
|
||||||
|
|
||||||
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
|
||||||
|
|
||||||
// create a mptokenissuance with locking
|
|
||||||
mptAlice.create(
|
|
||||||
{.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock});
|
|
||||||
|
|
||||||
mptAlice.authorize({.account = bob, .holderCount = 1});
|
|
||||||
|
|
||||||
// locks bob's mptoken
|
|
||||||
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
|
|
||||||
|
|
||||||
// trying to lock bob's mptoken again will still succeed
|
|
||||||
// but no changes to the objects
|
|
||||||
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
|
|
||||||
|
|
||||||
// alice locks the mptissuance
|
|
||||||
mptAlice.set({.account = alice, .flags = tfMPTLock});
|
|
||||||
|
|
||||||
// alice tries to lock up both mptissuance and mptoken again
|
|
||||||
// it will not change the flags and both will remain locked.
|
|
||||||
mptAlice.set({.account = alice, .flags = tfMPTLock});
|
|
||||||
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
|
|
||||||
|
|
||||||
// alice unlocks bob's mptoken
|
|
||||||
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock});
|
|
||||||
|
|
||||||
// locks up bob's mptoken again
|
|
||||||
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
|
|
||||||
if (!features[featureSingleAssetVault])
|
|
||||||
{
|
{
|
||||||
// Delete bobs' mptoken even though it is locked
|
// Test locking and unlocking
|
||||||
mptAlice.authorize({.account = bob, .flags = tfMPTUnauthorize});
|
Env env{*this, features};
|
||||||
|
|
||||||
|
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
||||||
|
|
||||||
|
// create a mptokenissuance with locking
|
||||||
|
mptAlice.create(
|
||||||
|
{.ownerCount = 1, .holderCount = 0, .flags = tfMPTCanLock});
|
||||||
|
|
||||||
|
mptAlice.authorize({.account = bob, .holderCount = 1});
|
||||||
|
|
||||||
|
// locks bob's mptoken
|
||||||
|
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
|
||||||
|
|
||||||
|
// trying to lock bob's mptoken again will still succeed
|
||||||
|
// but no changes to the objects
|
||||||
|
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
|
||||||
|
|
||||||
|
// alice locks the mptissuance
|
||||||
|
mptAlice.set({.account = alice, .flags = tfMPTLock});
|
||||||
|
|
||||||
|
// alice tries to lock up both mptissuance and mptoken again
|
||||||
|
// it will not change the flags and both will remain locked.
|
||||||
|
mptAlice.set({.account = alice, .flags = tfMPTLock});
|
||||||
|
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
|
||||||
|
|
||||||
|
// alice unlocks bob's mptoken
|
||||||
mptAlice.set(
|
mptAlice.set(
|
||||||
{.account = alice,
|
{.account = alice, .holder = bob, .flags = tfMPTUnlock});
|
||||||
.holder = bob,
|
|
||||||
.flags = tfMPTUnlock,
|
|
||||||
.err = tecOBJECT_NOT_FOUND});
|
|
||||||
|
|
||||||
return;
|
// locks up bob's mptoken again
|
||||||
|
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTLock});
|
||||||
|
if (!features[featureSingleAssetVault])
|
||||||
|
{
|
||||||
|
// Delete bobs' mptoken even though it is locked
|
||||||
|
mptAlice.authorize({.account = bob, .flags = tfMPTUnauthorize});
|
||||||
|
|
||||||
|
mptAlice.set(
|
||||||
|
{.account = alice,
|
||||||
|
.holder = bob,
|
||||||
|
.flags = tfMPTUnlock,
|
||||||
|
.err = tecOBJECT_NOT_FOUND});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cannot delete locked MPToken
|
||||||
|
mptAlice.authorize(
|
||||||
|
{.account = bob,
|
||||||
|
.flags = tfMPTUnauthorize,
|
||||||
|
.err = tecNO_PERMISSION});
|
||||||
|
|
||||||
|
// alice unlocks mptissuance
|
||||||
|
mptAlice.set({.account = alice, .flags = tfMPTUnlock});
|
||||||
|
|
||||||
|
// alice unlocks bob's mptoken
|
||||||
|
mptAlice.set(
|
||||||
|
{.account = alice, .holder = bob, .flags = tfMPTUnlock});
|
||||||
|
|
||||||
|
// alice unlocks mptissuance and bob's mptoken again despite that
|
||||||
|
// they are already unlocked. Make sure this will not change the
|
||||||
|
// flags
|
||||||
|
mptAlice.set(
|
||||||
|
{.account = alice, .holder = bob, .flags = tfMPTUnlock});
|
||||||
|
mptAlice.set({.account = alice, .flags = tfMPTUnlock});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cannot delete locked MPToken
|
if (features[featureSingleAssetVault])
|
||||||
mptAlice.authorize(
|
{
|
||||||
{.account = bob,
|
// Add permissioned domain
|
||||||
.flags = tfMPTUnauthorize,
|
std::string const credType = "credential";
|
||||||
.err = tecNO_PERMISSION});
|
|
||||||
|
|
||||||
// alice unlocks mptissuance
|
// Test setting and resetting domain ID
|
||||||
mptAlice.set({.account = alice, .flags = tfMPTUnlock});
|
Env env{*this, features};
|
||||||
|
|
||||||
// alice unlocks bob's mptoken
|
auto const domainId1 = [&]() {
|
||||||
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock});
|
Account const credIssuer1{"credIssuer1"};
|
||||||
|
env.fund(XRP(1000), credIssuer1);
|
||||||
|
|
||||||
// alice unlocks mptissuance and bob's mptoken again despite that
|
pdomain::Credentials const credentials1{
|
||||||
// they are already unlocked. Make sure this will not change the
|
{.issuer = credIssuer1, .credType = credType}};
|
||||||
// flags
|
|
||||||
mptAlice.set({.account = alice, .holder = bob, .flags = tfMPTUnlock});
|
env(pdomain::setTx(credIssuer1, credentials1));
|
||||||
mptAlice.set({.account = alice, .flags = tfMPTUnlock});
|
return [&]() {
|
||||||
|
auto tx = env.tx()->getJson(JsonOptions::none);
|
||||||
|
return pdomain::getNewDomain(env.meta());
|
||||||
|
}();
|
||||||
|
}();
|
||||||
|
|
||||||
|
auto const domainId2 = [&]() {
|
||||||
|
Account const credIssuer2{"credIssuer2"};
|
||||||
|
env.fund(XRP(1000), credIssuer2);
|
||||||
|
|
||||||
|
pdomain::Credentials const credentials2{
|
||||||
|
{.issuer = credIssuer2, .credType = credType}};
|
||||||
|
|
||||||
|
env(pdomain::setTx(credIssuer2, credentials2));
|
||||||
|
return [&]() {
|
||||||
|
auto tx = env.tx()->getJson(JsonOptions::none);
|
||||||
|
return pdomain::getNewDomain(env.meta());
|
||||||
|
}();
|
||||||
|
}();
|
||||||
|
|
||||||
|
MPTTester mptAlice(env, alice, {.holders = {bob}});
|
||||||
|
|
||||||
|
// create a mptokenissuance with auth.
|
||||||
|
mptAlice.create(
|
||||||
|
{.ownerCount = 1, .holderCount = 0, .flags = tfMPTRequireAuth});
|
||||||
|
BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt));
|
||||||
|
|
||||||
|
// reset "domain not set" to "domain not set", i.e. no change
|
||||||
|
mptAlice.set({.domainID = beast::zero});
|
||||||
|
BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt));
|
||||||
|
|
||||||
|
// reset "domain not set" to domain1
|
||||||
|
mptAlice.set({.domainID = domainId1});
|
||||||
|
BEAST_EXPECT(mptAlice.checkDomainID(domainId1));
|
||||||
|
|
||||||
|
// reset domain1 to domain2
|
||||||
|
mptAlice.set({.domainID = domainId2});
|
||||||
|
BEAST_EXPECT(mptAlice.checkDomainID(domainId2));
|
||||||
|
|
||||||
|
// reset domain to "domain not set"
|
||||||
|
mptAlice.set({.domainID = beast::zero});
|
||||||
|
BEAST_EXPECT(mptAlice.checkDomainID(std::nullopt));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -889,6 +1144,200 @@ class MPToken_test : public beast::unit_test::suite
|
|||||||
mptAlice.pay(bob, alice, 100, tecNO_AUTH);
|
mptAlice.pay(bob, alice, 100, tecNO_AUTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (features[featureSingleAssetVault] &&
|
||||||
|
features[featurePermissionedDomains])
|
||||||
|
{
|
||||||
|
// If RequireAuth is enabled and domain is a match, payment succeeds
|
||||||
|
{
|
||||||
|
Env env{*this, features};
|
||||||
|
std::string const credType = "credential";
|
||||||
|
Account const credIssuer1{"credIssuer1"};
|
||||||
|
env.fund(XRP(1000), credIssuer1, bob);
|
||||||
|
|
||||||
|
auto const domainId1 = [&]() {
|
||||||
|
pdomain::Credentials const credentials1{
|
||||||
|
{.issuer = credIssuer1, .credType = credType}};
|
||||||
|
|
||||||
|
env(pdomain::setTx(credIssuer1, credentials1));
|
||||||
|
return [&]() {
|
||||||
|
auto tx = env.tx()->getJson(JsonOptions::none);
|
||||||
|
return pdomain::getNewDomain(env.meta());
|
||||||
|
}();
|
||||||
|
}();
|
||||||
|
// bob is authorized via domain
|
||||||
|
env(credentials::create(bob, credIssuer1, credType));
|
||||||
|
env(credentials::accept(bob, credIssuer1, credType));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
MPTTester mptAlice(env, alice, {});
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
mptAlice.create({
|
||||||
|
.ownerCount = 1,
|
||||||
|
.holderCount = 0,
|
||||||
|
.flags = tfMPTRequireAuth | tfMPTCanTransfer,
|
||||||
|
.domainID = domainId1,
|
||||||
|
});
|
||||||
|
|
||||||
|
mptAlice.authorize({.account = bob});
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// bob is authorized via domain
|
||||||
|
mptAlice.pay(alice, bob, 100);
|
||||||
|
mptAlice.set({.domainID = beast::zero});
|
||||||
|
|
||||||
|
// bob is no longer authorized
|
||||||
|
mptAlice.pay(alice, bob, 100, tecNO_AUTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Env env{*this, features};
|
||||||
|
std::string const credType = "credential";
|
||||||
|
Account const credIssuer1{"credIssuer1"};
|
||||||
|
env.fund(XRP(1000), credIssuer1, bob);
|
||||||
|
|
||||||
|
auto const domainId1 = [&]() {
|
||||||
|
pdomain::Credentials const credentials1{
|
||||||
|
{.issuer = credIssuer1, .credType = credType}};
|
||||||
|
|
||||||
|
env(pdomain::setTx(credIssuer1, credentials1));
|
||||||
|
return [&]() {
|
||||||
|
auto tx = env.tx()->getJson(JsonOptions::none);
|
||||||
|
return pdomain::getNewDomain(env.meta());
|
||||||
|
}();
|
||||||
|
}();
|
||||||
|
// bob is authorized via domain
|
||||||
|
env(credentials::create(bob, credIssuer1, credType));
|
||||||
|
env(credentials::accept(bob, credIssuer1, credType));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
MPTTester mptAlice(env, alice, {});
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
mptAlice.create({
|
||||||
|
.ownerCount = 1,
|
||||||
|
.holderCount = 0,
|
||||||
|
.flags = tfMPTRequireAuth | tfMPTCanTransfer,
|
||||||
|
.domainID = domainId1,
|
||||||
|
});
|
||||||
|
|
||||||
|
// bob creates an empty MPToken
|
||||||
|
mptAlice.authorize({.account = bob});
|
||||||
|
|
||||||
|
// alice authorizes bob to hold funds
|
||||||
|
mptAlice.authorize({.account = alice, .holder = bob});
|
||||||
|
|
||||||
|
// alice sends 100 MPT to bob
|
||||||
|
mptAlice.pay(alice, bob, 100);
|
||||||
|
|
||||||
|
// alice UNAUTHORIZES bob
|
||||||
|
mptAlice.authorize(
|
||||||
|
{.account = alice,
|
||||||
|
.holder = bob,
|
||||||
|
.flags = tfMPTUnauthorize});
|
||||||
|
|
||||||
|
// bob is still authorized, via domain
|
||||||
|
mptAlice.pay(bob, alice, 10);
|
||||||
|
|
||||||
|
mptAlice.set({.domainID = beast::zero});
|
||||||
|
|
||||||
|
// bob fails to send back to alice because he is no longer
|
||||||
|
// authorize to move his funds!
|
||||||
|
mptAlice.pay(bob, alice, 10, tecNO_AUTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Env env{*this, features};
|
||||||
|
std::string const credType = "credential";
|
||||||
|
// credIssuer1 is the owner of domainId1 and a credential issuer
|
||||||
|
Account const credIssuer1{"credIssuer1"};
|
||||||
|
// credIssuer2 is the owner of domainId2 and a credential issuer
|
||||||
|
// Note, domainId2 also lists credentials issued by credIssuer1
|
||||||
|
Account const credIssuer2{"credIssuer2"};
|
||||||
|
env.fund(XRP(1000), credIssuer1, credIssuer2, bob, carol);
|
||||||
|
|
||||||
|
auto const domainId1 = [&]() {
|
||||||
|
pdomain::Credentials const credentials{
|
||||||
|
{.issuer = credIssuer1, .credType = credType}};
|
||||||
|
|
||||||
|
env(pdomain::setTx(credIssuer1, credentials));
|
||||||
|
return [&]() {
|
||||||
|
auto tx = env.tx()->getJson(JsonOptions::none);
|
||||||
|
return pdomain::getNewDomain(env.meta());
|
||||||
|
}();
|
||||||
|
}();
|
||||||
|
|
||||||
|
auto const domainId2 = [&]() {
|
||||||
|
pdomain::Credentials const credentials{
|
||||||
|
{.issuer = credIssuer1, .credType = credType},
|
||||||
|
{.issuer = credIssuer2, .credType = credType}};
|
||||||
|
|
||||||
|
env(pdomain::setTx(credIssuer2, credentials));
|
||||||
|
return [&]() {
|
||||||
|
auto tx = env.tx()->getJson(JsonOptions::none);
|
||||||
|
return pdomain::getNewDomain(env.meta());
|
||||||
|
}();
|
||||||
|
}();
|
||||||
|
|
||||||
|
// bob is authorized via credIssuer1 which is recognized by both
|
||||||
|
// domainId1 and domainId2
|
||||||
|
env(credentials::create(bob, credIssuer1, credType));
|
||||||
|
env(credentials::accept(bob, credIssuer1, credType));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// carol is authorized via credIssuer2, only recognized by
|
||||||
|
// domainId2
|
||||||
|
env(credentials::create(carol, credIssuer2, credType));
|
||||||
|
env(credentials::accept(carol, credIssuer2, credType));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
MPTTester mptAlice(env, alice, {});
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
mptAlice.create({
|
||||||
|
.ownerCount = 1,
|
||||||
|
.holderCount = 0,
|
||||||
|
.flags = tfMPTRequireAuth | tfMPTCanTransfer,
|
||||||
|
.domainID = domainId1,
|
||||||
|
});
|
||||||
|
|
||||||
|
// bob and carol create an empty MPToken
|
||||||
|
mptAlice.authorize({.account = bob});
|
||||||
|
mptAlice.authorize({.account = carol});
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// alice sends 50 MPT to bob but cannot send to carol
|
||||||
|
mptAlice.pay(alice, bob, 50);
|
||||||
|
mptAlice.pay(alice, carol, 50, tecNO_AUTH);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// bob cannot send to carol because they are not on the same
|
||||||
|
// domain (since credIssuer2 is not recognized by domainId1)
|
||||||
|
mptAlice.pay(bob, carol, 10, tecNO_AUTH);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// alice updates domainID to domainId2 which recognizes both
|
||||||
|
// credIssuer1 and credIssuer2
|
||||||
|
mptAlice.set({.domainID = domainId2});
|
||||||
|
// alice can now send to carol
|
||||||
|
mptAlice.pay(alice, carol, 10);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// bob can now send to carol because both are in the same
|
||||||
|
// domain
|
||||||
|
mptAlice.pay(bob, carol, 10);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// bob loses his authorization and can no longer send MPT
|
||||||
|
env(credentials::deleteCred(
|
||||||
|
credIssuer1, bob, credIssuer1, credType));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
mptAlice.pay(bob, carol, 10, tecNO_AUTH);
|
||||||
|
mptAlice.pay(bob, alice, 10, tecNO_AUTH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Non-issuer cannot send to each other if MPTCanTransfer isn't set
|
// Non-issuer cannot send to each other if MPTCanTransfer isn't set
|
||||||
{
|
{
|
||||||
Env env(*this, features);
|
Env env(*this, features);
|
||||||
@@ -1340,10 +1789,8 @@ class MPToken_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
testDepositPreauth()
|
testDepositPreauth(FeatureBitset features)
|
||||||
{
|
{
|
||||||
testcase("DepositPreauth");
|
|
||||||
|
|
||||||
using namespace test::jtx;
|
using namespace test::jtx;
|
||||||
Account const alice("alice"); // issuer
|
Account const alice("alice"); // issuer
|
||||||
Account const bob("bob"); // holder
|
Account const bob("bob"); // holder
|
||||||
@@ -1352,8 +1799,11 @@ class MPToken_test : public beast::unit_test::suite
|
|||||||
|
|
||||||
char const credType[] = "abcde";
|
char const credType[] = "abcde";
|
||||||
|
|
||||||
|
if (features[featureCredentials])
|
||||||
{
|
{
|
||||||
Env env(*this);
|
testcase("DepositPreauth");
|
||||||
|
|
||||||
|
Env env(*this, features);
|
||||||
|
|
||||||
env.fund(XRP(50000), diana, dpIssuer);
|
env.fund(XRP(50000), diana, dpIssuer);
|
||||||
env.close();
|
env.close();
|
||||||
@@ -2297,6 +2747,8 @@ public:
|
|||||||
|
|
||||||
// MPTokenIssuanceCreate
|
// MPTokenIssuanceCreate
|
||||||
testCreateValidation(all - featureSingleAssetVault);
|
testCreateValidation(all - featureSingleAssetVault);
|
||||||
|
testCreateValidation(
|
||||||
|
(all | featureSingleAssetVault) - featurePermissionedDomains);
|
||||||
testCreateValidation(all | featureSingleAssetVault);
|
testCreateValidation(all | featureSingleAssetVault);
|
||||||
testCreateEnabled(all - featureSingleAssetVault);
|
testCreateEnabled(all - featureSingleAssetVault);
|
||||||
testCreateEnabled(all | featureSingleAssetVault);
|
testCreateEnabled(all | featureSingleAssetVault);
|
||||||
@@ -2314,7 +2766,11 @@ public:
|
|||||||
testAuthorizeEnabled(all | featureSingleAssetVault);
|
testAuthorizeEnabled(all | featureSingleAssetVault);
|
||||||
|
|
||||||
// MPTokenIssuanceSet
|
// MPTokenIssuanceSet
|
||||||
testSetValidation(all);
|
testSetValidation(all - featureSingleAssetVault);
|
||||||
|
testSetValidation(
|
||||||
|
(all | featureSingleAssetVault) - featurePermissionedDomains);
|
||||||
|
testSetValidation(all | featureSingleAssetVault);
|
||||||
|
|
||||||
testSetEnabled(all - featureSingleAssetVault);
|
testSetEnabled(all - featureSingleAssetVault);
|
||||||
testSetEnabled(all | featureSingleAssetVault);
|
testSetEnabled(all | featureSingleAssetVault);
|
||||||
|
|
||||||
@@ -2323,8 +2779,9 @@ public:
|
|||||||
testClawback(all);
|
testClawback(all);
|
||||||
|
|
||||||
// Test Direct Payment
|
// Test Direct Payment
|
||||||
testPayment(all);
|
testPayment(all | featureSingleAssetVault);
|
||||||
testDepositPreauth();
|
testDepositPreauth(all);
|
||||||
|
testDepositPreauth(all - featureCredentials);
|
||||||
|
|
||||||
// Test MPT Amount is invalid in Tx, which don't support MPT
|
// Test MPT Amount is invalid in Tx, which don't support MPT
|
||||||
testMPTInvalidInTx(all);
|
testMPTInvalidInTx(all);
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -99,6 +100,8 @@ MPTTester::create(MPTCreate const& arg)
|
|||||||
jv[sfMPTokenMetadata] = strHex(*arg.metadata);
|
jv[sfMPTokenMetadata] = strHex(*arg.metadata);
|
||||||
if (arg.maxAmt)
|
if (arg.maxAmt)
|
||||||
jv[sfMaximumAmount] = std::to_string(*arg.maxAmt);
|
jv[sfMaximumAmount] = std::to_string(*arg.maxAmt);
|
||||||
|
if (arg.domainID)
|
||||||
|
jv[sfDomainID] = to_string(*arg.domainID);
|
||||||
if (submit(arg, jv) != tesSUCCESS)
|
if (submit(arg, jv) != tesSUCCESS)
|
||||||
{
|
{
|
||||||
// Verify issuance doesn't exist
|
// Verify issuance doesn't exist
|
||||||
@@ -235,6 +238,8 @@ MPTTester::set(MPTSet const& arg)
|
|||||||
jv[sfHolder] = arg.holder->human();
|
jv[sfHolder] = arg.holder->human();
|
||||||
if (arg.delegate)
|
if (arg.delegate)
|
||||||
jv[sfDelegate] = arg.delegate->human();
|
jv[sfDelegate] = arg.delegate->human();
|
||||||
|
if (arg.domainID)
|
||||||
|
jv[sfDomainID] = to_string(*arg.domainID);
|
||||||
if (submit(arg, jv) == tesSUCCESS && arg.flags.value_or(0))
|
if (submit(arg, jv) == tesSUCCESS && arg.flags.value_or(0))
|
||||||
{
|
{
|
||||||
auto require = [&](std::optional<Account> const& holder,
|
auto require = [&](std::optional<Account> const& holder,
|
||||||
@@ -272,6 +277,16 @@ MPTTester::forObject(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool
|
||||||
|
MPTTester::checkDomainID(std::optional<uint256> expected) const
|
||||||
|
{
|
||||||
|
return forObject([&](SLEP const& sle) -> bool {
|
||||||
|
if (sle->isFieldPresent(sfDomainID))
|
||||||
|
return expected == sle->getFieldH256(sfDomainID);
|
||||||
|
return (!expected.has_value());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool
|
[[nodiscard]] bool
|
||||||
MPTTester::checkMPTokenAmount(
|
MPTTester::checkMPTokenAmount(
|
||||||
Account const& holder_,
|
Account const& holder_,
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ struct MPTCreate
|
|||||||
std::optional<std::uint32_t> holderCount = std::nullopt;
|
std::optional<std::uint32_t> holderCount = std::nullopt;
|
||||||
bool fund = true;
|
bool fund = true;
|
||||||
std::optional<std::uint32_t> flags = {0};
|
std::optional<std::uint32_t> flags = {0};
|
||||||
|
std::optional<uint256> domainID = std::nullopt;
|
||||||
std::optional<TER> err = std::nullopt;
|
std::optional<TER> err = std::nullopt;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -139,6 +140,7 @@ struct MPTSet
|
|||||||
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;
|
||||||
std::optional<Account> delegate = std::nullopt;
|
std::optional<Account> delegate = std::nullopt;
|
||||||
|
std::optional<uint256> domainID = std::nullopt;
|
||||||
std::optional<TER> err = std::nullopt;
|
std::optional<TER> err = std::nullopt;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -165,6 +167,9 @@ public:
|
|||||||
void
|
void
|
||||||
set(MPTSet const& set = {});
|
set(MPTSet const& set = {});
|
||||||
|
|
||||||
|
[[nodiscard]] bool
|
||||||
|
checkDomainID(std::optional<uint256> expected) const;
|
||||||
|
|
||||||
[[nodiscard]] bool
|
[[nodiscard]] bool
|
||||||
checkMPTokenAmount(Account const& holder, std::int64_t expectedAmount)
|
checkMPTokenAmount(Account const& holder, std::int64_t expectedAmount)
|
||||||
const;
|
const;
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx)
|
|||||||
if (!ctx.rules.enabled(featureMPTokensV1))
|
if (!ctx.rules.enabled(featureMPTokensV1))
|
||||||
return temDISABLED;
|
return temDISABLED;
|
||||||
|
|
||||||
|
if (ctx.tx.isFieldPresent(sfDomainID) &&
|
||||||
|
!(ctx.rules.enabled(featurePermissionedDomains) &&
|
||||||
|
ctx.rules.enabled(featureSingleAssetVault)))
|
||||||
|
return temDISABLED;
|
||||||
|
|
||||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
@@ -48,6 +53,16 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx)
|
|||||||
return temMALFORMED;
|
return temMALFORMED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto const domain = ctx.tx[~sfDomainID])
|
||||||
|
{
|
||||||
|
if (*domain == beast::zero)
|
||||||
|
return temMALFORMED;
|
||||||
|
|
||||||
|
// Domain present implies that MPTokenIssuance is not public
|
||||||
|
if ((ctx.tx.getFlags() & tfMPTRequireAuth) == 0)
|
||||||
|
return temMALFORMED;
|
||||||
|
}
|
||||||
|
|
||||||
if (auto const metadata = ctx.tx[~sfMPTokenMetadata])
|
if (auto const metadata = ctx.tx[~sfMPTokenMetadata])
|
||||||
{
|
{
|
||||||
if (metadata->length() == 0 ||
|
if (metadata->length() == 0 ||
|
||||||
@@ -142,6 +157,7 @@ MPTokenIssuanceCreate::doApply()
|
|||||||
.assetScale = tx[~sfAssetScale],
|
.assetScale = tx[~sfAssetScale],
|
||||||
.transferFee = tx[~sfTransferFee],
|
.transferFee = tx[~sfTransferFee],
|
||||||
.metadata = tx[~sfMPTokenMetadata],
|
.metadata = tx[~sfMPTokenMetadata],
|
||||||
|
.domainId = tx[~sfDomainID],
|
||||||
});
|
});
|
||||||
return result ? tesSUCCESS : result.error();
|
return result ? tesSUCCESS : result.error();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#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/TxFlags.h>
|
#include <xrpl/protocol/TxFlags.h>
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
@@ -31,6 +32,14 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
|
|||||||
if (!ctx.rules.enabled(featureMPTokensV1))
|
if (!ctx.rules.enabled(featureMPTokensV1))
|
||||||
return temDISABLED;
|
return temDISABLED;
|
||||||
|
|
||||||
|
if (ctx.tx.isFieldPresent(sfDomainID) &&
|
||||||
|
!(ctx.rules.enabled(featurePermissionedDomains) &&
|
||||||
|
ctx.rules.enabled(featureSingleAssetVault)))
|
||||||
|
return temDISABLED;
|
||||||
|
|
||||||
|
if (ctx.tx.isFieldPresent(sfDomainID) && ctx.tx.isFieldPresent(sfHolder))
|
||||||
|
return temMALFORMED;
|
||||||
|
|
||||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
@@ -48,6 +57,13 @@ MPTokenIssuanceSet::preflight(PreflightContext const& ctx)
|
|||||||
if (holderID && accountID == holderID)
|
if (holderID && accountID == holderID)
|
||||||
return temMALFORMED;
|
return temMALFORMED;
|
||||||
|
|
||||||
|
if (ctx.rules.enabled(featureSingleAssetVault))
|
||||||
|
{
|
||||||
|
// Is this transaction actually changing anything ?
|
||||||
|
if (txFlags == 0 && !ctx.tx.isFieldPresent(sfDomainID))
|
||||||
|
return temMALFORMED;
|
||||||
|
}
|
||||||
|
|
||||||
return preflight2(ctx);
|
return preflight2(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,9 +113,14 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
|
|||||||
if (!sleMptIssuance)
|
if (!sleMptIssuance)
|
||||||
return tecOBJECT_NOT_FOUND;
|
return tecOBJECT_NOT_FOUND;
|
||||||
|
|
||||||
// if the mpt has disabled locking
|
if (!sleMptIssuance->isFlag(lsfMPTCanLock))
|
||||||
if (!((*sleMptIssuance)[sfFlags] & lsfMPTCanLock))
|
{
|
||||||
return tecNO_PERMISSION;
|
// For readability two separate `if` rather than `||` of two conditions
|
||||||
|
if (!ctx.view.rules().enabled(featureSingleAssetVault))
|
||||||
|
return tecNO_PERMISSION;
|
||||||
|
else if (ctx.tx.isFlag(tfMPTLock) || ctx.tx.isFlag(tfMPTUnlock))
|
||||||
|
return tecNO_PERMISSION;
|
||||||
|
}
|
||||||
|
|
||||||
// ensure it is issued by the tx submitter
|
// ensure it is issued by the tx submitter
|
||||||
if ((*sleMptIssuance)[sfIssuer] != ctx.tx[sfAccount])
|
if ((*sleMptIssuance)[sfIssuer] != ctx.tx[sfAccount])
|
||||||
@@ -117,6 +138,20 @@ MPTokenIssuanceSet::preclaim(PreclaimContext const& ctx)
|
|||||||
return tecOBJECT_NOT_FOUND;
|
return tecOBJECT_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto const domain = ctx.tx[~sfDomainID])
|
||||||
|
{
|
||||||
|
if (not sleMptIssuance->isFlag(lsfMPTRequireAuth))
|
||||||
|
return tecNO_PERMISSION;
|
||||||
|
|
||||||
|
if (*domain != beast::zero)
|
||||||
|
{
|
||||||
|
auto const sleDomain =
|
||||||
|
ctx.view.read(keylet::permissionedDomain(*domain));
|
||||||
|
if (!sleDomain)
|
||||||
|
return tecOBJECT_NOT_FOUND;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +161,7 @@ MPTokenIssuanceSet::doApply()
|
|||||||
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
auto const mptIssuanceID = ctx_.tx[sfMPTokenIssuanceID];
|
||||||
auto const txFlags = ctx_.tx.getFlags();
|
auto const txFlags = ctx_.tx.getFlags();
|
||||||
auto const holderID = ctx_.tx[~sfHolder];
|
auto const holderID = ctx_.tx[~sfHolder];
|
||||||
|
auto const domainID = ctx_.tx[~sfDomainID];
|
||||||
std::shared_ptr<SLE> sle;
|
std::shared_ptr<SLE> sle;
|
||||||
|
|
||||||
if (holderID)
|
if (holderID)
|
||||||
@@ -147,6 +183,24 @@ MPTokenIssuanceSet::doApply()
|
|||||||
if (flagsIn != flagsOut)
|
if (flagsIn != flagsOut)
|
||||||
sle->setFieldU32(sfFlags, flagsOut);
|
sle->setFieldU32(sfFlags, flagsOut);
|
||||||
|
|
||||||
|
if (domainID)
|
||||||
|
{
|
||||||
|
// This is enforced in preflight.
|
||||||
|
XRPL_ASSERT(
|
||||||
|
sle->getType() == ltMPTOKEN_ISSUANCE,
|
||||||
|
"MPTokenIssuanceSet::doApply : modifying MPTokenIssuance");
|
||||||
|
|
||||||
|
if (*domainID != beast::zero)
|
||||||
|
{
|
||||||
|
sle->setFieldH256(sfDomainID, *domainID);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (sle->isFieldPresent(sfDomainID))
|
||||||
|
sle->makeFieldAbsent(sfDomainID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
view().update(sle);
|
view().update(sle);
|
||||||
|
|
||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
|
|||||||
Reference in New Issue
Block a user