mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-06 18:26:51 +00:00
Compare commits
1 Commits
bthomee/jq
...
gregtatcam
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df0ed95383 |
@@ -58,6 +58,13 @@ isVaultPseudoAccountFrozen(
|
||||
MPTIssue const& mptShare,
|
||||
std::uint8_t depth);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isVaultPseudoAccountFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
SLE const& issuanceSle,
|
||||
std::uint8_t depth);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isLPTokenFrozen(
|
||||
ReadView const& view,
|
||||
|
||||
@@ -23,9 +23,15 @@ namespace xrpl {
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isGlobalFrozen(SLE const& issuanceSle);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isIndividualFrozen(SLE const& mptSle);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isFrozen(
|
||||
ReadView const& view,
|
||||
@@ -33,6 +39,9 @@ isFrozen(
|
||||
MPTIssue const& mptIssue,
|
||||
std::uint8_t depth = 0);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, SLE const& mptSle, std::uint8_t depth = 0);
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
|
||||
@@ -52,12 +52,10 @@ hasExpired(ReadView const& view, std::optional<std::uint32_t> const& exp)
|
||||
return exp && (view.parentCloseTime() >= tp{d{*exp}});
|
||||
}
|
||||
|
||||
bool
|
||||
isVaultPseudoAccountFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptShare,
|
||||
std::uint8_t depth)
|
||||
namespace {
|
||||
|
||||
std::optional<bool>
|
||||
checkVaultPseudoAccountFrozenPreconditions(ReadView const& view, std::uint8_t depth)
|
||||
{
|
||||
if (!view.rules().enabled(featureSingleAssetVault))
|
||||
return false;
|
||||
@@ -70,21 +68,31 @@ isVaultPseudoAccountFrozen(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
|
||||
auto const mptIssuance = view.read(keylet::mptIssuance(mptShare.getMptID()));
|
||||
if (mptIssuance == nullptr)
|
||||
return false; // zero MPToken won't block deletion of MPTokenIssuance
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto const issuer = mptIssuance->getAccountID(sfIssuer);
|
||||
bool
|
||||
isVaultPseudoAccountFrozenForIssuance(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
SLE const& issuanceSle,
|
||||
std::uint8_t depth)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
issuanceSle.getType() == ltMPTOKEN_ISSUANCE,
|
||||
"xrpl::isVaultPseudoAccountFrozenForIssuance : MPTokenIssuance SLE");
|
||||
|
||||
auto const issuer = issuanceSle.getAccountID(sfIssuer);
|
||||
|
||||
// Post-fixCleanup3_2_0: vault shares carry sfReferenceHolding pointing
|
||||
// to the vault pseudo's MPToken or RippleState for the underlying.
|
||||
// Read it to derive the underlying asset and recurse, skipping the
|
||||
// issuer-account-then-vault chain. Pre-amendment shares (no field)
|
||||
// fall back to the chain lookup below.
|
||||
if (mptIssuance->isFieldPresent(sfReferenceHolding))
|
||||
if (issuanceSle.isFieldPresent(sfReferenceHolding))
|
||||
{
|
||||
auto const sleHolding =
|
||||
view.read(keylet::unchecked(mptIssuance->getFieldH256(sfReferenceHolding)));
|
||||
view.read(keylet::unchecked(issuanceSle.getFieldH256(sfReferenceHolding)));
|
||||
if (!sleHolding)
|
||||
{
|
||||
// LCOV_EXCL_START
|
||||
@@ -93,7 +101,7 @@ isVaultPseudoAccountFrozen(
|
||||
// LCOV_EXCL_STOP
|
||||
}
|
||||
return isAnyFrozen(
|
||||
view, {issuer, account}, assetOfHolding(*mptIssuance, *sleHolding), depth + 1);
|
||||
view, {issuer, account}, assetOfHolding(issuanceSle, *sleHolding), depth + 1);
|
||||
}
|
||||
|
||||
auto const mptIssuer = view.read(keylet::account(issuer));
|
||||
@@ -119,6 +127,38 @@ isVaultPseudoAccountFrozen(
|
||||
return isAnyFrozen(view, {issuer, account}, vault->at(sfAsset), depth + 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool
|
||||
isVaultPseudoAccountFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
SLE const& issuanceSle,
|
||||
std::uint8_t depth)
|
||||
{
|
||||
if (auto const result = checkVaultPseudoAccountFrozenPreconditions(view, depth))
|
||||
return *result;
|
||||
|
||||
return isVaultPseudoAccountFrozenForIssuance(view, account, issuanceSle, depth);
|
||||
}
|
||||
|
||||
bool
|
||||
isVaultPseudoAccountFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
MPTIssue const& mptShare,
|
||||
std::uint8_t depth)
|
||||
{
|
||||
if (auto const result = checkVaultPseudoAccountFrozenPreconditions(view, depth))
|
||||
return *result;
|
||||
|
||||
auto const issuanceSle = view.read(keylet::mptIssuance(mptShare.getMptID()));
|
||||
if (issuanceSle == nullptr)
|
||||
return false; // zero MPToken won't block deletion of MPTokenIssuance
|
||||
|
||||
return isVaultPseudoAccountFrozenForIssuance(view, account, *issuanceSle, depth);
|
||||
}
|
||||
|
||||
bool
|
||||
isLPTokenFrozen(
|
||||
ReadView const& view,
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <xrpl/ledger/ReadView.h>
|
||||
#include <xrpl/ledger/Sandbox.h>
|
||||
#include <xrpl/ledger/View.h>
|
||||
#include <xrpl/ledger/helpers/MPTokenHelpers.h>
|
||||
#include <xrpl/ledger/helpers/RippleStateHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
@@ -598,7 +599,7 @@ ammAccountHolds(ReadView const& view, AccountID const& ammAccountID, Asset const
|
||||
return asset.visit(
|
||||
[&](MPTIssue const& issue) {
|
||||
if (auto const sle = view.read(keylet::mptoken(issue, ammAccountID));
|
||||
sle && !isFrozen(view, ammAccountID, issue))
|
||||
sle && !isFrozen(view, ammAccountID, *sle))
|
||||
return STAmount{issue, (*sle)[sfMPTAmount]};
|
||||
return STAmount{asset};
|
||||
},
|
||||
|
||||
@@ -37,22 +37,53 @@
|
||||
|
||||
namespace xrpl {
|
||||
|
||||
namespace {
|
||||
|
||||
bool
|
||||
isMPTLocked(SLE const& sle)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
sle.getType() == ltMPTOKEN || sle.getType() == ltMPTOKEN_ISSUANCE,
|
||||
"xrpl::isMPTLocked : MPToken or MPTokenIssuance SLE");
|
||||
|
||||
return sle.isFlag(lsfMPTLocked);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool
|
||||
isGlobalFrozen(ReadView const& view, MPTIssue const& mptIssue)
|
||||
{
|
||||
if (auto const sle = view.read(keylet::mptIssuance(mptIssue.getMptID())))
|
||||
return sle->isFlag(lsfMPTLocked);
|
||||
return isGlobalFrozen(*sle);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
isGlobalFrozen(SLE const& issuance)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
issuance.getType() == ltMPTOKEN_ISSUANCE, "xrpl::isGlobalFrozen : MPTokenIssuance SLE");
|
||||
|
||||
return isMPTLocked(issuance);
|
||||
}
|
||||
|
||||
bool
|
||||
isIndividualFrozen(ReadView const& view, AccountID const& account, MPTIssue const& mptIssue)
|
||||
{
|
||||
if (auto const sle = view.read(keylet::mptoken(mptIssue.getMptID(), account)))
|
||||
return sle->isFlag(lsfMPTLocked);
|
||||
return isIndividualFrozen(*sle);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
isIndividualFrozen(SLE const& mptSle)
|
||||
{
|
||||
XRPL_ASSERT(mptSle.getType() == ltMPTOKEN, "xrpl::isIndividualFrozen : MPToken SLE");
|
||||
|
||||
return isMPTLocked(mptSle);
|
||||
}
|
||||
|
||||
bool
|
||||
isFrozen(
|
||||
ReadView const& view,
|
||||
@@ -64,6 +95,23 @@ isFrozen(
|
||||
isVaultPseudoAccountFrozen(view, account, mptIssue, depth);
|
||||
}
|
||||
|
||||
bool
|
||||
isFrozen(ReadView const& view, AccountID const& account, SLE const& mptSle, std::uint8_t depth)
|
||||
{
|
||||
XRPL_ASSERT(mptSle.getType() == ltMPTOKEN, "xrpl::isFrozen : MPToken SLE");
|
||||
|
||||
MPTID const mptID = mptSle[sfMPTokenIssuanceID];
|
||||
auto const issuanceSle = view.read(keylet::mptIssuance(mptID));
|
||||
|
||||
if ((issuanceSle && isGlobalFrozen(*issuanceSle)) || isIndividualFrozen(mptSle))
|
||||
return true;
|
||||
|
||||
if (issuanceSle)
|
||||
return isVaultPseudoAccountFrozen(view, account, *issuanceSle, depth);
|
||||
|
||||
return isVaultPseudoAccountFrozen(view, account, MPTIssue{mptID}, depth);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isAnyFrozen(
|
||||
ReadView const& view,
|
||||
|
||||
@@ -338,7 +338,7 @@ accountHolds(
|
||||
{
|
||||
amount.clear(mptIssue);
|
||||
}
|
||||
else if (zeroIfFrozen == FreezeHandling::ZeroIfFrozen && isFrozen(view, account, mptIssue))
|
||||
else if (zeroIfFrozen == FreezeHandling::ZeroIfFrozen && isFrozen(view, account, *sleMpt))
|
||||
{
|
||||
amount.clear(mptIssue);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/ledger/ApplyView.h>
|
||||
#include <xrpl/ledger/helpers/AMMHelpers.h>
|
||||
#include <xrpl/ledger/helpers/TokenHelpers.h>
|
||||
#include <xrpl/protocol/AMMCore.h>
|
||||
#include <xrpl/protocol/AccountID.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -7075,6 +7076,63 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testDanglingAMMMPTokenFreezeCheck()
|
||||
{
|
||||
testcase("Dangling AMM MPToken freeze check");
|
||||
|
||||
using namespace jtx;
|
||||
FeatureBitset const all{testableAmendments()};
|
||||
|
||||
Env env(
|
||||
*this,
|
||||
envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->fees.referenceFee = XRPAmount(1);
|
||||
return cfg;
|
||||
}),
|
||||
all);
|
||||
|
||||
env.fund(XRP(1'000), gw_, alice_);
|
||||
MPTTester usd({.env = env, .issuer = gw_});
|
||||
MPTTester const btc({.env = env, .issuer = gw_});
|
||||
|
||||
AMM amm(env, gw_, usd(10'000), btc(10'000));
|
||||
for (auto i = 0; i < kMaxDeletableAmmTrustLines + 10; ++i)
|
||||
{
|
||||
Account const a{std::to_string(i)};
|
||||
env.fund(XRP(1'000), a);
|
||||
env(trust(a, STAmount{amm.lptIssue(), 10'000}));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// With too many LP-token trust lines to delete in one pass, the AMM
|
||||
// remains in an empty state with zero-balance MPToken objects.
|
||||
amm.withdrawAll(gw_);
|
||||
BEAST_EXPECT(amm.ammExists());
|
||||
BEAST_EXPECT(amm.expectBalances(usd(0), btc(0), IOUAmount{0}));
|
||||
|
||||
auto const ammToken = env.le(keylet::mptoken(usd.issuanceID(), amm.ammAccount()));
|
||||
if (!BEAST_EXPECT(ammToken))
|
||||
return;
|
||||
BEAST_EXPECT((*ammToken)[sfMPTAmount] == 0);
|
||||
|
||||
usd.destroy();
|
||||
BEAST_EXPECT(env.le(keylet::mptIssuance(usd.issuanceID())) == nullptr);
|
||||
|
||||
// A Payment cannot cross this empty AMM because BookStep skips AMMs
|
||||
// with zero LPTokenBalance. Probe the same ZeroIfFrozen balance read
|
||||
// used by AMM accounting.
|
||||
auto const balance = accountHolds(
|
||||
*env.current(),
|
||||
amm.ammAccount(),
|
||||
MPTIssue{usd.issuanceID()},
|
||||
FreezeHandling::ZeroIfFrozen,
|
||||
AuthHandling::IgnoreAuth,
|
||||
env.journal);
|
||||
|
||||
BEAST_EXPECT(balance == usd(0));
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
@@ -7110,6 +7168,7 @@ private:
|
||||
testLPTokenBalance(all - fixAMMv1_3);
|
||||
testAMMDepositWithFrozenAssets();
|
||||
testAutoDelete();
|
||||
testDanglingAMMMPTokenFreezeCheck();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user