From 3cc8e5564a51031c682e61b8fc7a4acd16fec3d8 Mon Sep 17 00:00:00 2001 From: Ed Hennis Date: Thu, 2 Oct 2025 22:09:35 -0400 Subject: [PATCH] Start converting LoanManage to handle pre-computed fields - Tests are not expected to pass --- src/xrpld/app/tx/detail/LoanManage.cpp | 76 ++++++++------------------ src/xrpld/app/tx/detail/LoanManage.h | 7 --- src/xrpld/app/tx/detail/LoanSet.cpp | 5 +- 3 files changed, 27 insertions(+), 61 deletions(-) diff --git a/src/xrpld/app/tx/detail/LoanManage.cpp b/src/xrpld/app/tx/detail/LoanManage.cpp index ff83134ac7..5e68616d5b 100644 --- a/src/xrpld/app/tx/detail/LoanManage.cpp +++ b/src/xrpld/app/tx/detail/LoanManage.cpp @@ -125,7 +125,8 @@ LoanManage::preclaim(PreclaimContext const& ctx) if (loanBrokerSle->at(sfOwner) != account) { JLOG(ctx.j.warn()) - << "LoanBroker for Loan does not belong to the account."; + << "LoanBroker for Loan does not belong to the account. LoanModify " + "can only be submitted by the Loan Broker."; return tecNO_PERMISSION; } @@ -138,8 +139,6 @@ LoanManage::defaultLoan( SLE::ref loanSle, SLE::ref brokerSle, SLE::ref vaultSle, - Number const& principalOutstanding, - Number const& interestOutstanding, Asset const& vaultAsset, beast::Journal j) { @@ -148,7 +147,8 @@ LoanManage::defaultLoan( std::int32_t const loanScale = loanSle->at(sfLoanScale); TenthBips32 const managementFeeRate{brokerSle->at(sfManagementFeeRate)}; auto brokerDebtTotalProxy = brokerSle->at(sfDebtTotal); - auto const totalDefaultAmount = principalOutstanding + interestOutstanding; + + Number const totalDefaultAmount = loanSle->at(sfTotalValueOutstanding); // Apply the First-Loss Capital to the Default Amount TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)}; @@ -156,6 +156,11 @@ LoanManage::defaultLoan( brokerSle->at(sfCoverRateLiquidation)}; auto const defaultCovered = roundToAsset( vaultAsset, + /* + * This formula is from the XLS-66 spec, section 3.2.3.2 (State + * Changes), specifically "if the `tfLoanDefault` flag is set" / "Apply + * the First-Loss Capital to the Default Amount" + */ std::min( tenthBipsOfValue( tenthBipsOfValue( @@ -220,14 +225,16 @@ LoanManage::defaultLoan( // The loss has been realized if (loanSle->isFlag(lsfLoanImpaired)) { + Number const lossRealized = loanSle->at(sfPrincipalOutstanding) + + loanSle->at(sfInterestOwed); auto vaultLossUnrealizedProxy = vaultSle->at(sfLossUnrealized); - if (vaultLossUnrealizedProxy < totalDefaultAmount) + if (vaultLossUnrealizedProxy < lossRealized) { JLOG(j.warn()) << "Vault unrealized loss is less than the default amount"; return tefBAD_LEDGER; } - vaultLossUnrealizedProxy -= totalDefaultAmount; + vaultLossUnrealizedProxy -= lossRealized; } view.update(vaultSle); } @@ -281,13 +288,13 @@ LoanManage::impairLoan( ApplyView& view, SLE::ref loanSle, SLE::ref vaultSle, - Number const& principalOutstanding, - Number const& interestOutstanding, beast::Journal j) { + Number const lossUnrealized = + loanSle->at(sfPrincipalOutstanding) + loanSle->at(sfInterestOwed); // Update the Vault object(set "paper loss") auto vaultLossUnrealizedProxy = vaultSle->at(sfLossUnrealized); - vaultLossUnrealizedProxy += principalOutstanding + interestOutstanding; + vaultLossUnrealizedProxy += lossUnrealized; if (vaultLossUnrealizedProxy > vaultSle->at(sfAssetsTotal) - vaultSle->at(sfAssetsAvailable)) { @@ -318,14 +325,12 @@ LoanManage::unimpairLoan( ApplyView& view, SLE::ref loanSle, SLE::ref vaultSle, - Number const& principalOutstanding, - Number const& interestOutstanding, - std::uint32_t paymentInterval, beast::Journal j) { // Update the Vault object(clear "paper loss") auto vaultLossUnrealizedProxy = vaultSle->at(sfLossUnrealized); - auto const lossReversed = principalOutstanding + interestOutstanding; + Number const lossReversed = + loanSle->at(sfPrincipalOutstanding) + loanSle->at(sfInterestOwed); if (vaultLossUnrealizedProxy < lossReversed) { // LCOV_EXCL_START @@ -339,6 +344,7 @@ LoanManage::unimpairLoan( // Update the Loan object loanSle->clearFlag(lsfLoanImpaired); + auto const paymentInterval = loanSle->at(sfPaymentInterval); auto const normalPaymentDueDate = std::max(loanSle->at(sfPreviousPaymentDate), loanSle->at(sfStartDate)) + paymentInterval; @@ -381,58 +387,22 @@ LoanManage::doApply() return tefBAD_LEDGER; // LCOV_EXCL_LINE auto const vaultAsset = vaultSle->at(sfAsset); - TenthBips32 const interestRate{loanSle->at(sfInterestRate)}; - std::int32_t const loanScale = loanSle->at(sfLoanScale); - auto const principalOutstanding = loanSle->at(sfPrincipalOutstanding); - - TenthBips32 const managementFeeRate{brokerSle->at(sfManagementFeeRate)}; - auto const paymentInterval = loanSle->at(sfPaymentInterval); - auto const paymentsRemaining = loanSle->at(sfPaymentRemaining); - auto const interestOutstanding = loanInterestOutstandingMinusFee( - vaultAsset, - loanScale, - principalOutstanding.value(), - interestRate, - paymentInterval, - paymentsRemaining, - managementFeeRate); - // Valid flag combinations are checked in preflight. No flags is valid - // just a noop. if (tx.isFlag(tfLoanDefault)) { - if (auto const ter = defaultLoan( - view, - loanSle, - brokerSle, - vaultSle, - principalOutstanding, - interestOutstanding, - vaultAsset, - j_)) + if (auto const ter = + defaultLoan(view, loanSle, brokerSle, vaultSle, vaultAsset, j_)) return ter; } else if (tx.isFlag(tfLoanImpair)) { - if (auto const ter = impairLoan( - view, - loanSle, - vaultSle, - principalOutstanding, - interestOutstanding, - j_)) + if (auto const ter = impairLoan(view, loanSle, vaultSle, j_)) return ter; } else if (tx.isFlag(tfLoanUnimpair)) { - if (auto const ter = unimpairLoan( - view, - loanSle, - vaultSle, - principalOutstanding, - interestOutstanding, - paymentInterval, - j_)) + if (auto const ter = unimpairLoan(view, loanSle, vaultSle, j_)) return ter; } diff --git a/src/xrpld/app/tx/detail/LoanManage.h b/src/xrpld/app/tx/detail/LoanManage.h index 92c5854115..0a043ca48c 100644 --- a/src/xrpld/app/tx/detail/LoanManage.h +++ b/src/xrpld/app/tx/detail/LoanManage.h @@ -53,8 +53,6 @@ public: SLE::ref loanSle, SLE::ref brokerSle, SLE::ref vaultSle, - Number const& principalOutstanding, - Number const& interestOutstanding, Asset const& vaultAsset, beast::Journal j); @@ -65,8 +63,6 @@ public: ApplyView& view, SLE::ref loanSle, SLE::ref vaultSle, - Number const& principalOutstanding, - Number const& interestOutstanding, beast::Journal j); /** Helper function that might be needed by other transactors @@ -76,9 +72,6 @@ public: ApplyView& view, SLE::ref loanSle, SLE::ref vaultSle, - Number const& principalOutstanding, - Number const& interestOutstanding, - std::uint32_t paymentInterval, beast::Journal j); TER diff --git a/src/xrpld/app/tx/detail/LoanSet.cpp b/src/xrpld/app/tx/detail/LoanSet.cpp index 447eec5967..7be4b9fe21 100644 --- a/src/xrpld/app/tx/detail/LoanSet.cpp +++ b/src/xrpld/app/tx/detail/LoanSet.cpp @@ -347,7 +347,10 @@ LoanSet::doApply() if (auto const value = tx[field]; value && !isRounded(vaultAsset, *value, properties.loanScale)) { - JLOG(j_.warn()) << name << " has too much precision."; + JLOG(j_.warn()) + << name << " has too much precision. Total loan value is " + << properties.totalValueOutstanding << " with a scale of " + << properties.loanScale; return tecPRECISION_LOSS; } }