-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Fix touchy "funds are conserved" assertion in LoanPay #6231
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: ximinez/staging-3.1
Are you sure you want to change the base?
Conversation
5a41799 to
c1e161c
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## ximinez/release-3.1.0-rc2 #6231 +/- ##
===========================================================
- Coverage 79.4% 79.3% -0.0%
===========================================================
Files 839 839
Lines 71716 71772 +56
Branches 8254 8271 +17
===========================================================
+ Hits 56914 56950 +36
- Misses 14802 14822 +20
🚀 New features to boost your workflow:
|
- Add "Yield Theft via Rounding Manipulation" test, which used to reliably triggered it. The test now verifies that no "yield theft" occurs.
c1e161c to
08170ff
Compare
|
|
||
| XRPL_ASSERT_PARTS( | ||
| *assetsAvailableProxy <= *assetsTotalProxy, | ||
| assetsAvailableAfter <= *assetsTotalProxy, |
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.
| assetsAvailableAfter <= *assetsTotalProxy, | |
| assetsAvailableAfter <= assetsTotalAfter, |
| JLOG(j_.warn()) << "LoanPay: Vault assets available unchanged after " | ||
| "rounding: Before: " | ||
| << assetsAvailableBefore | ||
| << ", After: " << assetsAvailableAfter; |
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.
nit:
| JLOG(j_.warn()) << "LoanPay: Vault assets available unchanged after " | |
| "rounding: Before: " | |
| << assetsAvailableBefore | |
| << ", After: " << assetsAvailableAfter; | |
| JLOG(j_.warn()) | |
| << "LoanPay: Vault assets available unchanged after rounding: " | |
| << "Before: " << assetsAvailableBefore | |
| << ", After: " << assetsAvailableAfter; |
| if (assetsAvailableAfter > *assetsTotalProxy) | ||
| { | ||
| // Assets available are not allowed to be larger than assets total. | ||
| // LCOV_EXCL_START | ||
| JLOG(j_.fatal()) | ||
| << "LoanPay: Vault assets available must not be greater " | ||
| "than assets outstanding. Available: " | ||
| << assetsAvailableAfter << ", Total: " << *assetsTotalProxy; |
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 (assetsAvailableAfter > *assetsTotalProxy) | |
| { | |
| // Assets available are not allowed to be larger than assets total. | |
| // LCOV_EXCL_START | |
| JLOG(j_.fatal()) | |
| << "LoanPay: Vault assets available must not be greater " | |
| "than assets outstanding. Available: " | |
| << assetsAvailableAfter << ", Total: " << *assetsTotalProxy; | |
| if (assetsAvailableAfter > assetsTotalAfter) | |
| { | |
| // Assets available are not allowed to be larger than assets total. | |
| // LCOV_EXCL_START | |
| JLOG(j_.fatal()) | |
| << "LoanPay: Vault assets available must not be greater " | |
| "than assets outstanding. Available: " | |
| << assetsAvailableAfter << ", Total: " << assetsTotalAfter; |
| std::minmax_element(exponents.begin(), exponents.end()); | ||
| // IOU rounding can be interesting. Give a margin of error that reflects | ||
| // the orders of magnitude between the extremes. | ||
| if (!asset.integral() && *max < STAmount::cMaxOffset * 3 / 4) |
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 understand that you're using the 3 / 4 division to create a threshold that is an order of magnitude, but perhaps the magic numbers are better place somewhere else, or at least provided as a constant variable? Future generations (most likely one of us), might find this confusing.
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.
How about using withinRelativeDistance()?
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 tricky thing is how does one determine an acceptable delta?
| ahIGNORE_AUTH, | ||
| j_, | ||
| SpendableHandling::shFULL_BALANCE); | ||
| auto const balanceScale = [&]() { |
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.
Perhaps this change can be done separately, but wouldn't enforcing that there is no absolute balance change, is better served in Loan invariant?
| JLOG(j_.warn()) | ||
| << "LoanPay: Vault assets expected change, but unchanged after " | ||
| "rounding: Before: " | ||
| << assetsTotalBefore << ", After: " << assetsTotalAfter | ||
| << ", ValueChange: " << paymentParts->valueChange; |
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.
nit:
| JLOG(j_.warn()) | |
| << "LoanPay: Vault assets expected change, but unchanged after " | |
| "rounding: Before: " | |
| << assetsTotalBefore << ", After: " << assetsTotalAfter | |
| << ", ValueChange: " << paymentParts->valueChange; | |
| JLOG(j_.warn()) << "LoanPay: Vault assets total expected change, but " | |
| "unchanged after rounding: " | |
| << "Before: " << assetsTotalBefore | |
| << ", After: " << assetsTotalAfter | |
| << ", ValueChange: " << paymentParts->valueChange; |
| log << "Periodic Payment: " << periodicPayment.value() << std::endl; | ||
| log << "Attack Payment: " << attackPayment.value() << std::endl; | ||
| log << "Initial Vault Assets: " << initialVaultAssets << std::endl; |
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.
Can we remove these logs?
| log << "[ALERT] Iteration " << i | ||
| << ": YIELD THEFT CONFIRMED. Vault Delta: " << delta | ||
| << std::endl; |
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 too
| log << "[INFO] Iteration " << i << ": Normal Yield: " << delta | ||
| << std::endl; |
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.
And these
| }(); | ||
| Vault vault{env}; | ||
| env(vault.deposit( | ||
| {.depositor = lender, .id = vaultId, .amount = iou(5'000'000)})); |
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 understand that this is copy pasted code, but it seems that this can be simplified.
The vault.deposit and loanBroker.coverDeposits are already performed in the createVaultAndBroker method. Thus these two operations can be removed, by passing the appropriate values to createVaultAndBroker, a few lines above.
| std::minmax_element(exponents.begin(), exponents.end()); | ||
| // IOU rounding can be interesting. Give a margin of error that reflects | ||
| // the orders of magnitude between the extremes. | ||
| if (!asset.integral() && *max < STAmount::cMaxOffset * 3 / 4) |
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.
Could you please expand on the balance scale logic? Suppose min = 7 and max = 9 and it's IOU. Then new max = 9 + (9 - 7) = 11. Why is it a better scale? I would have expected the lowest scale to be the best.
| ahIGNORE_AUTH, | ||
| j_, | ||
| SpendableHandling::shFULL_BALANCE); | ||
| auto const balanceScale = [&]() { |
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.
currently these checks are in DEBUG mode. Do we want to bring some checks into RELEASE?
High Level Overview of Change
IOU rounding when a Vault and a Loan are at significantly different scales can lead to the appearance of lost funds. An assertion in
LoanPaywants funds to be "conserved", but that isn't always possible.Context of Change
The new unit test
testYieldTheftRoundingwas submitted as a finding in ImmuneFi's attackathon. Due to earlier changes in how overpayments are calculated, the "theft" is now a non-issue, but the assertion still was causing problems.Update the rounding to improve accuracy, and calculate the amounts in a few different ways so if one still rounds unexpectedly, one of the others will not. This sounds weird, and it is, but IOU rounding can be weird. "Sometimes you eat the bear, and sometimes the bear eats you."
Type of Change