fix: Use cash-basis-aware debt delta for LoanSet DebtMaximum guard

Remove unused vaultMaximum local left over from the AssetsMaximum
guard refactor, and stop the DebtMaximum guard from unconditionally
adding interestDue to the projected DebtTotal. Reuse the existing
loanOriginationDeltas dispatcher, which already excludes interestDue
under cash-basis accounting.
This commit is contained in:
Vito
2026-07-24 11:55:58 +02:00
parent b9b3aedc88
commit 20358587ce
2 changed files with 13 additions and 12 deletions

View File

@@ -439,9 +439,8 @@ LoanSet::doApply()
principalRequested,
properties.loanState.managementFeeDue);
auto const vaultMaximum = *vaultSle->at(sfAssetsMaximum);
XRPL_ASSERT_PARTS(
vaultMaximum == 0 || vaultMaximum > *vaultTotalProxy,
*vaultSle->at(sfAssetsMaximum) == 0 || *vaultSle->at(sfAssetsMaximum) > *vaultTotalProxy,
"xrpl::LoanSet::doApply",
"Vault is below maximum limit");
@@ -492,8 +491,9 @@ LoanSet::doApply()
auto const loanAssetsToBorrower = principalRequested - originationFee;
auto const newDebtDelta = principalRequested + state.interestDue;
auto const newDebtTotal = brokerSle->at(sfDebtTotal) + newDebtDelta;
auto const [assetsTotalDelta, debtTotalDelta] =
loanOriginationDeltas(ctx_.view().rules(), vaultSle, principalRequested, state.interestDue);
auto const newDebtTotal = brokerSle->at(sfDebtTotal) + debtTotalDelta;
if (auto const debtMaximum = brokerSle->at(sfDebtMaximum);
debtMaximum != 0 && debtMaximum < newDebtTotal)
{
@@ -635,8 +635,6 @@ LoanSet::doApply()
view.insert(loan);
// Update the balances in the vault
auto const [assetsTotalDelta, debtTotalDelta] =
loanOriginationDeltas(ctx_.view().rules(), vaultSle, principalRequested, state.interestDue);
vaultAvailableProxy -= principalRequested;
vaultTotalProxy += assetsTotalDelta;
XRPL_ASSERT_PARTS(

View File

@@ -8684,8 +8684,7 @@ protected:
}
// AssetsMaximum guard checks interestDue headroom only under
// whole-life accounting (cash-basis never adds interest to
// AssetsTotal). DebtMaximum guard is unconditional either way.
// whole-life accounting; DebtMaximum guard also varies by model.
auto runVaultGuard = [&](FeatureBitset features, Number const& slack, TER expected) {
Env env(*this, features);
@@ -8761,12 +8760,16 @@ protected:
runVaultGuard(all_ | featureLendingProtocolV1_1, oneDrop, tesSUCCESS);
}
for (auto const features : {all_ | featureLendingProtocolV1_1, all_})
// DebtMaximum guard: cash-basis projects principal-only DebtTotal;
// whole-life projects principal + interestDue.
for (auto const cashBasis : {true, false})
{
testcase(
std::string("cash-basis: LoanSet DebtMaximum guard unaffected by amendment (") +
(features[featureLendingProtocolV1_1] ? "enabled)" : "disabled)"));
Number const newDebtTotal = principalOutstandingCash + interestDueCash;
std::string("LoanSet DebtMaximum guard (") +
(cashBasis ? "cash-basis)" : "whole-life)"));
auto const features = cashBasis ? all_ | featureLendingProtocolV1_1 : all_;
Number const newDebtTotal =
principalOutstandingCash + (cashBasis ? Number{} : interestDueCash);
runBrokerGuard(features, newDebtTotal - oneDrop, tecLIMIT_EXCEEDED);
runBrokerGuard(features, newDebtTotal, tesSUCCESS);
}