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:
Vlad
2025-01-31 18:40:33 +00:00
committed by GitHub
parent 1b75dc8bcd
commit 33e1c42599
21 changed files with 2479 additions and 49 deletions

View File

@@ -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();