mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-26 08:30:30 +00:00
Merge remote-tracking branch 'origin/develop' into merge-develop-sponsor
This commit is contained in:
@@ -1599,7 +1599,7 @@ class Vault_test : public beast::unit_test::Suite
|
||||
{.flags = tfMPTCanTransfer | tfMPTCanLock |
|
||||
(args.enableClawback ? tfMPTCanClawback : kNone) |
|
||||
(args.requireAuth ? tfMPTRequireAuth : kNone),
|
||||
.mutableFlags = tmfMPTCanMutateCanTransfer});
|
||||
.mutableFlags = tmfMPTCanEnableCanTransfer});
|
||||
PrettyAsset const asset = mptt.issuanceID();
|
||||
mptt.authorize({.account = owner});
|
||||
mptt.authorize({.account = depositor});
|
||||
@@ -2238,149 +2238,6 @@ class Vault_test : public beast::unit_test::Suite
|
||||
env.close();
|
||||
}
|
||||
|
||||
testCase([this](
|
||||
Env& env,
|
||||
Account const&,
|
||||
Account const& owner,
|
||||
Account const& depositor,
|
||||
PrettyAsset const& asset,
|
||||
Vault& vault,
|
||||
MPTTester& mptt) {
|
||||
testcase("MPT non-transferable: block deposit, allow withdraw");
|
||||
|
||||
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
tx = vault.deposit({.depositor = depositor, .id = keylet.key, .amount = asset(100)});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
// Issuer governance: clear CanTransfer. New exposure must be
|
||||
// blocked, but recovery paths must remain open so existing
|
||||
// depositors are not trapped.
|
||||
mptt.set({.mutableFlags = tmfMPTClearCanTransfer});
|
||||
env.close();
|
||||
|
||||
// New deposit is blocked.
|
||||
env(tx, Ter{tecNO_AUTH});
|
||||
env.close();
|
||||
|
||||
// Existing depositor can always withdraw, even though the asset
|
||||
// is no longer freely transferable.
|
||||
tx = vault.withdraw({.depositor = depositor, .id = keylet.key, .amount = asset(100)});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
// Delete vault with zero balance
|
||||
env(vault.del({.owner = owner, .id = keylet.key}));
|
||||
});
|
||||
|
||||
{
|
||||
testcase("MPT non-transferable: pre-fixCleanup3_2_0 withdraw blocked");
|
||||
|
||||
// Regression: before fixCleanup3_2_0 a depositor was trapped if
|
||||
// the issuer cleared lsfMPTCanTransfer. Verify that the legacy
|
||||
// (broken) behavior is preserved when the amendment is disabled.
|
||||
Env env{*this, testableAmendments() - fixCleanup3_2_0};
|
||||
Account const issuer{"issuer"};
|
||||
Account const owner{"owner"};
|
||||
Account const depositor{"depositor"};
|
||||
env.fund(XRP(10'000), issuer, owner, depositor);
|
||||
env.close();
|
||||
Vault const vault{env};
|
||||
|
||||
MPTTester mptt{env, issuer, kMptInitNoFund};
|
||||
mptt.create(
|
||||
{.flags = tfMPTCanTransfer | tfMPTCanLock,
|
||||
.mutableFlags = tmfMPTCanMutateCanTransfer});
|
||||
PrettyAsset const asset = mptt.issuanceID();
|
||||
mptt.authorize({.account = owner});
|
||||
mptt.authorize({.account = depositor});
|
||||
env(pay(issuer, depositor, asset(1'000)));
|
||||
env.close();
|
||||
|
||||
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
env(vault.deposit({.depositor = depositor, .id = keylet.key, .amount = asset(100)}));
|
||||
env.close();
|
||||
|
||||
mptt.set({.mutableFlags = tmfMPTClearCanTransfer});
|
||||
env.close();
|
||||
|
||||
// Pre-amendment: deposit blocked (matches new behavior).
|
||||
env(vault.deposit({.depositor = depositor, .id = keylet.key, .amount = asset(100)}),
|
||||
Ter{tecNO_AUTH});
|
||||
env.close();
|
||||
|
||||
// Pre-amendment: withdraw is also blocked - this is the bug
|
||||
// that fixCleanup3_2_0 fixes.
|
||||
env(vault.withdraw({.depositor = depositor, .id = keylet.key, .amount = asset(100)}),
|
||||
Ter{tecNO_AUTH});
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
testcase("MPT non-transferable: vault shares inherit restriction");
|
||||
|
||||
Env env{*this, testableAmendments()};
|
||||
Account const issuer{"issuer"};
|
||||
Account const owner{"owner"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10'000), issuer, owner, alice, bob);
|
||||
env.close();
|
||||
Vault const vault{env};
|
||||
|
||||
MPTTester mptt{env, issuer, kMptInitNoFund};
|
||||
mptt.create(
|
||||
{.flags = tfMPTCanTransfer | tfMPTCanLock,
|
||||
.mutableFlags = tmfMPTCanMutateCanTransfer});
|
||||
PrettyAsset const asset = mptt.issuanceID();
|
||||
mptt.authorize({.account = owner});
|
||||
mptt.authorize({.account = alice});
|
||||
mptt.authorize({.account = bob});
|
||||
env(pay(issuer, alice, asset(1'000)));
|
||||
env(pay(issuer, bob, asset(1'000)));
|
||||
env.close();
|
||||
|
||||
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(500)}));
|
||||
// Bob also deposits so he has a share MPToken to receive into.
|
||||
env(vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(500)}));
|
||||
env.close();
|
||||
|
||||
auto const shares = [&]() -> PrettyAsset {
|
||||
auto const sle = env.le(keylet);
|
||||
BEAST_EXPECT(sle != nullptr);
|
||||
return MPTIssue(sle->at(sfShareMPTID));
|
||||
}();
|
||||
|
||||
// Sanity: while CanTransfer is set on the underlying, peer-to-peer
|
||||
// share transfers are allowed.
|
||||
env(pay(alice, bob, shares(1)));
|
||||
env.close();
|
||||
|
||||
// Issuer governance: clear CanTransfer on the underlying.
|
||||
mptt.set({.mutableFlags = tmfMPTClearCanTransfer});
|
||||
env.close();
|
||||
|
||||
// Vault shares inherit the restriction: third-party share-to-share
|
||||
// payments are blocked.
|
||||
env(pay(alice, bob, shares(1)), Ter{tecNO_AUTH});
|
||||
env.close();
|
||||
|
||||
// Recovery path: existing share holders can still redeem shares
|
||||
// for the underlying asset via VaultWithdraw.
|
||||
env(vault.withdraw({.depositor = alice, .id = keylet.key, .amount = shares(1)}));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
testcase("MPT locked: vault shares inherit underlying lock");
|
||||
|
||||
@@ -2458,56 +2315,6 @@ class Vault_test : public beast::unit_test::Suite
|
||||
BEAST_EXPECT(expectOffers(env, alice, 1));
|
||||
}
|
||||
|
||||
{
|
||||
testcase("MPT non-transferable: pre-fixCleanup3_2_0 share transfer succeeds");
|
||||
|
||||
// Regression: before fixCleanup3_2_0 a peer-to-peer share Payment
|
||||
// succeeded even when the underlying asset's lsfMPTCanTransfer
|
||||
// was cleared. Verify that the legacy (non-inheriting) behavior
|
||||
// is preserved when the amendment is disabled.
|
||||
Env env{*this, testableAmendments() - fixCleanup3_2_0};
|
||||
Account const issuer{"issuer"};
|
||||
Account const owner{"owner"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
env.fund(XRP(10'000), issuer, owner, alice, bob);
|
||||
env.close();
|
||||
Vault const vault{env};
|
||||
|
||||
MPTTester mptt{env, issuer, kMptInitNoFund};
|
||||
mptt.create(
|
||||
{.flags = tfMPTCanTransfer | tfMPTCanLock,
|
||||
.mutableFlags = tmfMPTCanMutateCanTransfer});
|
||||
PrettyAsset const asset = mptt.issuanceID();
|
||||
mptt.authorize({.account = owner});
|
||||
mptt.authorize({.account = alice});
|
||||
mptt.authorize({.account = bob});
|
||||
env(pay(issuer, alice, asset(1'000)));
|
||||
env(pay(issuer, bob, asset(1'000)));
|
||||
env.close();
|
||||
|
||||
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(500)}));
|
||||
env(vault.deposit({.depositor = bob, .id = keylet.key, .amount = asset(500)}));
|
||||
env.close();
|
||||
|
||||
auto const shares = [&]() -> PrettyAsset {
|
||||
auto const sle = env.le(keylet);
|
||||
BEAST_EXPECT(sle != nullptr);
|
||||
return MPTIssue(sle->at(sfShareMPTID));
|
||||
}();
|
||||
|
||||
mptt.set({.mutableFlags = tmfMPTClearCanTransfer});
|
||||
env.close();
|
||||
|
||||
// Pre-amendment: share transfer leaks past underlying restriction.
|
||||
env(pay(alice, bob, shares(1)));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
testcase("MPT CanTrade governance: share inherits underlying on DEX and AMM");
|
||||
|
||||
@@ -2522,8 +2329,8 @@ class Vault_test : public beast::unit_test::Suite
|
||||
|
||||
MPTTester mptt{env, issuer, kMptInitNoFund};
|
||||
mptt.create(
|
||||
{.flags = tfMPTCanTransfer | tfMPTCanTrade | tfMPTCanLock,
|
||||
.mutableFlags = tmfMPTCanMutateCanTrade});
|
||||
{.flags = tfMPTCanTransfer | tfMPTCanLock,
|
||||
.mutableFlags = tmfMPTCanEnableCanTrade});
|
||||
PrettyAsset const asset = mptt.issuanceID();
|
||||
mptt.authorize({.account = owner});
|
||||
mptt.authorize({.account = alice});
|
||||
@@ -2547,38 +2354,18 @@ class Vault_test : public beast::unit_test::Suite
|
||||
return MPTIssue(sle->at(sfShareMPTID));
|
||||
}();
|
||||
|
||||
// Sanity: while CanTrade is set on the underlying, both the asset
|
||||
// and the vault share can be placed on the DEX.
|
||||
env(offer(alice, XRP(1), asset(10)));
|
||||
env(offer(alice, XRP(1), shares(1)));
|
||||
env.close();
|
||||
|
||||
// Issuer governance: clear CanTrade on the underlying.
|
||||
mptt.set({.mutableFlags = tmfMPTClearCanTrade});
|
||||
env.close();
|
||||
|
||||
// Control: clearing CanTrade on the underlying is observable on
|
||||
// the DEX path for that asset.
|
||||
// CanTrade is not set on the underlying, both the asset and
|
||||
// the vault share are blocked on the DEX.
|
||||
env(offer(alice, XRP(1), asset(10)), Ter{tecNO_PERMISSION});
|
||||
env(offer(alice, XRP(1), shares(1)), Ter{tecNO_PERMISSION});
|
||||
env.close();
|
||||
|
||||
// Control: clearing CanTrade on the underlying is also observable
|
||||
// on the AMM path for that asset.
|
||||
AMM const ammUnderlyingFails(
|
||||
// The inherited CanTrade restriction also blocks AMM creation.
|
||||
AMM const ammUnderlyingFail(
|
||||
env, alice, XRP(1'000), asset(1'000), Ter{tecNO_PERMISSION});
|
||||
|
||||
// Post-fixCleanup3_2_0: vault shares inherit the underlying's
|
||||
// CanTrade restriction on the DEX path (canTrade reads the
|
||||
// share's sfReferenceHolding and dispatches to the underlying).
|
||||
env(offer(bob, XRP(1), shares(1)), Ter{tecNO_PERMISSION});
|
||||
env.close();
|
||||
|
||||
// checkMPTAllowed mirrors the inheritance for AMM/Offer-
|
||||
// crossing/Check paths, so a share AMM also cannot be created
|
||||
// when the underlying CanTrade is cleared.
|
||||
AMM const ammShares(env, alice, XRP(1'000), shares(100), Ter{tecNO_PERMISSION});
|
||||
|
||||
// Deposit still works (canAddHolding does not consult the field).
|
||||
// Deposit still works before enabling CanTrade.
|
||||
env(vault.deposit({.depositor = alice, .id = keylet.key, .amount = asset(100)}));
|
||||
env.close();
|
||||
|
||||
@@ -2587,9 +2374,19 @@ class Vault_test : public beast::unit_test::Suite
|
||||
env(pay(alice, bob, shares(1)));
|
||||
env.close();
|
||||
|
||||
// Withdraw still works.
|
||||
// Withdraw still works before enabling CanTrade.
|
||||
env(vault.withdraw({.depositor = alice, .id = keylet.key, .amount = asset(100)}));
|
||||
env.close();
|
||||
|
||||
// Enable CanTrade on the underlying.
|
||||
mptt.set({.mutableFlags = tmfMPTSetCanTrade});
|
||||
env.close();
|
||||
|
||||
env(offer(alice, XRP(1), asset(10)));
|
||||
env(offer(alice, XRP(1), shares(1)));
|
||||
env.close();
|
||||
|
||||
AMM const ammUnderlying(env, alice, XRP(1'000), asset(1'000));
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user