diff --git a/include/xrpl/ledger/helpers/LendingHelpers.h b/include/xrpl/ledger/helpers/LendingHelpers.h index 5503fe5b97..fef18e3e09 100644 --- a/include/xrpl/ledger/helpers/LendingHelpers.h +++ b/include/xrpl/ledger/helpers/LendingHelpers.h @@ -341,23 +341,21 @@ loanPaymentDeltas(LoanPaymentParts const& parts); // transactors call. AccountingDeltas loanOriginationDeltas( - Rules const& rules, SLE::const_ref vaultSle, Number const& principalRequested, Number const& interestDue); bool loanOriginationExceedsVaultMaximum( - Rules const& rules, SLE::const_ref vaultSle, Number const& vaultTotal, Number const& interestDue); Number -loanVaultExposure(Rules const& rules, SLE::const_ref vaultSle, SLE::const_ref loanSle); +loanVaultExposure(SLE::const_ref vaultSle, SLE::const_ref loanSle); AccountingDeltas -loanPaymentDeltas(Rules const& rules, SLE::const_ref vaultSle, LoanPaymentParts const& parts); +loanPaymentDeltas(SLE::const_ref vaultSle, LoanPaymentParts const& parts); namespace detail { // These classes and functions should only be accessed by LendingHelper diff --git a/src/libxrpl/ledger/helpers/LendingHelpers.cpp b/src/libxrpl/ledger/helpers/LendingHelpers.cpp index 4259b8eee9..dac2c67181 100644 --- a/src/libxrpl/ledger/helpers/LendingHelpers.cpp +++ b/src/libxrpl/ledger/helpers/LendingHelpers.cpp @@ -148,6 +148,13 @@ loanOriginationExceedsVaultMaximum( return vaultMaximum != 0 && interestDue > vaultMaximum - vaultTotal; } +/* +XLS-66 section 3.2.3.2, defines the default amount as + +DefaultAmount = (Loan.PrincipalOutstanding + Loan.InterestOutstanding) + +Which is equivalent to (Loan.TotalValueOutstanding - Loan.ManagementFeeOutstanding) +*/ Number loanVaultExposure(SLE::const_ref loanSle) { @@ -172,6 +179,11 @@ loanOriginationDeltas(Number const& principalRequested) return {.assetsTotalDelta = kNumZero, .debtTotalDelta = principalRequested}; } +/* + * Under CashBasis accounting, Loan default amount is: + * + * DefaultAmount = Loan.PrincipalOutstanding + */ Number loanVaultExposure(SLE::const_ref loanSle) { @@ -193,36 +205,33 @@ namespace { // VaultVersion::CashBasis). Vaults created before activation keep accrual-basis // accounting forever, even after the amendment later turns on. bool -cashBasisEnabled(Rules const& rules, SLE::const_ref vaultSle) +cashBasisEnabled(SLE::const_ref vaultSle) { - return rules.enabled(featureLendingProtocolV1_1) && - getVaultVersion(vaultSle) == VaultVersion::CashBasis; + return getVaultVersion(vaultSle) == VaultVersion::CashBasis; } } // namespace AccountingDeltas loanOriginationDeltas( - Rules const& rules, SLE::const_ref vaultSle, Number const& principalRequested, Number const& interestDue) { - return cashBasisEnabled(rules, vaultSle) + return cashBasisEnabled(vaultSle) ? CashBasis::loanOriginationDeltas(principalRequested) : Accrual::loanOriginationDeltas(principalRequested, interestDue); } bool loanOriginationExceedsVaultMaximum( - Rules const& rules, SLE::const_ref vaultSle, Number const& vaultTotal, Number const& interestDue) { // Cash-basis origination doesn't recognize interest into AssetsTotal, so // interest due can never push the vault past AssetsMaximum at origination. - if (cashBasisEnabled(rules, vaultSle)) + if (cashBasisEnabled(vaultSle)) return false; auto const vaultMaximum = vaultSle->at(sfAssetsMaximum); @@ -230,17 +239,17 @@ loanOriginationExceedsVaultMaximum( } Number -loanVaultExposure(Rules const& rules, SLE::const_ref vaultSle, SLE::const_ref loanSle) +loanVaultExposure(SLE::const_ref vaultSle, SLE::const_ref loanSle) { - return cashBasisEnabled(rules, vaultSle) ? CashBasis::loanVaultExposure(loanSle) - : Accrual::loanVaultExposure(loanSle); + return cashBasisEnabled(vaultSle) ? CashBasis::loanVaultExposure(loanSle) + : Accrual::loanVaultExposure(loanSle); } AccountingDeltas -loanPaymentDeltas(Rules const& rules, SLE::const_ref vaultSle, LoanPaymentParts const& parts) +loanPaymentDeltas(SLE::const_ref vaultSle, LoanPaymentParts const& parts) { - return cashBasisEnabled(rules, vaultSle) ? CashBasis::loanPaymentDeltas(parts) - : Accrual::loanPaymentDeltas(parts); + return cashBasisEnabled(vaultSle) ? CashBasis::loanPaymentDeltas(parts) + : Accrual::loanPaymentDeltas(parts); } namespace detail { diff --git a/src/libxrpl/tx/transactors/lending/LoanManage.cpp b/src/libxrpl/tx/transactors/lending/LoanManage.cpp index 5c71843129..a312dba3b3 100644 --- a/src/libxrpl/tx/transactors/lending/LoanManage.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanManage.cpp @@ -141,7 +141,7 @@ LoanManage::defaultLoan( std::int32_t const loanScale = loanSle->at(sfLoanScale); auto brokerDebtTotalProxy = brokerSle->at(sfDebtTotal); - Number const totalDefaultAmount = loanVaultExposure(view.rules(), vaultSle, loanSle); + Number const totalDefaultAmount = loanVaultExposure(vaultSle, loanSle); // Apply the First-Loss Capital to the Default Amount TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)}; @@ -287,7 +287,7 @@ LoanManage::impairLoan( Asset const& vaultAsset, beast::Journal j) { - Number const lossUnrealized = loanVaultExposure(view.rules(), vaultSle, loanSle); + Number const lossUnrealized = loanVaultExposure(vaultSle, loanSle); // The vault may be at a different scale than the loan. Reduce rounding // errors during the accounting by rounding some of the values to that @@ -336,7 +336,7 @@ LoanManage::unimpairLoan( // Update the Vault object(clear "paper loss") auto vaultLossUnrealizedProxy = vaultSle->at(sfLossUnrealized); - Number const lossReversed = loanVaultExposure(view.rules(), vaultSle, loanSle); + Number const lossReversed = loanVaultExposure(vaultSle, loanSle); if (vaultLossUnrealizedProxy < lossReversed) { // LCOV_EXCL_START diff --git a/src/libxrpl/tx/transactors/lending/LoanPay.cpp b/src/libxrpl/tx/transactors/lending/LoanPay.cpp index 5af6107eda..0053ed496e 100644 --- a/src/libxrpl/tx/transactors/lending/LoanPay.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanPay.cpp @@ -420,8 +420,7 @@ LoanPay::doApply() // LCOV_EXCL_STOP } - auto const [assetsTotalDelta, debtTotalDelta] = - loanPaymentDeltas(view.rules(), vaultSle, *paymentParts); + auto const [assetsTotalDelta, debtTotalDelta] = loanPaymentDeltas(vaultSle, *paymentParts); JLOG(j_.debug()) << "Loan Pay: principal paid: " << paymentParts->principalPaid << ", interest paid: " << paymentParts->interestPaid diff --git a/src/libxrpl/tx/transactors/lending/LoanSet.cpp b/src/libxrpl/tx/transactors/lending/LoanSet.cpp index a4575e40b0..bafadd7c1d 100644 --- a/src/libxrpl/tx/transactors/lending/LoanSet.cpp +++ b/src/libxrpl/tx/transactors/lending/LoanSet.cpp @@ -444,8 +444,7 @@ LoanSet::doApply() "xrpl::LoanSet::doApply", "Vault is below maximum limit"); - if (loanOriginationExceedsVaultMaximum( - ctx_.view().rules(), vaultSle, vaultTotalProxy, state.interestDue)) + if (loanOriginationExceedsVaultMaximum(vaultSle, vaultTotalProxy, state.interestDue)) { JLOG(j_.warn()) << "Loan would exceed the maximum assets of the vault"; return tecLIMIT_EXCEEDED; @@ -492,7 +491,7 @@ LoanSet::doApply() auto const loanAssetsToBorrower = principalRequested - originationFee; auto const [assetsTotalDelta, debtTotalDelta] = - loanOriginationDeltas(ctx_.view().rules(), vaultSle, principalRequested, state.interestDue); + loanOriginationDeltas(vaultSle, principalRequested, state.interestDue); auto const newDebtTotal = brokerSle->at(sfDebtTotal) + debtTotalDelta; if (auto const debtMaximum = brokerSle->at(sfDebtMaximum); debtMaximum != 0 && debtMaximum < newDebtTotal) diff --git a/src/test/app/LendingHelpers_test.cpp b/src/test/app/LendingHelpers_test.cpp index 82f455ce6b..94f5813bd8 100644 --- a/src/test/app/LendingHelpers_test.cpp +++ b/src/test/app/LendingHelpers_test.cpp @@ -1672,8 +1672,8 @@ class LendingHelpers_test : public beast::unit_test::Suite testcase("loanOriginationDeltas dispatcher: amendment disabled picks Accrual"); Env env{*this}; env.disableFeature(featureLendingProtocolV1_1); - auto const deltas = loanOriginationDeltas( - env.current()->rules(), cashBasisVault, principalRequested, interestDue); + auto const deltas = + loanOriginationDeltas(cashBasisVault, principalRequested, interestDue); auto const expected = xrpl::Accrual::loanOriginationDeltas(principalRequested, interestDue); BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta); @@ -1685,8 +1685,7 @@ class LendingHelpers_test : public beast::unit_test::Suite "loanOriginationDeltas dispatcher: amendment enabled, legacy vault picks " "Accrual"); Env const env{*this}; - auto const deltas = loanOriginationDeltas( - env.current()->rules(), legacyVault, principalRequested, interestDue); + auto const deltas = loanOriginationDeltas(legacyVault, principalRequested, interestDue); auto const expected = xrpl::Accrual::loanOriginationDeltas(principalRequested, interestDue); BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta); @@ -1698,8 +1697,8 @@ class LendingHelpers_test : public beast::unit_test::Suite "loanOriginationDeltas dispatcher: amendment enabled, LEVersion == " "VaultVersion::CashBasis picks CashBasis"); Env const env{*this}; - auto const deltas = loanOriginationDeltas( - env.current()->rules(), cashBasisVault, principalRequested, interestDue); + auto const deltas = + loanOriginationDeltas(cashBasisVault, principalRequested, interestDue); auto const expected = xrpl::CashBasis::loanOriginationDeltas(principalRequested); BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta); BEAST_EXPECT(deltas.debtTotalDelta == expected.debtTotalDelta); @@ -1726,8 +1725,7 @@ class LendingHelpers_test : public beast::unit_test::Suite Env env{*this}; env.disableFeature(featureLendingProtocolV1_1); BEAST_EXPECT( - loanOriginationExceedsVaultMaximum( - env.current()->rules(), cashBasisVault, vaultTotal, interestDue) == + loanOriginationExceedsVaultMaximum(cashBasisVault, vaultTotal, interestDue) == xrpl::Accrual::loanOriginationExceedsVaultMaximum( vaultMaximum, vaultTotal, interestDue)); } @@ -1738,8 +1736,7 @@ class LendingHelpers_test : public beast::unit_test::Suite "picks Accrual"); Env const env{*this}; BEAST_EXPECT( - loanOriginationExceedsVaultMaximum( - env.current()->rules(), legacyVault, vaultTotal, interestDue) == + loanOriginationExceedsVaultMaximum(legacyVault, vaultTotal, interestDue) == xrpl::Accrual::loanOriginationExceedsVaultMaximum( vaultMaximum, vaultTotal, interestDue)); } @@ -1750,8 +1747,8 @@ class LendingHelpers_test : public beast::unit_test::Suite "VaultVersion::CashBasis picks CashBasis"); Env const env{*this}; BEAST_EXPECT( - loanOriginationExceedsVaultMaximum( - env.current()->rules(), cashBasisVault, vaultTotal, interestDue) == false); + loanOriginationExceedsVaultMaximum(cashBasisVault, vaultTotal, interestDue) == + false); } } @@ -1769,8 +1766,7 @@ class LendingHelpers_test : public beast::unit_test::Suite env.disableFeature(featureLendingProtocolV1_1); auto sle = makeLoanSle(Number{1'000}, Number{800}, Number{50}); BEAST_EXPECT( - loanVaultExposure(env.current()->rules(), cashBasisVault, sle) == - xrpl::Accrual::loanVaultExposure(sle)); + loanVaultExposure(cashBasisVault, sle) == xrpl::Accrual::loanVaultExposure(sle)); } { @@ -1778,8 +1774,7 @@ class LendingHelpers_test : public beast::unit_test::Suite Env const env{*this}; auto sle = makeLoanSle(Number{1'000}, Number{800}, Number{50}); BEAST_EXPECT( - loanVaultExposure(env.current()->rules(), legacyVault, sle) == - xrpl::Accrual::loanVaultExposure(sle)); + loanVaultExposure(legacyVault, sle) == xrpl::Accrual::loanVaultExposure(sle)); } { @@ -1790,8 +1785,7 @@ class LendingHelpers_test : public beast::unit_test::Suite Env const env{*this}; auto sle = makeLoanSle(Number{1'000}, Number{800}, Number{50}); BEAST_EXPECT( - loanVaultExposure(env.current()->rules(), cashBasisVault, sle) == - xrpl::CashBasis::loanVaultExposure(sle)); + loanVaultExposure(cashBasisVault, sle) == xrpl::CashBasis::loanVaultExposure(sle)); } } @@ -1813,7 +1807,7 @@ class LendingHelpers_test : public beast::unit_test::Suite testcase("loanPaymentDeltas dispatcher: amendment disabled picks Accrual"); Env env{*this}; env.disableFeature(featureLendingProtocolV1_1); - auto const deltas = loanPaymentDeltas(env.current()->rules(), cashBasisVault, parts); + auto const deltas = loanPaymentDeltas(cashBasisVault, parts); auto const expected = xrpl::Accrual::loanPaymentDeltas(parts); BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta); BEAST_EXPECT(deltas.debtTotalDelta == expected.debtTotalDelta); @@ -1822,7 +1816,7 @@ class LendingHelpers_test : public beast::unit_test::Suite { testcase("loanPaymentDeltas dispatcher: amendment enabled, legacy vault picks Accrual"); Env const env{*this}; - auto const deltas = loanPaymentDeltas(env.current()->rules(), legacyVault, parts); + auto const deltas = loanPaymentDeltas(legacyVault, parts); auto const expected = xrpl::Accrual::loanPaymentDeltas(parts); BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta); BEAST_EXPECT(deltas.debtTotalDelta == expected.debtTotalDelta); @@ -1834,7 +1828,7 @@ class LendingHelpers_test : public beast::unit_test::Suite "VaultVersion::CashBasis " "picks CashBasis"); Env const env{*this}; - auto const deltas = loanPaymentDeltas(env.current()->rules(), cashBasisVault, parts); + auto const deltas = loanPaymentDeltas(cashBasisVault, parts); auto const expected = xrpl::CashBasis::loanPaymentDeltas(parts); BEAST_EXPECT(deltas.assetsTotalDelta == expected.assetsTotalDelta); BEAST_EXPECT(deltas.debtTotalDelta == expected.debtTotalDelta);