-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deep freeze (#5187) #5187
base: develop
Are you sure you want to change the base?
Deep freeze (#5187) #5187
Conversation
Specification: XRPLF/XRPL-Standards#220
to accepting the amendment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial review. I didn't go through all the tests.
9421c37
to
c7c26ac
Compare
ba87028
to
de58a26
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial review
Co-authored-by: Ed Hennis <ed@ripple.com>
src/test/app/AMM_test.cpp
Outdated
@@ -4397,6 +4397,48 @@ struct AMM_test : public jtx::AMMTest | |||
0, | |||
std::nullopt, | |||
{features}); | |||
|
|||
// Individually deep frozen account | |||
if (features[featureDeepFreeze]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need to repeat the code. Just add a conditional error:
auto const err = features[featureDeepFreeze] ? ter(tecPATH_DRY) : ter(tesSUCCESS)
And then use it in pay
:
env(pay(alice, carol, USD(1)),
path(~USD),
sendmax(XRP(10)),
txflags(tfNoRippleDirect | tfPartialPayment),
err);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, you shouldn't test here and other updated unit-tests if the feature is disabled because trust
is going to fail. You need a separate test for disabled feature in SetTrust
and also add to SetTrust
tests for invalid combination of the flags. Also need to add OfferCreate
and offer crossing, if not added yet, to Offer
.
The issue is related to unity build, working on a fix |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #5187 +/- ##
=========================================
- Coverage 78.0% 77.9% -0.1%
=========================================
Files 789 789
Lines 66955 67164 +209
Branches 8132 8166 +34
=========================================
+ Hits 52223 52325 +102
- Misses 14732 14839 +107
|
@@ -1778,6 +1781,151 @@ class Freeze_test : public beast::unit_test::suite | |||
} | |||
} | |||
|
|||
void | |||
testNFTOffersWhenFreeze(FeatureBitset features) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these tests only exercise when someone accepts a sell offer from the owner. have we considered adding tests where the NFT owner accepts a buy offer? (where the potential buyer is deep frozen)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if buyer is deep frozen, he's normally frozen. His accountHolds check will not let this transaction through. Such tests exist in nft tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i thought of another scenario:
- alice mints NFT
- alice creates a sell offer using USD
- bob creates a buy offer using USD
- carol (broker) accepts the two offers and earns some USD (if buy offer amount is higher than sell offer, broker has the oppportunity to earn
buy offer amt - sell offer amt
) You can find unit tests by searching forbrokerOffers
.
we would need to make sure that at step 4, carol cannot broker the offers if she is deep frozen
// test: this should actually fail since the currency holder doesn't | ||
// want to receive this currency. | ||
env(token::acceptSellOffer(A1, sellOfferIndex)); | ||
env.close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is incorrect. A2
has indicated that they want to receive the currency by creating the offer. Any affirmative action by the account receiving funds can override the "limit" set on the trust line, or create a trust line if one doesn't exist. The same is true of offers on the DEX.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We had this case discussed during last call. It's a part of 'missing checks if account is allowed to receive currency'. I'm not convinced this is correct behavior, normal offers behave differently. Also I see the use-case where currency holder would want to freeze the trustline to stop inflow of certain currency instead of cancelling all existing nft offers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only exception here would actually be acceptance of check, which is really an indication of acceptance. But even this is not currently possible.
if (zeroIfFrozen == fhZERO_IF_FROZEN) | ||
{ | ||
if (isFrozen(view, account, currency, issuer) || | ||
isDeepFrozen(view, account, currency, issuer)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious, why not keep the original code and just add || isDeepFrozen
?
else if (
(zeroIfFrozen == fhZERO_IF_FROZEN) &&
(isFrozen(view, account, currency, issuer) ||
isDeepFrozen(view, account, currency, issuer)))
Unless there is a good reason to change, IMO fewer changes is less risk and easier to review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just couldn't help myself to change the previous version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see much benefit in this change. It's one line change vs. a bunch of changes. But it's just my opinion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I find it a bit easier to understand and read, that's the only reason for this change. I can roll back if you insist. I has good test coverage though, so I don't find it risky
auto const trustLine = | ||
view.read(keylet::line(id, issue.account, issue.currency)); | ||
|
||
if (!trustLine) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why returning tesSUCCESS
? Shouldn't this be similar to a few lines above:
if (!trustLine)
{
return (flags & tapRETRY) ? TER{terNO_LINE} : TER{tecNO_LINE};
}
If returning an error then the code also has to be amendment gated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here it's simply checking if trust line exists. If it doesn't exist, there's no freeze/deep freeze flags there so we just exit
env.close(); | ||
|
||
// test: A1 can send XRP using USD through A2 offer | ||
// TODO: This might be not a correct behavior. A2 doesn't want to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When holder freezes trust line, his intention is to avoid receiving this token. So it might be incorrect behavior because in this case he actually receives those.
env.close(); | ||
|
||
// test: G1 cannot send XRP using USD through A2 offer | ||
// TODO: This might be not a correct behavior. A2 doesn't want to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
} | ||
|
||
void | ||
testLongerPathsWhenFrozen(FeatureBitset features) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why "LongerPaths"? It's the same number of steps, just a payment is used instead of an offer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it the same number? These tests have book step and xrp step at the very least?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the offer crossing tests you are exchanging XRP for USD or USD for XRP. Payment does the same thing. The payment engine steps are slightly different. In case of the offer crossing the steps are:
XRPEndpointOfferCrossingStep
BookOfferCrossingStep
DirectIOfferCrossingStep
and in case of the payment the steps are:
XRPEndpointPaymentStep
BookPaymentStep
DirectIPaymentStep
A longer path is something like this: exchange XRP for USD and then exchange USD for GBP as part of the same payment. Or as a unit test something like this:
env(pay(carol, bob, USD(1)),
test::jtx::path(~USD, ~GBP),
sendmax(XRP(1)), txflags(tfNoRippleDirect));
@@ -981,7 +987,11 @@ class AccountLines_test : public beast::unit_test::suite | |||
env.close(); | |||
|
|||
// Set flags on gw2 trust lines so we can look for them. | |||
env(trust(alice, gw2Currency(0), gw2, tfSetNoRipple | tfSetFreeze)); | |||
env(trust( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the difference between this test and the one above? (Both of them are prefaced by the same comment // Set flags on gw2 trust lines so we can look for them.
)
Are any of the pre-conditions different in these tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
different RPC formats seem to be the difference
}; | ||
|
||
/** | ||
* @brief Invariant: frozen trust line balance change is not allowed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A normal frozen trust line could increase its balance (another counter-party could send IOUs to increase the frozen account's balance). Is this a different case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there unit tests which exercise this invariant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
working on them at the moment.
@@ -62,6 +62,10 @@ addLine(Json::Value& jsonLines, RPCTrustLine const& line) | |||
jPeer[jss::freeze] = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the ledger_entry
RPC command return deep_freeze
flags (provided, it is set appropriately on the trust line) ? Can you add unit tests for the same?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am referring to retrieval of trust-line ledger objects using the ledger_entry
RPC
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ledger_entry doesn't return anything related to trustline flags, so in a nutshell, it doesn't return information about either normal freeze or deep freeze.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ledger_entry
does return a Flags
field in the response. But it does not explicitly specify a boolean value for the deep_freeze field (unlike the account_lines
rpc)
Perhaps, it does not warrant a unit test. I'll leave that up to your discretion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I see. I probably looked into wrong implementation.
But anyway, since raw flags are provided, there's no reason to add deep_freeze flag specific test in my opinion.
XRPL_ASSERT( | ||
after, "ripple::BalanceChangeNotFrozen::visitEntry : invalid after."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name is supposed to indicate what you're testing, not what failed.
XRPL_ASSERT( | |
after, "ripple::BalanceChangeNotFrozen::visitEntry : invalid after."); | |
XRPL_ASSERT( | |
after, "ripple::BalanceChangeNotFrozen::visitEntry : valid after."); |
STAmount amt = | ||
line ? line->at(sfBalance) : line->at(sfBalance).zeroed(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
STAmount amt = | |
line ? line->at(sfBalance) : line->at(sfBalance).zeroed(); | |
STAmount amt = | |
line ? line->at(sfBalance) : other->at(sfBalance).zeroed(); |
XRPL_ASSERT( | ||
change.balanceChangeSign, | ||
"ripple::BalanceChangeNotFrozen::recordBalance : invalid trust " | ||
"line balance sign."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
XRPL_ASSERT( | |
change.balanceChangeSign, | |
"ripple::BalanceChangeNotFrozen::recordBalance : invalid trust " | |
"line balance sign."); | |
XRPL_ASSERT( | |
change.balanceChangeSign, | |
"ripple::BalanceChangeNotFrozen::recordBalance : valid trust " | |
"line balance sign."); |
recordBalance( | ||
after->at(sfHighLimit).getIssuer(), | ||
currency, | ||
{after, balanceChangeSign}); | ||
|
||
recordBalance( | ||
after->at(sfLowLimit).getIssuer(), | ||
currency, | ||
{after, -balanceChangeSign}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
High and low sign handling is a consistent source of confusion. Some comments here would be helpful. Something like:
recordBalance( | |
after->at(sfHighLimit).getIssuer(), | |
currency, | |
{after, balanceChangeSign}); | |
recordBalance( | |
after->at(sfLowLimit).getIssuer(), | |
currency, | |
{after, -balanceChangeSign}); | |
// Change from low account's perspective, which is trust line default | |
recordBalance( | |
after->at(sfHighLimit).getIssuer(), | |
currency, | |
{after, balanceChangeSign}); | |
// Change from high account's perspective, which reverses the sign. | |
recordBalance( | |
after->at(sfLowLimit).getIssuer(), | |
currency, | |
{after, -balanceChangeSign}); |
XRPL_ASSERT( | ||
enforce, | ||
"ripple::BalanceChangeNotFrozen::validateFrozenState : Attempting to " | ||
"move frozen funds."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
XRPL_ASSERT( | |
enforce, | |
"ripple::BalanceChangeNotFrozen::validateFrozenState : Attempting to " | |
"move frozen funds."); | |
XRPL_ASSERT( | |
enforce, | |
"ripple::BalanceChangeNotFrozen::validateFrozenState : enforce invariant."); |
XRPL_ASSERT( | ||
enforce, | ||
"ripple::BalanceChangeNotFrozen::finalize : Issuer not found."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
XRPL_ASSERT( | |
enforce, | |
"ripple::BalanceChangeNotFrozen::finalize : Issuer not found."); | |
XRPL_ASSERT( | |
enforce, | |
"ripple::BalanceChangeNotFrozen::finalize : enforce invariant."); |
XRPL_ASSERT( | ||
!isXRP(issue.currency), | ||
"NFTokenAcceptOffer::checkAcceptAsset : input is not XRP"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
XRPL_ASSERT( | |
!isXRP(issue.currency), | |
"NFTokenAcceptOffer::checkAcceptAsset : input is not XRP"); | |
XRPL_ASSERT( | |
!isXRP(issue.currency) && view.rules().enabled(featureDeepFreeze) , | |
"NFTokenAcceptOffer::checkAcceptAsset : valid to check"); |
High Level Overview of Change
This PR implements Deep Freeze feature described in this specification: XLS-77d
Context of Change
Added new flags and functionality allowing trust lines to be deep-frozen.
Type of Change
.gitignore
, formatting, dropping support for older tooling)API Impact
libxrpl
change (any change that may affectlibxrpl
or dependents oflibxrpl
)