Adds functionality to block vault deposits #6361
Conversation
| { | ||
| auto tx = vault.set({.owner = owner, .id = keylet.key}); | ||
| tx[sfFlags] = tfVaultPrivate; | ||
| tx[sfFlags] = tfVaultDepositBlock | tfVaultDepositUnblock; |
There was a problem hiding this comment.
Reviewer note: tfVaultPrivate and tfVaultDepositBlock have identical numerical values.
There was a problem hiding this comment.
Reviewer note:
tfVaultPrivateandtfVaultDepositBlockhave identical numerical values.
Is that a coincidence, or does it have functional implications?
Also, if you have a reason to want this intentionally, enforce it in the code with a static_assert. (There's an example in the "VaultCreate flags".)
There was a problem hiding this comment.
It's just a coincidence, there are no implications. The comment is to say that even though I changed the flags, the unit-test still tests the same logic: an invalid flag.
There was a problem hiding this comment.
It's just a coincidence, there are no implications. The comment is to say that even though I changed the flags, the unit-test still tests the same logic: an invalid flag.
Oh, I see. tfVaultPrivate was never a valid flag for VaultSet, but since you added tfVaultDepositBlock with the same value, it would have looked valid, so you had to change it to an invalid combo.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## tapanito/lending-fix-amendment #6361 +/- ##
==============================================================
Coverage 81.5% 81.5%
==============================================================
Files 999 999
Lines 74550 74595 +45
Branches 7560 7553 -7
==============================================================
+ Hits 60722 60778 +56
+ Misses 13828 13817 -11
🚀 New features to boost your workflow:
|
Move vault share/asset conversion functions (assetsToSharesDeposit, sharesToAssetsDeposit, assetsToSharesWithdraw, sharesToAssetsWithdraw) and isVaultInsolvent from View.h/View.cpp into a dedicated VaultHelpers.h/VaultHelpers.cpp module. Reorder includes in Vault transactor .cpp files to place own header first. Fix VaultSet flag validation logic.
| Vault& vault, | ||
| PrettyAsset const& asset, | ||
| auto&&...) { | ||
| testcase("IOU insolvent vault blocks deposits"); |
There was a problem hiding this comment.
I see tests for MPT and IOU. Should you also do XRP?
There was a problem hiding this comment.
I made additional changes to tests:
- I moved the "general" checks from MPT/IOU unit tests to
testVaultDepositBlockGeneral(). There is no benefit to test that blocking / unblocking works for all assets. - Regarding XRP tests, I noticed that there are no XRP tests for SAV. I'll add insolvency unit tests, but full XRP sav tests are out of scope.
| if (ctx.view.rules().enabled(fixLendingProtocolV1_1)) | ||
| { | ||
| // Perform these checks early to avoid unnecessary processing | ||
|
|
||
| // The Vault is insolvent, deposits are not allowed | ||
| if (isVaultInsolvent(vault, sleShareIssuance)) | ||
| { | ||
| JLOG(ctx.j.debug()) << "VaultDeposit: Vault is insolvent, deposits are not allowed"; | ||
| return tecNO_PERMISSION; | ||
| } | ||
|
|
||
| if (vault->isFlag(lsfVaultDepositBlocked)) | ||
| { | ||
| JLOG(ctx.j.debug()) << "VaultDeposit: Vault deposits are blocked"; | ||
| return tecNO_PERMISSION; | ||
| } | ||
| } |
There was a problem hiding this comment.
Maybe these should both be ter results. You'll probably need to create a new one - terNO_PERMISSION, maybe. That may make some of your tests more complicated because the transactions that now fail will be retried when you close the Env.
Consider:
- In both cases, the Vault owner could unblock the Vault, or make it solvent, immediately after this transaction. From
TER.h:// Cause: // Prior application of another, possibly non-existent, transaction could // allow this transaction to succeed. - The deposit transaction and the Vault owner's transaction could be in the same ledger. If both transactions survive consensus, the ordering is unpredictable.
- Vault owner unblocks deposits: If the owner's unblock is ordered after the deposit, then with a
tec, the deposit fee is claimed and the transaction is done. With ater, the deposit will be tried again after the unblock, and succeed. A better user experience. - Vault owner blocks deposits: Conversely, if the owner's block is ordered after the deposit, the deposit succeeds. If it's ordered before the deposit, the transaction is not included in the validated ledger - an acceptable outcome. The depositor doesn't lose their fee. If the owner then unblocks before the deposit expires, the deposit will succeed on that ledger. OTOH, if the depositor does not unblock, the transaction may not achieve finality for a while, so there are downsides.
- Attack vector? Since I'm talking about the possibility of a transaction getting broadcast, then not claim a fee, there is the possibility of a malicious user using this to waste resources. It is possible, but it would require lucky timing to have any meaningful effect. The vault owner could not launch the attack easily by themselves - the spec says there should be an exception for them. Even without an exception, they'd have to use tickets, which comes with its own unpredictability. So now you need an attacker account, and cooperation with the vault owner (or knowledge of the vault owner's plans). One account sends a bunch of
VaultDeposits, then the vault owner sends aVaultSetblocking deposits. In consensus, which set of transactions goes first is unpredictable. Any deposits ordered first will succeed. If theVaultSetis deferred to the next ledger, all of the deposits in the current ledger will succeed. If theVaultSetis in the same ledger and comes first, then the deposits may not get validated. The more deposits there are, the greater the chance that theVaultSetwill not be broadcast in time to be validated in the same ledger. And once the Vault is viewed as locked, no further deposits will be broadcast from the receiving node. In short, there are more effective ways to waste resources.
- Attack vector? Since I'm talking about the possibility of a transaction getting broadcast, then not claim a fee, there is the possibility of a malicious user using this to waste resources. It is possible, but it would require lucky timing to have any meaningful effect. The vault owner could not launch the attack easily by themselves - the spec says there should be an exception for them. Even without an exception, they'd have to use tickets, which comes with its own unpredictability. So now you need an attacker account, and cooperation with the vault owner (or knowledge of the vault owner's plans). One account sends a bunch of
- Vault owner unblocks deposits: If the owner's unblock is ordered after the deposit, then with a
There was a problem hiding this comment.
A simple change, yet so much subtilty. I guess a lid to a can of worms is usually plain..
Philosophy aside, in an ideal world, VaultSet transaction would be prioritised over other vault transactions to prevent race conditions, alas we do not live in such a world. Especially, if the VaultOwner is blocking deposits this may be for a good reason, perhaps to protect depositors, or prevent diluting shares of existing depositors.
Vault owner unblocks deposits: If the owner's unblock is ordered after the deposit, then with a tec, the deposit fee is claimed and the transaction is done. With a ter, the deposit will be tried again after the unblock, and succeed. A better user experience.
This is a better user experience, but at the same time, I vaguely remember us wanting to minimize ter codes as much as possible, though I don't remember the rationale. I suppose, because, as we say, sometimes you eat the bear, sometimes the bear eats you. A better UX is a compelling argument though.
Vault owner blocks deposits: Conversely, if the owner's block is ordered after the deposit, the deposit succeeds. If it's ordered before the deposit, the transaction is not included in the validated ledger - an acceptable outcome. The depositor doesn't lose their fee. If the owner then unblocks before the deposit expires, the deposit will succeed on that ledger. OTOH, if the depositor does not unblock, the transaction may not achieve finality for a while, so there are downsides. [ ... ]
How long does it take for a transaction to expire? I believe the transaction is punted to the next open ledger, where if it fails it fails. I think such behaviour is not worth it, i.e. given that transaction order is unpredictable, blocking and unblocking deposits back to back is nonsensical behaviour, as there are no guarantees that the blocking action will block the deposits that are in the same ledger, and similarly there are no guarantees about unblock.
At the end, I think the UX improvement is marginal, is it really worth adding a ter code for it? 🤔
| if (isVaultInsolvent(vault, sleShareIssuance)) | ||
| { | ||
| JLOG(ctx.j.debug()) << "VaultDeposit: Vault is insolvent, deposits are not allowed"; | ||
| return tecNO_PERMISSION; |
There was a problem hiding this comment.
The proposed updates to the spec indicate this should be tecLOCKED. XRPLF/XRPL-Standards#469 Section 6.2.2 item 7
There was a problem hiding this comment.
Is it okay to use tecLOCKED in this situation? That particular tec code is only used with MPTs as far as I know. It might be confusing for users to return it in such situation.
|
@ximinez , there was a small change to the spec. We introduced a This flag acts as a signal to depositors that the owner of this particular vault may block deposits. Thus, this flag can only be set when creating the vault. If In contrast, if the flag is set, the owner may block / unblock the Vault deposits as they see fit. |
…into tapanito/vault-block-deposit
|
/ai-review |
…into tapanito/vault-block-deposit
|
This PR has conflicts, please resolve them in order for the PR to be reviewed. |
| return tecNO_PERMISSION; | ||
| } | ||
|
|
||
| if (!vault->isFlag(lsfVaultDepositBlocked) && ctx.tx.isFlag(tfVaultDepositUnblock)) |
There was a problem hiding this comment.
I think it may make sense to allow cases where the flag we want to add already exists or the flag we want to remove doesn't exist.
We don't return an error when a transaction is updating another field and the value is the same as the current value, but we do return an error when the flag is already added, which feels inconsistent.
…into tapanito/vault-block-deposit
|
All conflicts have been resolved. Assigned reviewers can now start or resume their review. |
Specification: XRPLF/XRPL-Standards#469
High Level Overview of Change
Context of Change
Type of Change
.gitignore, formatting, dropping support for older tooling)API Impact
libxrplchange (any change that may affectlibxrplor dependents oflibxrpl)