mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-19 10:35:50 +00:00
Add deep freeze feature (XLS-77d) (#5187)
- spec: XRPLF/XRPL-Standards#220 - amendment: "DeepFreeze" - implemented deep freeze spec to allow token issuers to prevent currency holders from being able to acquire more of these tokens. - in combination with normal freeze, deep freeze effectively prevents any balance trust line balance change of a currency holder (except direct issuer <-> holder payments). - added 2 new invariant checks to verify that deep freeze cannot be enacted without normal freeze and transfer is not frozen. - made some fixes to existing freeze handling. Co-authored-by: Ed Hennis <ed@ripple.com> Co-authored-by: Howard Hinnant <howard.hinnant@gmail.com>
This commit is contained in:
@@ -80,7 +80,7 @@ namespace detail {
|
||||
// Feature.cpp. Because it's only used to reserve storage, and determine how
|
||||
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
|
||||
// the actual number of amendments. A LogicError on startup will verify this.
|
||||
static constexpr std::size_t numFeatures = 85;
|
||||
static constexpr std::size_t numFeatures = 86;
|
||||
|
||||
/** Amendments that this server supports and the default voting behavior.
|
||||
Whether they are enabled depends on the Rules defined in the validated
|
||||
|
||||
@@ -160,10 +160,12 @@ enum LedgerSpecificFlags {
|
||||
lsfHighAuth = 0x00080000,
|
||||
lsfLowNoRipple = 0x00100000,
|
||||
lsfHighNoRipple = 0x00200000,
|
||||
lsfLowFreeze = 0x00400000, // True, low side has set freeze flag
|
||||
lsfHighFreeze = 0x00800000, // True, high side has set freeze flag
|
||||
lsfAMMNode = 0x01000000, // True, trust line to AMM. Used by client
|
||||
// apps to identify payments via AMM.
|
||||
lsfLowFreeze = 0x00400000, // True, low side has set freeze flag
|
||||
lsfHighFreeze = 0x00800000, // True, high side has set freeze flag
|
||||
lsfLowDeepFreeze = 0x02000000, // True, low side has set deep freeze flag
|
||||
lsfHighDeepFreeze = 0x04000000, // True, high side has set deep freeze flag
|
||||
lsfAMMNode = 0x01000000, // True, trust line to AMM. Used by client
|
||||
// apps to identify payments via AMM.
|
||||
|
||||
// ltSIGNER_LIST
|
||||
lsfOneOwnerCount = 0x00010000, // True, uses only one OwnerCount
|
||||
|
||||
@@ -114,9 +114,11 @@ constexpr std::uint32_t tfSetNoRipple = 0x00020000;
|
||||
constexpr std::uint32_t tfClearNoRipple = 0x00040000;
|
||||
constexpr std::uint32_t tfSetFreeze = 0x00100000;
|
||||
constexpr std::uint32_t tfClearFreeze = 0x00200000;
|
||||
constexpr std::uint32_t tfSetDeepFreeze = 0x00400000;
|
||||
constexpr std::uint32_t tfClearDeepFreeze = 0x00800000;
|
||||
constexpr std::uint32_t tfTrustSetMask =
|
||||
~(tfUniversal | tfSetfAuth | tfSetNoRipple | tfClearNoRipple | tfSetFreeze |
|
||||
tfClearFreeze);
|
||||
tfClearFreeze | tfSetDeepFreeze | tfClearDeepFreeze);
|
||||
|
||||
// EnableAmendment flags:
|
||||
constexpr std::uint32_t tfGotMajority = 0x00010000;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DynamicNFT, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Credentials, Supported::yes, VoteBehavior::DefaultNo)
|
||||
@@ -116,3 +117,4 @@ XRPL_FIX (NFTokenNegOffer, Supported::yes, VoteBehavior::Obsolete)
|
||||
XRPL_FIX (NFTokenDirV1, Supported::yes, VoteBehavior::Obsolete)
|
||||
XRPL_FEATURE(NonFungibleTokensV1, Supported::yes, VoteBehavior::Obsolete)
|
||||
XRPL_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete)
|
||||
|
||||
|
||||
@@ -284,6 +284,8 @@ JSS(flags); // out: AccountOffers,
|
||||
JSS(forward); // in: AccountTx
|
||||
JSS(freeze); // out: AccountLines
|
||||
JSS(freeze_peer); // out: AccountLines
|
||||
JSS(deep_freeze); // out: AccountLines
|
||||
JSS(deep_freeze_peer); // out: AccountLines
|
||||
JSS(frozen_balances); // out: GatewayBalances
|
||||
JSS(full); // in: LedgerClearer, handlers/Ledger
|
||||
JSS(full_reply); // out: PathFind
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -408,6 +408,183 @@ class Invariants_test : public beast::unit_test::suite
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
testNoDeepFreezeTrustLinesWithoutFreeze()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
testcase << "trust lines with deep freeze flag without freeze "
|
||||
"not allowed";
|
||||
doInvariantCheck(
|
||||
{{"a trust line with deep freeze flag without normal freeze was "
|
||||
"created"}},
|
||||
[](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
auto const sleNew = std::make_shared<SLE>(
|
||||
keylet::line(A1, A2, A1["USD"].currency));
|
||||
sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
|
||||
sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
|
||||
|
||||
std::uint32_t uFlags = 0u;
|
||||
uFlags |= lsfLowDeepFreeze;
|
||||
sleNew->setFieldU32(sfFlags, uFlags);
|
||||
ac.view().insert(sleNew);
|
||||
return true;
|
||||
});
|
||||
|
||||
doInvariantCheck(
|
||||
{{"a trust line with deep freeze flag without normal freeze was "
|
||||
"created"}},
|
||||
[](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
auto const sleNew = std::make_shared<SLE>(
|
||||
keylet::line(A1, A2, A1["USD"].currency));
|
||||
sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
|
||||
sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
|
||||
std::uint32_t uFlags = 0u;
|
||||
uFlags |= lsfHighDeepFreeze;
|
||||
sleNew->setFieldU32(sfFlags, uFlags);
|
||||
ac.view().insert(sleNew);
|
||||
return true;
|
||||
});
|
||||
|
||||
doInvariantCheck(
|
||||
{{"a trust line with deep freeze flag without normal freeze was "
|
||||
"created"}},
|
||||
[](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
auto const sleNew = std::make_shared<SLE>(
|
||||
keylet::line(A1, A2, A1["USD"].currency));
|
||||
sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
|
||||
sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
|
||||
std::uint32_t uFlags = 0u;
|
||||
uFlags |= lsfLowDeepFreeze | lsfHighDeepFreeze;
|
||||
sleNew->setFieldU32(sfFlags, uFlags);
|
||||
ac.view().insert(sleNew);
|
||||
return true;
|
||||
});
|
||||
|
||||
doInvariantCheck(
|
||||
{{"a trust line with deep freeze flag without normal freeze was "
|
||||
"created"}},
|
||||
[](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
auto const sleNew = std::make_shared<SLE>(
|
||||
keylet::line(A1, A2, A1["USD"].currency));
|
||||
sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
|
||||
sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
|
||||
std::uint32_t uFlags = 0u;
|
||||
uFlags |= lsfLowDeepFreeze | lsfHighFreeze;
|
||||
sleNew->setFieldU32(sfFlags, uFlags);
|
||||
ac.view().insert(sleNew);
|
||||
return true;
|
||||
});
|
||||
|
||||
doInvariantCheck(
|
||||
{{"a trust line with deep freeze flag without normal freeze was "
|
||||
"created"}},
|
||||
[](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
auto const sleNew = std::make_shared<SLE>(
|
||||
keylet::line(A1, A2, A1["USD"].currency));
|
||||
sleNew->setFieldAmount(sfLowLimit, A1["USD"](0));
|
||||
sleNew->setFieldAmount(sfHighLimit, A1["USD"](0));
|
||||
std::uint32_t uFlags = 0u;
|
||||
uFlags |= lsfLowFreeze | lsfHighDeepFreeze;
|
||||
sleNew->setFieldU32(sfFlags, uFlags);
|
||||
ac.view().insert(sleNew);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
testTransfersNotFrozen()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
testcase << "transfers when frozen";
|
||||
|
||||
Account G1{"G1"};
|
||||
// Helper function to establish the trustlines
|
||||
auto const createTrustlines =
|
||||
[&](Account const& A1, Account const& A2, Env& env) {
|
||||
// Preclose callback to establish trust lines with gateway
|
||||
env.fund(XRP(1000), G1);
|
||||
|
||||
env.trust(G1["USD"](10000), A1);
|
||||
env.trust(G1["USD"](10000), A2);
|
||||
env.close();
|
||||
|
||||
env(pay(G1, A1, G1["USD"](1000)));
|
||||
env(pay(G1, A2, G1["USD"](1000)));
|
||||
env.close();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto const A1FrozenByIssuer =
|
||||
[&](Account const& A1, Account const& A2, Env& env) {
|
||||
createTrustlines(A1, A2, env);
|
||||
env(trust(G1, A1["USD"](10000), tfSetFreeze));
|
||||
env.close();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto const A1DeepFrozenByIssuer =
|
||||
[&](Account const& A1, Account const& A2, Env& env) {
|
||||
A1FrozenByIssuer(A1, A2, env);
|
||||
env(trust(G1, A1["USD"](10000), tfSetDeepFreeze));
|
||||
env.close();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
auto const changeBalances = [&](Account const& A1,
|
||||
Account const& A2,
|
||||
ApplyContext& ac,
|
||||
int A1Balance,
|
||||
int A2Balance) {
|
||||
auto const sleA1 = ac.view().peek(keylet::line(A1, G1["USD"]));
|
||||
auto const sleA2 = ac.view().peek(keylet::line(A2, G1["USD"]));
|
||||
|
||||
sleA1->setFieldAmount(sfBalance, G1["USD"](A1Balance));
|
||||
sleA2->setFieldAmount(sfBalance, G1["USD"](A2Balance));
|
||||
|
||||
ac.view().update(sleA1);
|
||||
ac.view().update(sleA2);
|
||||
};
|
||||
|
||||
// test: imitating frozen A1 making a payment to A2.
|
||||
doInvariantCheck(
|
||||
{{"Attempting to move frozen funds"}},
|
||||
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
changeBalances(A1, A2, ac, -900, -1100);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttPAYMENT, [](STObject& tx) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
A1FrozenByIssuer);
|
||||
|
||||
// test: imitating deep frozen A1 making a payment to A2.
|
||||
doInvariantCheck(
|
||||
{{"Attempting to move frozen funds"}},
|
||||
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
changeBalances(A1, A2, ac, -900, -1100);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttPAYMENT, [](STObject& tx) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
A1DeepFrozenByIssuer);
|
||||
|
||||
// test: imitating A2 making a payment to deep frozen A1.
|
||||
doInvariantCheck(
|
||||
{{"Attempting to move frozen funds"}},
|
||||
[&](Account const& A1, Account const& A2, ApplyContext& ac) {
|
||||
changeBalances(A1, A2, ac, -1100, -900);
|
||||
return true;
|
||||
},
|
||||
XRPAmount{},
|
||||
STTx{ttPAYMENT, [](STObject& tx) {}},
|
||||
{tecINVARIANT_FAILED, tefINVARIANT_FAILED},
|
||||
A1DeepFrozenByIssuer);
|
||||
}
|
||||
|
||||
void
|
||||
testXRPBalanceCheck()
|
||||
{
|
||||
@@ -1061,6 +1238,8 @@ public:
|
||||
testAccountRootsDeletedClean();
|
||||
testTypesMatch();
|
||||
testNoXRPTrustLine();
|
||||
testNoDeepFreezeTrustLinesWithoutFreeze();
|
||||
testTransfersNotFrozen();
|
||||
testXRPBalanceCheck();
|
||||
testTransactionFeeCheck();
|
||||
testNoBadOffers();
|
||||
|
||||
@@ -167,7 +167,11 @@ public:
|
||||
env.close();
|
||||
|
||||
// Set flags on gw2 trust lines so we can look for them.
|
||||
env(trust(alice, gw2Currency(0), gw2, tfSetNoRipple | tfSetFreeze));
|
||||
env(trust(
|
||||
alice,
|
||||
gw2Currency(0),
|
||||
gw2,
|
||||
tfSetNoRipple | tfSetFreeze | tfSetDeepFreeze));
|
||||
}
|
||||
env.close();
|
||||
LedgerInfo const ledger58Info = env.closed()->info();
|
||||
@@ -344,6 +348,7 @@ public:
|
||||
gw2.human() + R"("})");
|
||||
auto const& line = lines[jss::result][jss::lines][0u];
|
||||
BEAST_EXPECT(line[jss::freeze].asBool() == true);
|
||||
BEAST_EXPECT(line[jss::deep_freeze].asBool() == true);
|
||||
BEAST_EXPECT(line[jss::no_ripple].asBool() == true);
|
||||
BEAST_EXPECT(line[jss::peer_authorized].asBool() == true);
|
||||
}
|
||||
@@ -359,6 +364,7 @@ public:
|
||||
alice.human() + R"("})");
|
||||
auto const& lineA = linesA[jss::result][jss::lines][0u];
|
||||
BEAST_EXPECT(lineA[jss::freeze_peer].asBool() == true);
|
||||
BEAST_EXPECT(lineA[jss::deep_freeze_peer].asBool() == true);
|
||||
BEAST_EXPECT(lineA[jss::no_ripple_peer].asBool() == true);
|
||||
BEAST_EXPECT(lineA[jss::authorized].asBool() == true);
|
||||
|
||||
@@ -981,7 +987,11 @@ public:
|
||||
env.close();
|
||||
|
||||
// Set flags on gw2 trust lines so we can look for them.
|
||||
env(trust(alice, gw2Currency(0), gw2, tfSetNoRipple | tfSetFreeze));
|
||||
env(trust(
|
||||
alice,
|
||||
gw2Currency(0),
|
||||
gw2,
|
||||
tfSetNoRipple | tfSetFreeze | tfSetDeepFreeze));
|
||||
}
|
||||
env.close();
|
||||
LedgerInfo const ledger58Info = env.closed()->info();
|
||||
@@ -1311,6 +1321,7 @@ public:
|
||||
gw2.human() + R"("}})");
|
||||
auto const& line = lines[jss::result][jss::lines][0u];
|
||||
BEAST_EXPECT(line[jss::freeze].asBool() == true);
|
||||
BEAST_EXPECT(line[jss::deep_freeze].asBool() == true);
|
||||
BEAST_EXPECT(line[jss::no_ripple].asBool() == true);
|
||||
BEAST_EXPECT(line[jss::peer_authorized].asBool() == true);
|
||||
BEAST_EXPECT(
|
||||
@@ -1338,6 +1349,7 @@ public:
|
||||
alice.human() + R"("}})");
|
||||
auto const& lineA = linesA[jss::result][jss::lines][0u];
|
||||
BEAST_EXPECT(lineA[jss::freeze_peer].asBool() == true);
|
||||
BEAST_EXPECT(lineA[jss::deep_freeze_peer].asBool() == true);
|
||||
BEAST_EXPECT(lineA[jss::no_ripple_peer].asBool() == true);
|
||||
BEAST_EXPECT(lineA[jss::authorized].asBool() == true);
|
||||
BEAST_EXPECT(
|
||||
|
||||
@@ -139,6 +139,13 @@ public:
|
||||
return mFlags & (mViewLowest ? lsfLowFreeze : lsfHighFreeze);
|
||||
}
|
||||
|
||||
/** Have we set the deep freeze flag on our peer */
|
||||
bool
|
||||
getDeepFreeze() const
|
||||
{
|
||||
return mFlags & (mViewLowest ? lsfLowDeepFreeze : lsfHighDeepFreeze);
|
||||
}
|
||||
|
||||
/** Has the peer set the freeze flag on us */
|
||||
bool
|
||||
getFreezePeer() const
|
||||
@@ -146,6 +153,13 @@ public:
|
||||
return mFlags & (!mViewLowest ? lsfLowFreeze : lsfHighFreeze);
|
||||
}
|
||||
|
||||
/** Has the peer set the deep freeze flag on us */
|
||||
bool
|
||||
getDeepFreezePeer() const
|
||||
{
|
||||
return mFlags & (!mViewLowest ? lsfLowDeepFreeze : lsfHighDeepFreeze);
|
||||
}
|
||||
|
||||
STAmount const&
|
||||
getBalance() const
|
||||
{
|
||||
|
||||
@@ -52,6 +52,12 @@ checkFreeze(
|
||||
{
|
||||
return terNO_LINE;
|
||||
}
|
||||
// Unlike normal freeze, a deep frozen trust line acts the same
|
||||
// regardless of which side froze it
|
||||
if (sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze))
|
||||
{
|
||||
return terNO_LINE;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
|
||||
@@ -392,6 +392,7 @@ CashCheck::doApply()
|
||||
false, // authorize account
|
||||
(sleDst->getFlags() & lsfDefaultRipple) == 0,
|
||||
false, // freeze trust line
|
||||
false, // deep freeze trust line
|
||||
initialBalance, // zero initial balance
|
||||
Issue(currency, account_), // limit of zero
|
||||
0, // quality in
|
||||
|
||||
@@ -259,6 +259,32 @@ CreateOffer::checkAcceptAsset(
|
||||
}
|
||||
}
|
||||
|
||||
// An account can not create a trustline to itself, so no line can exist
|
||||
// to be frozen. Additionally, an issuer can always accept its own
|
||||
// issuance.
|
||||
if (issue.account == id)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
auto const trustLine =
|
||||
view.read(keylet::line(id, issue.account, issue.currency));
|
||||
|
||||
if (!trustLine)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// There's no difference which side enacted deep freeze, accepting
|
||||
// tokens shouldn't be possible.
|
||||
bool const deepFrozen =
|
||||
(*trustLine)[sfFlags] & (lsfLowDeepFreeze | lsfHighDeepFreeze);
|
||||
|
||||
if (deepFrozen)
|
||||
{
|
||||
return tecFROZEN;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -556,6 +556,322 @@ NoXRPTrustLines::finalize(
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
NoDeepFreezeTrustLinesWithoutFreeze::visitEntry(
|
||||
bool,
|
||||
std::shared_ptr<SLE const> const&,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
if (after && after->getType() == ltRIPPLE_STATE)
|
||||
{
|
||||
std::uint32_t const uFlags = after->getFieldU32(sfFlags);
|
||||
bool const lowFreeze = uFlags & lsfLowFreeze;
|
||||
bool const lowDeepFreeze = uFlags & lsfLowDeepFreeze;
|
||||
|
||||
bool const highFreeze = uFlags & lsfHighFreeze;
|
||||
bool const highDeepFreeze = uFlags & lsfHighDeepFreeze;
|
||||
|
||||
deepFreezeWithoutFreeze_ =
|
||||
(lowDeepFreeze && !lowFreeze) || (highDeepFreeze && !highFreeze);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
NoDeepFreezeTrustLinesWithoutFreeze::finalize(
|
||||
STTx const&,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const&,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
if (!deepFreezeWithoutFreeze_)
|
||||
return true;
|
||||
|
||||
JLOG(j.fatal()) << "Invariant failed: a trust line with deep freeze flag "
|
||||
"without normal freeze was created";
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
TransfersNotFrozen::visitEntry(
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
/*
|
||||
* A trust line freeze state alone doesn't determine if a transfer is
|
||||
* frozen. The transfer must be examined "end-to-end" because both sides of
|
||||
* the transfer may have different freeze states and freeze impact depends
|
||||
* on the transfer direction. This is why first we need to track the
|
||||
* transfers using IssuerChanges senders/receivers.
|
||||
*
|
||||
* Only in validateIssuerChanges, after we collected all changes can we
|
||||
* determine if the transfer is valid.
|
||||
*/
|
||||
if (!isValidEntry(before, after))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto const balanceChange = calculateBalanceChange(before, after, isDelete);
|
||||
if (balanceChange.signum() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
recordBalanceChanges(after, balanceChange);
|
||||
}
|
||||
|
||||
bool
|
||||
TransfersNotFrozen::finalize(
|
||||
STTx const& tx,
|
||||
TER const ter,
|
||||
XRPAmount const fee,
|
||||
ReadView const& view,
|
||||
beast::Journal const& j)
|
||||
{
|
||||
/*
|
||||
* We check this invariant regardless of deep freeze amendment status,
|
||||
* allowing for detection and logging of potential issues even when the
|
||||
* amendment is disabled.
|
||||
*
|
||||
* If an exploit that allows moving frozen assets is discovered,
|
||||
* we can alert operators who monitor fatal messages and trigger assert in
|
||||
* debug builds for an early warning.
|
||||
*
|
||||
* In an unlikely event that an exploit is found, this early detection
|
||||
* enables encouraging the UNL to expedite deep freeze amendment activation
|
||||
* or deploy hotfixes via new amendments. In case of a new amendment, we'd
|
||||
* only have to change this line setting 'enforce' variable.
|
||||
* enforce = view.rules().enabled(featureDeepFreeze) ||
|
||||
* view.rules().enabled(fixFreezeExploit);
|
||||
*/
|
||||
[[maybe_unused]] bool const enforce =
|
||||
view.rules().enabled(featureDeepFreeze);
|
||||
|
||||
for (auto const& [issue, changes] : balanceChanges_)
|
||||
{
|
||||
auto const issuerSle = findIssuer(issue.account, view);
|
||||
// It should be impossible for the issuer to not be found, but check
|
||||
// just in case so rippled doesn't crash in release.
|
||||
if (!issuerSle)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
enforce,
|
||||
"ripple::TransfersNotFrozen::finalize : enforce "
|
||||
"invariant.");
|
||||
if (enforce)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!validateIssuerChanges(issuerSle, changes, tx, j, enforce))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TransfersNotFrozen::isValidEntry(
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after)
|
||||
{
|
||||
// `after` can never be null, even if the trust line is deleted.
|
||||
XRPL_ASSERT(
|
||||
after, "ripple::TransfersNotFrozen::isValidEntry : valid after.");
|
||||
if (!after)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (after->getType() == ltACCOUNT_ROOT)
|
||||
{
|
||||
possibleIssuers_.emplace(after->at(sfAccount), after);
|
||||
return false;
|
||||
}
|
||||
|
||||
/* While LedgerEntryTypesMatch invariant also checks types, all invariants
|
||||
* are processed regardless of previous failures.
|
||||
*
|
||||
* This type check is still necessary here because it prevents potential
|
||||
* issues in subsequent processing.
|
||||
*/
|
||||
return after->getType() == ltRIPPLE_STATE &&
|
||||
(!before || before->getType() == ltRIPPLE_STATE);
|
||||
}
|
||||
|
||||
STAmount
|
||||
TransfersNotFrozen::calculateBalanceChange(
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after,
|
||||
bool isDelete)
|
||||
{
|
||||
auto const getBalance = [](auto const& line, auto const& other, bool zero) {
|
||||
STAmount amt =
|
||||
line ? line->at(sfBalance) : other->at(sfBalance).zeroed();
|
||||
return zero ? amt.zeroed() : amt;
|
||||
};
|
||||
|
||||
/* Trust lines can be created dynamically by other transactions such as
|
||||
* Payment and OfferCreate that cross offers. Such trust line won't be
|
||||
* created frozen, but the sender might be, so the starting balance must be
|
||||
* treated as zero.
|
||||
*/
|
||||
auto const balanceBefore = getBalance(before, after, false);
|
||||
|
||||
/* Same as above, trust lines can be dynamically deleted, and for frozen
|
||||
* trust lines, payments not involving the issuer must be blocked. This is
|
||||
* achieved by treating the final balance as zero when isDelete=true to
|
||||
* ensure frozen line restrictions are enforced even during deletion.
|
||||
*/
|
||||
auto const balanceAfter = getBalance(after, before, isDelete);
|
||||
|
||||
return balanceAfter - balanceBefore;
|
||||
}
|
||||
|
||||
void
|
||||
TransfersNotFrozen::recordBalance(Issue const& issue, BalanceChange change)
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
change.balanceChangeSign,
|
||||
"ripple::TransfersNotFrozen::recordBalance : valid trustline "
|
||||
"balance sign.");
|
||||
auto& changes = balanceChanges_[issue];
|
||||
if (change.balanceChangeSign < 0)
|
||||
changes.senders.emplace_back(std::move(change));
|
||||
else
|
||||
changes.receivers.emplace_back(std::move(change));
|
||||
}
|
||||
|
||||
void
|
||||
TransfersNotFrozen::recordBalanceChanges(
|
||||
std::shared_ptr<SLE const> const& after,
|
||||
STAmount const& balanceChange)
|
||||
{
|
||||
auto const balanceChangeSign = balanceChange.signum();
|
||||
auto const currency = after->at(sfBalance).getCurrency();
|
||||
|
||||
// Change from low account's perspective, which is trust line default
|
||||
recordBalance(
|
||||
{currency, after->at(sfHighLimit).getIssuer()},
|
||||
{after, balanceChangeSign});
|
||||
|
||||
// Change from high account's perspective, which reverses the sign.
|
||||
recordBalance(
|
||||
{currency, after->at(sfLowLimit).getIssuer()},
|
||||
{after, -balanceChangeSign});
|
||||
}
|
||||
|
||||
std::shared_ptr<SLE const>
|
||||
TransfersNotFrozen::findIssuer(AccountID const& issuerID, ReadView const& view)
|
||||
{
|
||||
if (auto it = possibleIssuers_.find(issuerID); it != possibleIssuers_.end())
|
||||
{
|
||||
return it->second;
|
||||
}
|
||||
|
||||
return view.read(keylet::account(issuerID));
|
||||
}
|
||||
|
||||
bool
|
||||
TransfersNotFrozen::validateIssuerChanges(
|
||||
std::shared_ptr<SLE const> const& issuer,
|
||||
IssuerChanges const& changes,
|
||||
STTx const& tx,
|
||||
beast::Journal const& j,
|
||||
bool enforce)
|
||||
{
|
||||
if (!issuer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool const globalFreeze = issuer->isFlag(lsfGlobalFreeze);
|
||||
if (changes.receivers.empty() || changes.senders.empty())
|
||||
{
|
||||
/* If there are no receivers, then the holder(s) are returning
|
||||
* their tokens to the issuer. Likewise, if there are no
|
||||
* senders, then the issuer is issuing tokens to the holder(s).
|
||||
* This is allowed regardless of the issuer's freeze flags. (The
|
||||
* holder may have contradicting freeze flags, but that will be
|
||||
* checked when the holder is treated as issuer.)
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto const& actors : {changes.senders, changes.receivers})
|
||||
{
|
||||
for (auto const& change : actors)
|
||||
{
|
||||
bool const high = change.line->at(sfLowLimit).getIssuer() ==
|
||||
issuer->at(sfAccount);
|
||||
|
||||
if (!validateFrozenState(
|
||||
change, high, tx, j, enforce, globalFreeze))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TransfersNotFrozen::validateFrozenState(
|
||||
BalanceChange const& change,
|
||||
bool high,
|
||||
STTx const& tx,
|
||||
beast::Journal const& j,
|
||||
bool enforce,
|
||||
bool globalFreeze)
|
||||
{
|
||||
bool const freeze = change.balanceChangeSign < 0 &&
|
||||
change.line->isFlag(high ? lsfLowFreeze : lsfHighFreeze);
|
||||
bool const deepFreeze =
|
||||
change.line->isFlag(high ? lsfLowDeepFreeze : lsfHighDeepFreeze);
|
||||
bool const frozen = globalFreeze || deepFreeze || freeze;
|
||||
|
||||
bool const isAMMLine = change.line->isFlag(lsfAMMNode);
|
||||
|
||||
if (!frozen)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// AMMClawbacks are allowed to override some freeze rules
|
||||
if ((!isAMMLine || globalFreeze) && tx.getTxnType() == ttAMM_CLAWBACK)
|
||||
{
|
||||
JLOG(j.debug()) << "Invariant check allowing funds to be moved "
|
||||
<< (change.balanceChangeSign > 0 ? "to" : "from")
|
||||
<< " a frozen trustline for AMMClawback "
|
||||
<< tx.getTransactionID();
|
||||
return true;
|
||||
}
|
||||
|
||||
JLOG(j.fatal()) << "Invariant failed: Attempting to move frozen funds for "
|
||||
<< tx.getTransactionID();
|
||||
XRPL_ASSERT(
|
||||
enforce,
|
||||
"ripple::TransfersNotFrozen::validateFrozenState : enforce "
|
||||
"invariant.");
|
||||
|
||||
if (enforce)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
ValidNewAccountRoot::visitEntry(
|
||||
bool,
|
||||
|
||||
@@ -270,6 +270,114 @@ public:
|
||||
beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: Trust lines with deep freeze flag are not allowed if normal
|
||||
* freeze flag is not set.
|
||||
*
|
||||
* We iterate all the trust lines created by this transaction and ensure
|
||||
* that they don't have deep freeze flag set without normal freeze flag set.
|
||||
*/
|
||||
class NoDeepFreezeTrustLinesWithoutFreeze
|
||||
{
|
||||
bool deepFreezeWithoutFreeze_ = false;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(
|
||||
bool,
|
||||
std::shared_ptr<SLE const> const&,
|
||||
std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(
|
||||
STTx const&,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const&,
|
||||
beast::Journal const&);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: frozen trust line balance change is not allowed.
|
||||
*
|
||||
* We iterate all affected trust lines and ensure that they don't have
|
||||
* unexpected change of balance if they're frozen.
|
||||
*/
|
||||
class TransfersNotFrozen
|
||||
{
|
||||
struct BalanceChange
|
||||
{
|
||||
std::shared_ptr<SLE const> const line;
|
||||
int const balanceChangeSign;
|
||||
};
|
||||
|
||||
struct IssuerChanges
|
||||
{
|
||||
std::vector<BalanceChange> senders;
|
||||
std::vector<BalanceChange> receivers;
|
||||
};
|
||||
|
||||
using ByIssuer = std::map<Issue, IssuerChanges>;
|
||||
ByIssuer balanceChanges_;
|
||||
|
||||
std::map<AccountID, std::shared_ptr<SLE const> const> possibleIssuers_;
|
||||
|
||||
public:
|
||||
void
|
||||
visitEntry(
|
||||
bool,
|
||||
std::shared_ptr<SLE const> const&,
|
||||
std::shared_ptr<SLE const> const&);
|
||||
|
||||
bool
|
||||
finalize(
|
||||
STTx const&,
|
||||
TER const,
|
||||
XRPAmount const,
|
||||
ReadView const&,
|
||||
beast::Journal const&);
|
||||
|
||||
private:
|
||||
bool
|
||||
isValidEntry(
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after);
|
||||
|
||||
STAmount
|
||||
calculateBalanceChange(
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
std::shared_ptr<SLE const> const& after,
|
||||
bool isDelete);
|
||||
|
||||
void
|
||||
recordBalance(Issue const& issue, BalanceChange change);
|
||||
|
||||
void
|
||||
recordBalanceChanges(
|
||||
std::shared_ptr<SLE const> const& after,
|
||||
STAmount const& balanceChange);
|
||||
|
||||
std::shared_ptr<SLE const>
|
||||
findIssuer(AccountID const& issuerID, ReadView const& view);
|
||||
|
||||
bool
|
||||
validateIssuerChanges(
|
||||
std::shared_ptr<SLE const> const& issuer,
|
||||
IssuerChanges const& changes,
|
||||
STTx const& tx,
|
||||
beast::Journal const& j,
|
||||
bool enforce);
|
||||
|
||||
bool
|
||||
validateFrozenState(
|
||||
BalanceChange const& change,
|
||||
bool high,
|
||||
STTx const& tx,
|
||||
beast::Journal const& j,
|
||||
bool enforce,
|
||||
bool globalFreeze);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Invariant: offers should be for non-negative amounts and must not
|
||||
* be XRP to XRP.
|
||||
@@ -518,6 +626,8 @@ using InvariantChecks = std::tuple<
|
||||
XRPBalanceChecks,
|
||||
XRPNotCreated,
|
||||
NoXRPTrustLines,
|
||||
NoDeepFreezeTrustLinesWithoutFreeze,
|
||||
TransfersNotFrozen,
|
||||
NoBadOffers,
|
||||
NoZeroEscrow,
|
||||
ValidNewAccountRoot,
|
||||
|
||||
@@ -268,6 +268,20 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx)
|
||||
ctx.j) < needed)
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
|
||||
// Make sure that we are allowed to hold what the taker will pay us.
|
||||
// This is a similar approach taken by usual offers.
|
||||
if (!needed.native())
|
||||
{
|
||||
auto const result = checkAcceptAsset(
|
||||
ctx.view,
|
||||
ctx.flags,
|
||||
(*so)[sfOwner],
|
||||
ctx.j,
|
||||
needed.asset().get<Issue>());
|
||||
if (result != tesSUCCESS)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Fix a bug where the transfer of an NFToken with a transfer fee could
|
||||
@@ -510,4 +524,62 @@ NFTokenAcceptOffer::doApply()
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
TER
|
||||
NFTokenAcceptOffer::checkAcceptAsset(
|
||||
ReadView const& view,
|
||||
ApplyFlags const flags,
|
||||
AccountID const id,
|
||||
beast::Journal const j,
|
||||
Issue const& issue)
|
||||
{
|
||||
// Only valid for custom currencies
|
||||
|
||||
if (!view.rules().enabled(featureDeepFreeze))
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
XRPL_ASSERT(
|
||||
!isXRP(issue.currency),
|
||||
"NFTokenAcceptOffer::checkAcceptAsset : valid to check.");
|
||||
auto const issuerAccount = view.read(keylet::account(issue.account));
|
||||
|
||||
if (!issuerAccount)
|
||||
{
|
||||
JLOG(j.debug())
|
||||
<< "delay: can't receive IOUs from non-existent issuer: "
|
||||
<< to_string(issue.account);
|
||||
|
||||
return tecNO_ISSUER;
|
||||
}
|
||||
|
||||
// An account can not create a trustline to itself, so no line can exist
|
||||
// to be frozen. Additionally, an issuer can always accept its own
|
||||
// issuance.
|
||||
if (issue.account == id)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
auto const trustLine =
|
||||
view.read(keylet::line(id, issue.account, issue.currency));
|
||||
|
||||
if (!trustLine)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// There's no difference which side enacted deep freeze, accepting
|
||||
// tokens shouldn't be possible.
|
||||
bool const deepFrozen =
|
||||
(*trustLine)[sfFlags] & (lsfLowDeepFreeze | lsfHighDeepFreeze);
|
||||
|
||||
if (deepFrozen)
|
||||
{
|
||||
return tecFROZEN;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -44,6 +44,14 @@ private:
|
||||
AccountID const& seller,
|
||||
uint256 const& nfTokenID);
|
||||
|
||||
static TER
|
||||
checkAcceptAsset(
|
||||
ReadView const& view,
|
||||
ApplyFlags const flags,
|
||||
AccountID const id,
|
||||
beast::Journal const j,
|
||||
Issue const& issue);
|
||||
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
|
||||
@@ -273,6 +273,20 @@ TOfferStreamBase<TIn, TOut>::step()
|
||||
continue;
|
||||
}
|
||||
|
||||
bool const deepFrozen = isDeepFrozen(
|
||||
view_,
|
||||
offer_.owner(),
|
||||
offer_.issueIn().currency,
|
||||
offer_.issueIn().account);
|
||||
if (deepFrozen)
|
||||
{
|
||||
JLOG(j_.trace())
|
||||
<< "Removing deep frozen unfunded offer " << entry->key();
|
||||
permRmOffer(entry->key());
|
||||
offer_ = TOffer<TIn, TOut>{};
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate owner funds
|
||||
ownerFunds_ = accountFundsHelper(
|
||||
view_,
|
||||
|
||||
@@ -26,6 +26,42 @@
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/st.h>
|
||||
|
||||
namespace {
|
||||
|
||||
uint32_t
|
||||
computeFreezeFlags(
|
||||
uint32_t uFlags,
|
||||
bool bHigh,
|
||||
bool bNoFreeze,
|
||||
bool bSetFreeze,
|
||||
bool bClearFreeze,
|
||||
bool bSetDeepFreeze,
|
||||
bool bClearDeepFreeze)
|
||||
{
|
||||
if (bSetFreeze && !bClearFreeze && !bNoFreeze)
|
||||
{
|
||||
uFlags |= (bHigh ? ripple::lsfHighFreeze : ripple::lsfLowFreeze);
|
||||
}
|
||||
else if (bClearFreeze && !bSetFreeze)
|
||||
{
|
||||
uFlags &= ~(bHigh ? ripple::lsfHighFreeze : ripple::lsfLowFreeze);
|
||||
}
|
||||
if (bSetDeepFreeze && !bClearDeepFreeze && !bNoFreeze)
|
||||
{
|
||||
uFlags |=
|
||||
(bHigh ? ripple::lsfHighDeepFreeze : ripple::lsfLowDeepFreeze);
|
||||
}
|
||||
else if (bClearDeepFreeze && !bSetDeepFreeze)
|
||||
{
|
||||
uFlags &=
|
||||
~(bHigh ? ripple::lsfHighDeepFreeze : ripple::lsfLowDeepFreeze);
|
||||
}
|
||||
|
||||
return uFlags;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace ripple {
|
||||
|
||||
NotTEC
|
||||
@@ -45,6 +81,16 @@ SetTrust::preflight(PreflightContext const& ctx)
|
||||
return temINVALID_FLAG;
|
||||
}
|
||||
|
||||
if (!ctx.rules.enabled(featureDeepFreeze))
|
||||
{
|
||||
// Even though the deep freeze flags are included in the
|
||||
// `tfTrustSetMask`, they are not valid if the amendment is not enabled.
|
||||
if (uTxFlags & (tfSetDeepFreeze | tfClearDeepFreeze))
|
||||
{
|
||||
return temINVALID_FLAG;
|
||||
}
|
||||
}
|
||||
|
||||
STAmount const saLimitAmount(tx.getFieldAmount(sfLimitAmount));
|
||||
|
||||
if (!isLegalNet(saLimitAmount))
|
||||
@@ -182,6 +228,58 @@ SetTrust::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// Checking all freeze/deep freeze flag invariants.
|
||||
if (ctx.view.rules().enabled(featureDeepFreeze))
|
||||
{
|
||||
bool const bNoFreeze = sle->isFlag(lsfNoFreeze);
|
||||
bool const bSetFreeze = (uTxFlags & tfSetFreeze);
|
||||
bool const bSetDeepFreeze = (uTxFlags & tfSetDeepFreeze);
|
||||
|
||||
if (bNoFreeze && (bSetFreeze || bSetDeepFreeze))
|
||||
{
|
||||
// Cannot freeze the trust line if NoFreeze is set
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
bool const bClearFreeze = (uTxFlags & tfClearFreeze);
|
||||
bool const bClearDeepFreeze = (uTxFlags & tfClearDeepFreeze);
|
||||
if ((bSetFreeze || bSetDeepFreeze) &&
|
||||
(bClearFreeze || bClearDeepFreeze))
|
||||
{
|
||||
// Freezing and unfreezing in the same transaction should be
|
||||
// illegal
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
bool const bHigh = id > uDstAccountID;
|
||||
// Fetching current state of trust line
|
||||
auto const sleRippleState =
|
||||
ctx.view.read(keylet::line(id, uDstAccountID, currency));
|
||||
std::uint32_t uFlags =
|
||||
sleRippleState ? sleRippleState->getFieldU32(sfFlags) : 0u;
|
||||
// Computing expected trust line state
|
||||
uFlags = computeFreezeFlags(
|
||||
uFlags,
|
||||
bHigh,
|
||||
bNoFreeze,
|
||||
bSetFreeze,
|
||||
bClearFreeze,
|
||||
bSetDeepFreeze,
|
||||
bClearDeepFreeze);
|
||||
|
||||
auto const frozen = uFlags & (bHigh ? lsfHighFreeze : lsfLowFreeze);
|
||||
auto const deepFrozen =
|
||||
uFlags & (bHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze);
|
||||
|
||||
// Trying to set deep freeze on not already frozen trust line must
|
||||
// fail. This also checks that clearing normal freeze while deep
|
||||
// frozen must not work
|
||||
if (deepFrozen && !frozen)
|
||||
{
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -197,7 +295,7 @@ SetTrust::doApply()
|
||||
Currency const currency(saLimitAmount.getCurrency());
|
||||
AccountID uDstAccountID(saLimitAmount.getIssuer());
|
||||
|
||||
// true, iff current is high account.
|
||||
// true, if current is high account.
|
||||
bool const bHigh = account_ > uDstAccountID;
|
||||
|
||||
auto const sle = view().peek(keylet::account(account_));
|
||||
@@ -242,13 +340,15 @@ SetTrust::doApply()
|
||||
bool const bClearNoRipple = (uTxFlags & tfClearNoRipple);
|
||||
bool const bSetFreeze = (uTxFlags & tfSetFreeze);
|
||||
bool const bClearFreeze = (uTxFlags & tfClearFreeze);
|
||||
bool const bSetDeepFreeze = (uTxFlags & tfSetDeepFreeze);
|
||||
bool const bClearDeepFreeze = (uTxFlags & tfClearDeepFreeze);
|
||||
|
||||
auto viewJ = ctx_.app.journal("View");
|
||||
|
||||
// Trust lines to self are impossible but because of the old bug there are
|
||||
// two on 19-02-2022. This code was here to allow those trust lines to be
|
||||
// deleted. The fixTrustLinesToSelf fix amendment will remove them when it
|
||||
// enables so this code will no longer be needed.
|
||||
// Trust lines to self are impossible but because of the old bug there
|
||||
// are two on 19-02-2022. This code was here to allow those trust lines
|
||||
// to be deleted. The fixTrustLinesToSelf fix amendment will remove them
|
||||
// when it enables so this code will no longer be needed.
|
||||
if (!view().rules().enabled(fixTrustLinesToSelf) &&
|
||||
account_ == uDstAccountID)
|
||||
{
|
||||
@@ -408,14 +508,16 @@ SetTrust::doApply()
|
||||
uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple);
|
||||
}
|
||||
|
||||
if (bSetFreeze && !bClearFreeze && !sle->isFlag(lsfNoFreeze))
|
||||
{
|
||||
uFlagsOut |= (bHigh ? lsfHighFreeze : lsfLowFreeze);
|
||||
}
|
||||
else if (bClearFreeze && !bSetFreeze)
|
||||
{
|
||||
uFlagsOut &= ~(bHigh ? lsfHighFreeze : lsfLowFreeze);
|
||||
}
|
||||
// Have to use lsfNoFreeze to maintain pre-deep freeze behavior
|
||||
bool const bNoFreeze = sle->isFlag(lsfNoFreeze);
|
||||
uFlagsOut = computeFreezeFlags(
|
||||
uFlagsOut,
|
||||
bHigh,
|
||||
bNoFreeze,
|
||||
bSetFreeze,
|
||||
bClearFreeze,
|
||||
bSetDeepFreeze,
|
||||
bClearDeepFreeze);
|
||||
|
||||
if (QUALITY_ONE == uLowQualityOut)
|
||||
uLowQualityOut = 0;
|
||||
@@ -498,8 +600,8 @@ SetTrust::doApply()
|
||||
// Reserve is not scaled by load.
|
||||
else if (bReserveIncrease && mPriorBalance < reserveCreate)
|
||||
{
|
||||
JLOG(j_.trace())
|
||||
<< "Delay transaction: Insufficent reserve to add trust line.";
|
||||
JLOG(j_.trace()) << "Delay transaction: Insufficent reserve to "
|
||||
"add trust line.";
|
||||
|
||||
// Another transaction could provide XRP to the account and then
|
||||
// this transaction would succeed.
|
||||
@@ -515,17 +617,18 @@ SetTrust::doApply()
|
||||
// Line does not exist.
|
||||
else if (
|
||||
!saLimitAmount && // Setting default limit.
|
||||
(!bQualityIn || !uQualityIn) && // Not setting quality in or setting
|
||||
// default quality in.
|
||||
(!bQualityOut || !uQualityOut) && // Not setting quality out or setting
|
||||
// default quality out.
|
||||
(!bQualityIn || !uQualityIn) && // Not setting quality in or
|
||||
// setting default quality in.
|
||||
(!bQualityOut || !uQualityOut) && // Not setting quality out or
|
||||
// setting default quality out.
|
||||
(!bSetAuth))
|
||||
{
|
||||
JLOG(j_.trace())
|
||||
<< "Redundant: Setting non-existent ripple line to defaults.";
|
||||
return tecNO_LINE_REDUNDANT;
|
||||
}
|
||||
else if (mPriorBalance < reserveCreate) // Reserve is not scaled by load.
|
||||
else if (mPriorBalance < reserveCreate) // Reserve is not scaled by
|
||||
// load.
|
||||
{
|
||||
JLOG(j_.trace()) << "Delay transaction: Line does not exist. "
|
||||
"Insufficent reserve to create line.";
|
||||
@@ -555,6 +658,7 @@ SetTrust::doApply()
|
||||
bSetAuth,
|
||||
bSetNoRipple && !bClearNoRipple,
|
||||
bSetFreeze && !bClearFreeze,
|
||||
bSetDeepFreeze,
|
||||
saBalance,
|
||||
saLimitAllow, // Limit for who is being charged.
|
||||
uQualityIn,
|
||||
|
||||
@@ -153,6 +153,13 @@ isFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
|
||||
asset.value());
|
||||
}
|
||||
|
||||
[[nodiscard]] bool
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer);
|
||||
|
||||
// Returns the amount an account can spend without going into debt.
|
||||
//
|
||||
// <-- saAmount: amount of currency held by account. May be negative.
|
||||
@@ -438,6 +445,7 @@ trustCreate(
|
||||
const bool bAuth, // --> authorize account.
|
||||
const bool bNoRipple, // --> others cannot ripple through
|
||||
const bool bFreeze, // --> funds cannot leave
|
||||
bool bDeepFreeze, // --> can neither receive nor send funds
|
||||
STAmount const& saBalance, // --> balance of account being set.
|
||||
// Issuer should be noAccount()
|
||||
STAmount const& saLimit, // --> limit for account being set.
|
||||
|
||||
@@ -267,6 +267,32 @@ isFrozen(
|
||||
isIndividualFrozen(view, account, mptIssue);
|
||||
}
|
||||
|
||||
bool
|
||||
isDeepFrozen(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Currency const& currency,
|
||||
AccountID const& issuer)
|
||||
{
|
||||
if (isXRP(currency))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (issuer == account)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
auto const sle = view.read(keylet::line(account, issuer, currency));
|
||||
if (!sle)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return sle->isFlag(lsfHighDeepFreeze) || sle->isFlag(lsfLowDeepFreeze);
|
||||
}
|
||||
|
||||
STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
@@ -284,17 +310,25 @@ accountHolds(
|
||||
|
||||
// IOU: Return balance on trust line modulo freeze
|
||||
auto const sle = view.read(keylet::line(account, issuer, currency));
|
||||
if (!sle)
|
||||
{
|
||||
amount.clear(Issue{currency, issuer});
|
||||
}
|
||||
else if (
|
||||
(zeroIfFrozen == fhZERO_IF_FROZEN) &&
|
||||
isFrozen(view, account, currency, issuer))
|
||||
{
|
||||
amount.clear(Issue{currency, issuer});
|
||||
}
|
||||
else
|
||||
auto const allowBalance = [&]() {
|
||||
if (!sle)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (zeroIfFrozen == fhZERO_IF_FROZEN)
|
||||
{
|
||||
if (isFrozen(view, account, currency, issuer) ||
|
||||
isDeepFrozen(view, account, currency, issuer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}();
|
||||
|
||||
if (allowBalance)
|
||||
{
|
||||
amount = sle->getFieldAmount(sfBalance);
|
||||
if (account > issuer)
|
||||
@@ -304,6 +338,11 @@ accountHolds(
|
||||
}
|
||||
amount.setIssuer(issuer);
|
||||
}
|
||||
else
|
||||
{
|
||||
amount.clear(Issue{currency, issuer});
|
||||
}
|
||||
|
||||
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(account)
|
||||
<< " amount=" << amount.getFullText();
|
||||
|
||||
@@ -863,6 +902,7 @@ trustCreate(
|
||||
const bool bAuth, // --> authorize account.
|
||||
const bool bNoRipple, // --> others cannot ripple through
|
||||
const bool bFreeze, // --> funds cannot leave
|
||||
bool bDeepFreeze, // --> can neither receive nor send funds
|
||||
STAmount const& saBalance, // --> balance of account being set.
|
||||
// Issuer should be noAccount()
|
||||
STAmount const& saLimit, // --> limit for account being set.
|
||||
@@ -944,7 +984,11 @@ trustCreate(
|
||||
}
|
||||
if (bFreeze)
|
||||
{
|
||||
uFlags |= (!bSetHigh ? lsfLowFreeze : lsfHighFreeze);
|
||||
uFlags |= (bSetHigh ? lsfHighFreeze : lsfLowFreeze);
|
||||
}
|
||||
if (bDeepFreeze)
|
||||
{
|
||||
uFlags |= (bSetHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze);
|
||||
}
|
||||
|
||||
if ((slePeer->getFlags() & lsfDefaultRipple) == 0)
|
||||
@@ -1189,6 +1233,7 @@ rippleCreditIOU(
|
||||
false,
|
||||
noRipple,
|
||||
false,
|
||||
false,
|
||||
saBalance,
|
||||
saReceiverLimit,
|
||||
0,
|
||||
@@ -1688,6 +1733,7 @@ issueIOU(
|
||||
false,
|
||||
noRipple,
|
||||
false,
|
||||
false,
|
||||
final_balance,
|
||||
limit,
|
||||
0,
|
||||
|
||||
@@ -62,6 +62,10 @@ addLine(Json::Value& jsonLines, RPCTrustLine const& line)
|
||||
jPeer[jss::freeze] = true;
|
||||
if (line.getFreezePeer())
|
||||
jPeer[jss::freeze_peer] = true;
|
||||
if (line.getDeepFreeze())
|
||||
jPeer[jss::deep_freeze] = true;
|
||||
if (line.getDeepFreezePeer())
|
||||
jPeer[jss::deep_freeze_peer] = true;
|
||||
}
|
||||
|
||||
// {
|
||||
|
||||
Reference in New Issue
Block a user