mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-26 22:15:52 +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
|
// 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
|
// 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.
|
// 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.
|
/** Amendments that this server supports and the default voting behavior.
|
||||||
Whether they are enabled depends on the Rules defined in the validated
|
Whether they are enabled depends on the Rules defined in the validated
|
||||||
|
|||||||
@@ -162,6 +162,8 @@ enum LedgerSpecificFlags {
|
|||||||
lsfHighNoRipple = 0x00200000,
|
lsfHighNoRipple = 0x00200000,
|
||||||
lsfLowFreeze = 0x00400000, // True, low side has set freeze flag
|
lsfLowFreeze = 0x00400000, // True, low side has set freeze flag
|
||||||
lsfHighFreeze = 0x00800000, // True, high 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
|
lsfAMMNode = 0x01000000, // True, trust line to AMM. Used by client
|
||||||
// apps to identify payments via AMM.
|
// apps to identify payments via AMM.
|
||||||
|
|
||||||
|
|||||||
@@ -114,9 +114,11 @@ constexpr std::uint32_t tfSetNoRipple = 0x00020000;
|
|||||||
constexpr std::uint32_t tfClearNoRipple = 0x00040000;
|
constexpr std::uint32_t tfClearNoRipple = 0x00040000;
|
||||||
constexpr std::uint32_t tfSetFreeze = 0x00100000;
|
constexpr std::uint32_t tfSetFreeze = 0x00100000;
|
||||||
constexpr std::uint32_t tfClearFreeze = 0x00200000;
|
constexpr std::uint32_t tfClearFreeze = 0x00200000;
|
||||||
|
constexpr std::uint32_t tfSetDeepFreeze = 0x00400000;
|
||||||
|
constexpr std::uint32_t tfClearDeepFreeze = 0x00800000;
|
||||||
constexpr std::uint32_t tfTrustSetMask =
|
constexpr std::uint32_t tfTrustSetMask =
|
||||||
~(tfUniversal | tfSetfAuth | tfSetNoRipple | tfClearNoRipple | tfSetFreeze |
|
~(tfUniversal | tfSetfAuth | tfSetNoRipple | tfClearNoRipple | tfSetFreeze |
|
||||||
tfClearFreeze);
|
tfClearFreeze | tfSetDeepFreeze | tfClearDeepFreeze);
|
||||||
|
|
||||||
// EnableAmendment flags:
|
// EnableAmendment flags:
|
||||||
constexpr std::uint32_t tfGotMajority = 0x00010000;
|
constexpr std::uint32_t tfGotMajority = 0x00010000;
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||||
// in include/xrpl/protocol/Feature.h.
|
// in include/xrpl/protocol/Feature.h.
|
||||||
|
|
||||||
|
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo)
|
XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo)
|
||||||
XRPL_FEATURE(DynamicNFT, Supported::yes, VoteBehavior::DefaultNo)
|
XRPL_FEATURE(DynamicNFT, Supported::yes, VoteBehavior::DefaultNo)
|
||||||
XRPL_FEATURE(Credentials, 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_FIX (NFTokenDirV1, Supported::yes, VoteBehavior::Obsolete)
|
||||||
XRPL_FEATURE(NonFungibleTokensV1, Supported::yes, VoteBehavior::Obsolete)
|
XRPL_FEATURE(NonFungibleTokensV1, Supported::yes, VoteBehavior::Obsolete)
|
||||||
XRPL_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete)
|
XRPL_FEATURE(CryptoConditionsSuite, Supported::yes, VoteBehavior::Obsolete)
|
||||||
|
|
||||||
|
|||||||
@@ -284,6 +284,8 @@ JSS(flags); // out: AccountOffers,
|
|||||||
JSS(forward); // in: AccountTx
|
JSS(forward); // in: AccountTx
|
||||||
JSS(freeze); // out: AccountLines
|
JSS(freeze); // out: AccountLines
|
||||||
JSS(freeze_peer); // out: AccountLines
|
JSS(freeze_peer); // out: AccountLines
|
||||||
|
JSS(deep_freeze); // out: AccountLines
|
||||||
|
JSS(deep_freeze_peer); // out: AccountLines
|
||||||
JSS(frozen_balances); // out: GatewayBalances
|
JSS(frozen_balances); // out: GatewayBalances
|
||||||
JSS(full); // in: LedgerClearer, handlers/Ledger
|
JSS(full); // in: LedgerClearer, handlers/Ledger
|
||||||
JSS(full_reply); // out: PathFind
|
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
|
void
|
||||||
testXRPBalanceCheck()
|
testXRPBalanceCheck()
|
||||||
{
|
{
|
||||||
@@ -1061,6 +1238,8 @@ public:
|
|||||||
testAccountRootsDeletedClean();
|
testAccountRootsDeletedClean();
|
||||||
testTypesMatch();
|
testTypesMatch();
|
||||||
testNoXRPTrustLine();
|
testNoXRPTrustLine();
|
||||||
|
testNoDeepFreezeTrustLinesWithoutFreeze();
|
||||||
|
testTransfersNotFrozen();
|
||||||
testXRPBalanceCheck();
|
testXRPBalanceCheck();
|
||||||
testTransactionFeeCheck();
|
testTransactionFeeCheck();
|
||||||
testNoBadOffers();
|
testNoBadOffers();
|
||||||
|
|||||||
@@ -167,7 +167,11 @@ public:
|
|||||||
env.close();
|
env.close();
|
||||||
|
|
||||||
// Set flags on gw2 trust lines so we can look for them.
|
// 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();
|
env.close();
|
||||||
LedgerInfo const ledger58Info = env.closed()->info();
|
LedgerInfo const ledger58Info = env.closed()->info();
|
||||||
@@ -344,6 +348,7 @@ public:
|
|||||||
gw2.human() + R"("})");
|
gw2.human() + R"("})");
|
||||||
auto const& line = lines[jss::result][jss::lines][0u];
|
auto const& line = lines[jss::result][jss::lines][0u];
|
||||||
BEAST_EXPECT(line[jss::freeze].asBool() == true);
|
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::no_ripple].asBool() == true);
|
||||||
BEAST_EXPECT(line[jss::peer_authorized].asBool() == true);
|
BEAST_EXPECT(line[jss::peer_authorized].asBool() == true);
|
||||||
}
|
}
|
||||||
@@ -359,6 +364,7 @@ public:
|
|||||||
alice.human() + R"("})");
|
alice.human() + R"("})");
|
||||||
auto const& lineA = linesA[jss::result][jss::lines][0u];
|
auto const& lineA = linesA[jss::result][jss::lines][0u];
|
||||||
BEAST_EXPECT(lineA[jss::freeze_peer].asBool() == true);
|
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::no_ripple_peer].asBool() == true);
|
||||||
BEAST_EXPECT(lineA[jss::authorized].asBool() == true);
|
BEAST_EXPECT(lineA[jss::authorized].asBool() == true);
|
||||||
|
|
||||||
@@ -981,7 +987,11 @@ public:
|
|||||||
env.close();
|
env.close();
|
||||||
|
|
||||||
// Set flags on gw2 trust lines so we can look for them.
|
// 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();
|
env.close();
|
||||||
LedgerInfo const ledger58Info = env.closed()->info();
|
LedgerInfo const ledger58Info = env.closed()->info();
|
||||||
@@ -1311,6 +1321,7 @@ public:
|
|||||||
gw2.human() + R"("}})");
|
gw2.human() + R"("}})");
|
||||||
auto const& line = lines[jss::result][jss::lines][0u];
|
auto const& line = lines[jss::result][jss::lines][0u];
|
||||||
BEAST_EXPECT(line[jss::freeze].asBool() == true);
|
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::no_ripple].asBool() == true);
|
||||||
BEAST_EXPECT(line[jss::peer_authorized].asBool() == true);
|
BEAST_EXPECT(line[jss::peer_authorized].asBool() == true);
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
@@ -1338,6 +1349,7 @@ public:
|
|||||||
alice.human() + R"("}})");
|
alice.human() + R"("}})");
|
||||||
auto const& lineA = linesA[jss::result][jss::lines][0u];
|
auto const& lineA = linesA[jss::result][jss::lines][0u];
|
||||||
BEAST_EXPECT(lineA[jss::freeze_peer].asBool() == true);
|
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::no_ripple_peer].asBool() == true);
|
||||||
BEAST_EXPECT(lineA[jss::authorized].asBool() == true);
|
BEAST_EXPECT(lineA[jss::authorized].asBool() == true);
|
||||||
BEAST_EXPECT(
|
BEAST_EXPECT(
|
||||||
|
|||||||
@@ -139,6 +139,13 @@ public:
|
|||||||
return mFlags & (mViewLowest ? lsfLowFreeze : lsfHighFreeze);
|
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 */
|
/** Has the peer set the freeze flag on us */
|
||||||
bool
|
bool
|
||||||
getFreezePeer() const
|
getFreezePeer() const
|
||||||
@@ -146,6 +153,13 @@ public:
|
|||||||
return mFlags & (!mViewLowest ? lsfLowFreeze : lsfHighFreeze);
|
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&
|
STAmount const&
|
||||||
getBalance() const
|
getBalance() const
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ checkFreeze(
|
|||||||
{
|
{
|
||||||
return terNO_LINE;
|
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;
|
return tesSUCCESS;
|
||||||
|
|||||||
@@ -392,6 +392,7 @@ CashCheck::doApply()
|
|||||||
false, // authorize account
|
false, // authorize account
|
||||||
(sleDst->getFlags() & lsfDefaultRipple) == 0,
|
(sleDst->getFlags() & lsfDefaultRipple) == 0,
|
||||||
false, // freeze trust line
|
false, // freeze trust line
|
||||||
|
false, // deep freeze trust line
|
||||||
initialBalance, // zero initial balance
|
initialBalance, // zero initial balance
|
||||||
Issue(currency, account_), // limit of zero
|
Issue(currency, account_), // limit of zero
|
||||||
0, // quality in
|
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;
|
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
|
void
|
||||||
ValidNewAccountRoot::visitEntry(
|
ValidNewAccountRoot::visitEntry(
|
||||||
bool,
|
bool,
|
||||||
|
|||||||
@@ -270,6 +270,114 @@ public:
|
|||||||
beast::Journal const&);
|
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
|
* @brief Invariant: offers should be for non-negative amounts and must not
|
||||||
* be XRP to XRP.
|
* be XRP to XRP.
|
||||||
@@ -518,6 +626,8 @@ using InvariantChecks = std::tuple<
|
|||||||
XRPBalanceChecks,
|
XRPBalanceChecks,
|
||||||
XRPNotCreated,
|
XRPNotCreated,
|
||||||
NoXRPTrustLines,
|
NoXRPTrustLines,
|
||||||
|
NoDeepFreezeTrustLinesWithoutFreeze,
|
||||||
|
TransfersNotFrozen,
|
||||||
NoBadOffers,
|
NoBadOffers,
|
||||||
NoZeroEscrow,
|
NoZeroEscrow,
|
||||||
ValidNewAccountRoot,
|
ValidNewAccountRoot,
|
||||||
|
|||||||
@@ -268,6 +268,20 @@ NFTokenAcceptOffer::preclaim(PreclaimContext const& ctx)
|
|||||||
ctx.j) < needed)
|
ctx.j) < needed)
|
||||||
return tecINSUFFICIENT_FUNDS;
|
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
|
// Fix a bug where the transfer of an NFToken with a transfer fee could
|
||||||
@@ -510,4 +524,62 @@ NFTokenAcceptOffer::doApply()
|
|||||||
return tecINTERNAL;
|
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
|
} // namespace ripple
|
||||||
|
|||||||
@@ -44,6 +44,14 @@ private:
|
|||||||
AccountID const& seller,
|
AccountID const& seller,
|
||||||
uint256 const& nfTokenID);
|
uint256 const& nfTokenID);
|
||||||
|
|
||||||
|
static TER
|
||||||
|
checkAcceptAsset(
|
||||||
|
ReadView const& view,
|
||||||
|
ApplyFlags const flags,
|
||||||
|
AccountID const id,
|
||||||
|
beast::Journal const j,
|
||||||
|
Issue const& issue);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||||
|
|
||||||
|
|||||||
@@ -273,6 +273,20 @@ TOfferStreamBase<TIn, TOut>::step()
|
|||||||
continue;
|
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
|
// Calculate owner funds
|
||||||
ownerFunds_ = accountFundsHelper(
|
ownerFunds_ = accountFundsHelper(
|
||||||
view_,
|
view_,
|
||||||
|
|||||||
@@ -26,6 +26,42 @@
|
|||||||
#include <xrpl/protocol/Quality.h>
|
#include <xrpl/protocol/Quality.h>
|
||||||
#include <xrpl/protocol/st.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 {
|
namespace ripple {
|
||||||
|
|
||||||
NotTEC
|
NotTEC
|
||||||
@@ -45,6 +81,16 @@ SetTrust::preflight(PreflightContext const& ctx)
|
|||||||
return temINVALID_FLAG;
|
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));
|
STAmount const saLimitAmount(tx.getFieldAmount(sfLimitAmount));
|
||||||
|
|
||||||
if (!isLegalNet(saLimitAmount))
|
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;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +295,7 @@ SetTrust::doApply()
|
|||||||
Currency const currency(saLimitAmount.getCurrency());
|
Currency const currency(saLimitAmount.getCurrency());
|
||||||
AccountID uDstAccountID(saLimitAmount.getIssuer());
|
AccountID uDstAccountID(saLimitAmount.getIssuer());
|
||||||
|
|
||||||
// true, iff current is high account.
|
// true, if current is high account.
|
||||||
bool const bHigh = account_ > uDstAccountID;
|
bool const bHigh = account_ > uDstAccountID;
|
||||||
|
|
||||||
auto const sle = view().peek(keylet::account(account_));
|
auto const sle = view().peek(keylet::account(account_));
|
||||||
@@ -242,13 +340,15 @@ SetTrust::doApply()
|
|||||||
bool const bClearNoRipple = (uTxFlags & tfClearNoRipple);
|
bool const bClearNoRipple = (uTxFlags & tfClearNoRipple);
|
||||||
bool const bSetFreeze = (uTxFlags & tfSetFreeze);
|
bool const bSetFreeze = (uTxFlags & tfSetFreeze);
|
||||||
bool const bClearFreeze = (uTxFlags & tfClearFreeze);
|
bool const bClearFreeze = (uTxFlags & tfClearFreeze);
|
||||||
|
bool const bSetDeepFreeze = (uTxFlags & tfSetDeepFreeze);
|
||||||
|
bool const bClearDeepFreeze = (uTxFlags & tfClearDeepFreeze);
|
||||||
|
|
||||||
auto viewJ = ctx_.app.journal("View");
|
auto viewJ = ctx_.app.journal("View");
|
||||||
|
|
||||||
// Trust lines to self are impossible but because of the old bug there are
|
// Trust lines to self are impossible but because of the old bug there
|
||||||
// two on 19-02-2022. This code was here to allow those trust lines to be
|
// are two on 19-02-2022. This code was here to allow those trust lines
|
||||||
// deleted. The fixTrustLinesToSelf fix amendment will remove them when it
|
// to be deleted. The fixTrustLinesToSelf fix amendment will remove them
|
||||||
// enables so this code will no longer be needed.
|
// when it enables so this code will no longer be needed.
|
||||||
if (!view().rules().enabled(fixTrustLinesToSelf) &&
|
if (!view().rules().enabled(fixTrustLinesToSelf) &&
|
||||||
account_ == uDstAccountID)
|
account_ == uDstAccountID)
|
||||||
{
|
{
|
||||||
@@ -408,14 +508,16 @@ SetTrust::doApply()
|
|||||||
uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple);
|
uFlagsOut &= ~(bHigh ? lsfHighNoRipple : lsfLowNoRipple);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bSetFreeze && !bClearFreeze && !sle->isFlag(lsfNoFreeze))
|
// Have to use lsfNoFreeze to maintain pre-deep freeze behavior
|
||||||
{
|
bool const bNoFreeze = sle->isFlag(lsfNoFreeze);
|
||||||
uFlagsOut |= (bHigh ? lsfHighFreeze : lsfLowFreeze);
|
uFlagsOut = computeFreezeFlags(
|
||||||
}
|
uFlagsOut,
|
||||||
else if (bClearFreeze && !bSetFreeze)
|
bHigh,
|
||||||
{
|
bNoFreeze,
|
||||||
uFlagsOut &= ~(bHigh ? lsfHighFreeze : lsfLowFreeze);
|
bSetFreeze,
|
||||||
}
|
bClearFreeze,
|
||||||
|
bSetDeepFreeze,
|
||||||
|
bClearDeepFreeze);
|
||||||
|
|
||||||
if (QUALITY_ONE == uLowQualityOut)
|
if (QUALITY_ONE == uLowQualityOut)
|
||||||
uLowQualityOut = 0;
|
uLowQualityOut = 0;
|
||||||
@@ -498,8 +600,8 @@ SetTrust::doApply()
|
|||||||
// Reserve is not scaled by load.
|
// Reserve is not scaled by load.
|
||||||
else if (bReserveIncrease && mPriorBalance < reserveCreate)
|
else if (bReserveIncrease && mPriorBalance < reserveCreate)
|
||||||
{
|
{
|
||||||
JLOG(j_.trace())
|
JLOG(j_.trace()) << "Delay transaction: Insufficent reserve to "
|
||||||
<< "Delay transaction: Insufficent reserve to add trust line.";
|
"add trust line.";
|
||||||
|
|
||||||
// Another transaction could provide XRP to the account and then
|
// Another transaction could provide XRP to the account and then
|
||||||
// this transaction would succeed.
|
// this transaction would succeed.
|
||||||
@@ -515,17 +617,18 @@ SetTrust::doApply()
|
|||||||
// Line does not exist.
|
// Line does not exist.
|
||||||
else if (
|
else if (
|
||||||
!saLimitAmount && // Setting default limit.
|
!saLimitAmount && // Setting default limit.
|
||||||
(!bQualityIn || !uQualityIn) && // Not setting quality in or setting
|
(!bQualityIn || !uQualityIn) && // Not setting quality in or
|
||||||
// default quality in.
|
// setting default quality in.
|
||||||
(!bQualityOut || !uQualityOut) && // Not setting quality out or setting
|
(!bQualityOut || !uQualityOut) && // Not setting quality out or
|
||||||
// default quality out.
|
// setting default quality out.
|
||||||
(!bSetAuth))
|
(!bSetAuth))
|
||||||
{
|
{
|
||||||
JLOG(j_.trace())
|
JLOG(j_.trace())
|
||||||
<< "Redundant: Setting non-existent ripple line to defaults.";
|
<< "Redundant: Setting non-existent ripple line to defaults.";
|
||||||
return tecNO_LINE_REDUNDANT;
|
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. "
|
JLOG(j_.trace()) << "Delay transaction: Line does not exist. "
|
||||||
"Insufficent reserve to create line.";
|
"Insufficent reserve to create line.";
|
||||||
@@ -555,6 +658,7 @@ SetTrust::doApply()
|
|||||||
bSetAuth,
|
bSetAuth,
|
||||||
bSetNoRipple && !bClearNoRipple,
|
bSetNoRipple && !bClearNoRipple,
|
||||||
bSetFreeze && !bClearFreeze,
|
bSetFreeze && !bClearFreeze,
|
||||||
|
bSetDeepFreeze,
|
||||||
saBalance,
|
saBalance,
|
||||||
saLimitAllow, // Limit for who is being charged.
|
saLimitAllow, // Limit for who is being charged.
|
||||||
uQualityIn,
|
uQualityIn,
|
||||||
|
|||||||
@@ -153,6 +153,13 @@ isFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
|
|||||||
asset.value());
|
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.
|
// Returns the amount an account can spend without going into debt.
|
||||||
//
|
//
|
||||||
// <-- saAmount: amount of currency held by account. May be negative.
|
// <-- saAmount: amount of currency held by account. May be negative.
|
||||||
@@ -438,6 +445,7 @@ trustCreate(
|
|||||||
const bool bAuth, // --> authorize account.
|
const bool bAuth, // --> authorize account.
|
||||||
const bool bNoRipple, // --> others cannot ripple through
|
const bool bNoRipple, // --> others cannot ripple through
|
||||||
const bool bFreeze, // --> funds cannot leave
|
const bool bFreeze, // --> funds cannot leave
|
||||||
|
bool bDeepFreeze, // --> can neither receive nor send funds
|
||||||
STAmount const& saBalance, // --> balance of account being set.
|
STAmount const& saBalance, // --> balance of account being set.
|
||||||
// Issuer should be noAccount()
|
// Issuer should be noAccount()
|
||||||
STAmount const& saLimit, // --> limit for account being set.
|
STAmount const& saLimit, // --> limit for account being set.
|
||||||
|
|||||||
@@ -267,6 +267,32 @@ isFrozen(
|
|||||||
isIndividualFrozen(view, account, mptIssue);
|
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
|
STAmount
|
||||||
accountHolds(
|
accountHolds(
|
||||||
ReadView const& view,
|
ReadView const& view,
|
||||||
@@ -284,17 +310,25 @@ accountHolds(
|
|||||||
|
|
||||||
// IOU: Return balance on trust line modulo freeze
|
// IOU: Return balance on trust line modulo freeze
|
||||||
auto const sle = view.read(keylet::line(account, issuer, currency));
|
auto const sle = view.read(keylet::line(account, issuer, currency));
|
||||||
|
auto const allowBalance = [&]() {
|
||||||
if (!sle)
|
if (!sle)
|
||||||
{
|
{
|
||||||
amount.clear(Issue{currency, issuer});
|
return false;
|
||||||
}
|
}
|
||||||
else if (
|
|
||||||
(zeroIfFrozen == fhZERO_IF_FROZEN) &&
|
if (zeroIfFrozen == fhZERO_IF_FROZEN)
|
||||||
isFrozen(view, account, currency, issuer))
|
|
||||||
{
|
{
|
||||||
amount.clear(Issue{currency, issuer});
|
if (isFrozen(view, account, currency, issuer) ||
|
||||||
|
isDeepFrozen(view, account, currency, issuer))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (allowBalance)
|
||||||
{
|
{
|
||||||
amount = sle->getFieldAmount(sfBalance);
|
amount = sle->getFieldAmount(sfBalance);
|
||||||
if (account > issuer)
|
if (account > issuer)
|
||||||
@@ -304,6 +338,11 @@ accountHolds(
|
|||||||
}
|
}
|
||||||
amount.setIssuer(issuer);
|
amount.setIssuer(issuer);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
amount.clear(Issue{currency, issuer});
|
||||||
|
}
|
||||||
|
|
||||||
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(account)
|
JLOG(j.trace()) << "accountHolds:" << " account=" << to_string(account)
|
||||||
<< " amount=" << amount.getFullText();
|
<< " amount=" << amount.getFullText();
|
||||||
|
|
||||||
@@ -863,6 +902,7 @@ trustCreate(
|
|||||||
const bool bAuth, // --> authorize account.
|
const bool bAuth, // --> authorize account.
|
||||||
const bool bNoRipple, // --> others cannot ripple through
|
const bool bNoRipple, // --> others cannot ripple through
|
||||||
const bool bFreeze, // --> funds cannot leave
|
const bool bFreeze, // --> funds cannot leave
|
||||||
|
bool bDeepFreeze, // --> can neither receive nor send funds
|
||||||
STAmount const& saBalance, // --> balance of account being set.
|
STAmount const& saBalance, // --> balance of account being set.
|
||||||
// Issuer should be noAccount()
|
// Issuer should be noAccount()
|
||||||
STAmount const& saLimit, // --> limit for account being set.
|
STAmount const& saLimit, // --> limit for account being set.
|
||||||
@@ -944,7 +984,11 @@ trustCreate(
|
|||||||
}
|
}
|
||||||
if (bFreeze)
|
if (bFreeze)
|
||||||
{
|
{
|
||||||
uFlags |= (!bSetHigh ? lsfLowFreeze : lsfHighFreeze);
|
uFlags |= (bSetHigh ? lsfHighFreeze : lsfLowFreeze);
|
||||||
|
}
|
||||||
|
if (bDeepFreeze)
|
||||||
|
{
|
||||||
|
uFlags |= (bSetHigh ? lsfHighDeepFreeze : lsfLowDeepFreeze);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((slePeer->getFlags() & lsfDefaultRipple) == 0)
|
if ((slePeer->getFlags() & lsfDefaultRipple) == 0)
|
||||||
@@ -1189,6 +1233,7 @@ rippleCreditIOU(
|
|||||||
false,
|
false,
|
||||||
noRipple,
|
noRipple,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
saBalance,
|
saBalance,
|
||||||
saReceiverLimit,
|
saReceiverLimit,
|
||||||
0,
|
0,
|
||||||
@@ -1688,6 +1733,7 @@ issueIOU(
|
|||||||
false,
|
false,
|
||||||
noRipple,
|
noRipple,
|
||||||
false,
|
false,
|
||||||
|
false,
|
||||||
final_balance,
|
final_balance,
|
||||||
limit,
|
limit,
|
||||||
0,
|
0,
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ addLine(Json::Value& jsonLines, RPCTrustLine const& line)
|
|||||||
jPeer[jss::freeze] = true;
|
jPeer[jss::freeze] = true;
|
||||||
if (line.getFreezePeer())
|
if (line.getFreezePeer())
|
||||||
jPeer[jss::freeze_peer] = true;
|
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