-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix: Reject VaultWithdraw fixed-share amounts that round to zero #7950
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
base: develop
Are you sure you want to change the base?
Changes from 1 commit
ed6a130
c8e46d5
a404103
fce3847
11fd4cc
84c5035
fc0ca82
f3ab038
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -805,19 +805,36 @@ ValidVault::finalize( | |||||
| auto const& beforeVault = beforeVault_[0]; | ||||||
|
|
||||||
| auto const maybeVaultDeltaAssets = deltaAssets(afterVault.pseudoId); | ||||||
| if (!maybeVaultDeltaAssets) | ||||||
|
|
||||||
| // Post-fixCleanup3_4_0: a withdrawal that redeems shares from a | ||||||
| // pool with no effective value left to back them (e.g. fully | ||||||
| // impaired/insolvent) legitimately moves zero assets on both | ||||||
| // sides — VaultWithdraw::doApply does not touch either | ||||||
| // balance-holding entry for a zero-value transfer, so no delta | ||||||
| // is recorded. VaultWithdraw::doApply separately rejects | ||||||
| // (tecPRECISION_LOSS) the case where a *positive* per-share | ||||||
| // value merely rounds down to zero, so a missing delta while | ||||||
| // the pool still held positive effective value indicates a | ||||||
| // real accounting bug, not this exception. | ||||||
| bool const zeroDeltaIsLegitimate = view.rules().enabled(fixCleanup3_4_0) && | ||||||
| !maybeVaultDeltaAssets && beforeVault.assetsTotal <= beforeVault.lossUnrealized; | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it's possible to have a legitimate case where
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! Changed to strict equality. |
||||||
|
|
||||||
| if (!maybeVaultDeltaAssets && !zeroDeltaIsLegitimate) | ||||||
| { | ||||||
| JLOG(j.fatal()) << "Invariant failed: withdrawal must change vault balance"; | ||||||
| return false; // That's all we can do | ||||||
| } | ||||||
|
|
||||||
| DeltaInfo const vaultDeltaAssets = maybeVaultDeltaAssets.value_or( | ||||||
| DeltaInfo{.delta = kNumZero, .scale = std::nullopt}); | ||||||
|
|
||||||
| // Get the posterior scale to round calculations to | ||||||
| auto const minScale = computeVaultMinScale(*maybeVaultDeltaAssets, view.rules()); | ||||||
| auto const minScale = computeVaultMinScale(vaultDeltaAssets, view.rules()); | ||||||
|
|
||||||
| auto const vaultPseudoDeltaAssets = | ||||||
| roundToAsset(vaultAsset, maybeVaultDeltaAssets->delta, minScale); | ||||||
| roundToAsset(vaultAsset, vaultDeltaAssets.delta, minScale); | ||||||
|
|
||||||
| if (vaultPseudoDeltaAssets >= kZero) | ||||||
| if (!zeroDeltaIsLegitimate && vaultPseudoDeltaAssets >= kZero) | ||||||
| { | ||||||
| JLOG(j.fatal()) << "Invariant failed: withdrawal must decrease vault balance"; | ||||||
| result = false; | ||||||
|
|
@@ -832,7 +849,7 @@ ValidVault::finalize( | |||||
| return destination == vaultAsset.getIssuer(); | ||||||
| }(); | ||||||
|
|
||||||
| if (!issuerWithdrawal) | ||||||
| if (!issuerWithdrawal && !zeroDeltaIsLegitimate) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Account-delta cross-check shouldn't be gated by zeroDeltaIsLegitimate. Use zero fallback:
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done in 11fd4cc, with one adjustment: with the gate removed, a legitimate zero-value withdrawal has both destination deltas absent, which would trip the "must change one destination balance" check. So that specific case is tolerated explicitly (nothing moved anywhere, nothing to cross-check), while any one-sided balance change now runs the full cross-check against the zero-fallback vault delta. The sub-ULP check also had to switch from
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ Auto-resolved: the latest review no longer flags this issue, so it appears to have been addressed. If that is not correct, reopen this thread and it will be re-checked on the next review.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ Auto-resolved: the latest review no longer flags this issue, so it appears to have been addressed. If that is not correct, reopen this thread and it will be re-checked on the next review.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✅ Auto-resolved: the latest review no longer flags this issue, so it appears to have been addressed. If that is not correct, reopen this thread and it will be re-checked on the next review. |
||||||
| { | ||||||
| auto const maybeAccDelta = deltaAssetsTxAccount(tx, fee); | ||||||
| auto const maybeOtherAccDelta = [&]() -> std::optional<DeltaInfo> { | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.