284 auto const& tx =
ctx_.tx;
287 auto const amount = tx[sfAmount];
289 auto const loanID = tx[sfLoanID];
293 std::int32_t const loanScale = loanSle->at(sfLoanScale);
295 auto const brokerID = loanSle->at(sfLoanBrokerID);
299 auto const brokerOwner = brokerSle->at(sfOwner);
300 auto const brokerPseudoAccount = brokerSle->at(sfAccount);
301 auto const vaultID = brokerSle->at(sfVaultID);
305 auto const vaultPseudoAccount = vaultSle->at(sfAccount);
306 auto const asset = *vaultSle->at(sfAsset);
309 auto coverAvailableProxy = brokerSle->at(sfCoverAvailable);
310 TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
311 auto debtTotalProxy = brokerSle->at(sfDebtTotal);
323 bool const sendBrokerFeeToOwner = [&]() {
327 auto const minCover = [&]() {
328 if (
view.rules().enabled(fixCleanup3_2_0))
337 asset,
tenthBipsOfValue(debtTotalProxy.value(), coverRateMinimum), loanScale);
339 return coverAvailableProxy >= minCover && !
isDeepFrozen(
view, brokerOwner, asset) &&
343 auto const brokerPayee = sendBrokerFeeToOwner ? brokerOwner : brokerPseudoAccount;
345 if (!sendBrokerFeeToOwner)
351 JLOG(
j_.warn()) <<
"Both Loan Broker and Loan Broker pseudo-account "
352 "can not receive funds (deep frozen).";
363 if (loanSle->isFlag(lsfLoanImpaired))
367 JLOG(
j_.fatal()) <<
"Failed to unimpair loan before payment.";
374 if (tx.isFlag(tfLoanLatePayment))
376 if (tx.isFlag(tfLoanFullPayment))
378 if (tx.isFlag(tfLoanOverpayment))
383 std::expected<LoanPaymentParts, TER>
const paymentParts =
389 paymentParts.error(),
"xrpl::LoanPay::doApply",
"payment error is an error");
390 return paymentParts.error();
395 view.update(loanSle);
399 paymentParts->principalPaid >= 0,
400 "xrpl::LoanPay::doApply",
401 "valid principal paid");
404 paymentParts->interestPaid >= 0,
405 "xrpl::LoanPay::doApply",
406 "valid interest paid");
409 paymentParts->principalPaid + paymentParts->interestPaid > 0,
410 "xrpl::LoanPay::doApply",
412 XRPL_ASSERT_PARTS(paymentParts->feePaid >= 0,
"xrpl::LoanPay::doApply",
"valid fee paid");
414 if (paymentParts->principalPaid < 0 || paymentParts->interestPaid < 0 ||
415 paymentParts->feePaid < 0)
418 JLOG(
j_.fatal()) <<
"Loan payment computation returned invalid values.";
423 auto const [assetsTotalDelta, debtTotalDelta] =
loanPaymentDeltas(vaultSle, *paymentParts);
425 JLOG(
j_.debug()) <<
"Loan Pay: principal paid: " << paymentParts->principalPaid
426 <<
", interest paid: " << paymentParts->interestPaid
427 <<
", fee paid: " << paymentParts->feePaid
428 <<
", assets total delta: " << assetsTotalDelta
429 <<
", debt total delta: " << debtTotalDelta;
433 view.update(brokerSle);
435 auto assetsAvailableProxy = vaultSle->at(sfAssetsAvailable);
436 auto assetsTotalProxy = vaultSle->at(sfAssetsTotal);
438 auto const totalPaidToVaultRaw = paymentParts->principalPaid + paymentParts->interestPaid;
439 auto const totalPaidToVaultRounded =
442 !asset.integral() || totalPaidToVaultRaw == totalPaidToVaultRounded,
443 "xrpl::LoanPay::doApply",
444 "rounding does nothing for integral asset");
445 auto const totalPaidToBroker = paymentParts->feePaid;
448 (totalPaidToVaultRaw + totalPaidToBroker) ==
449 (paymentParts->principalPaid + paymentParts->interestPaid + paymentParts->feePaid),
450 "xrpl::LoanPay::doApply",
457 isRounded(asset, debtTotalDelta, loanScale),
458 "xrpl::LoanPay::doApply",
459 "debtTotalDelta rounding good");
467 view.update(vaultSle);
469 Number const assetsAvailableBefore = *assetsAvailableProxy;
470 Number const assetsTotalBefore = *assetsTotalProxy;
482 assetsAvailableBefore == pseudoAccountBalanceBefore,
483 "xrpl::LoanPay::doApply",
484 "vault pseudo balance agrees before");
488 assetsAvailableProxy += totalPaidToVaultRounded;
489 assetsTotalProxy += assetsTotalDelta;
492 *assetsAvailableProxy <= *assetsTotalProxy,
493 "xrpl::LoanPay::doApply",
494 "assets available must not be greater than assets outstanding");
496 JLOG(
j_.debug()) <<
"total paid to vault raw: " << totalPaidToVaultRaw
497 <<
", total paid to vault rounded: " << totalPaidToVaultRounded
498 <<
", total paid to broker: " << totalPaidToBroker
499 <<
", amount from transaction: " << amount;
503 totalPaidToVaultRounded + totalPaidToBroker <= amount,
504 "xrpl::LoanPay::doApply",
505 "amount is sufficient");
507 if (!sendBrokerFeeToOwner)
513 coverAvailableProxy += totalPaidToBroker;
521 Number const assetsAvailableAfter = *assetsAvailableProxy;
522 Number const assetsTotalAfter = *assetsTotalProxy;
525 assetsAvailableAfter <= assetsTotalAfter,
526 "xrpl::LoanPay::doApply",
527 "assets available must not be greater than assets outstanding");
528 if (assetsAvailableAfter == assetsAvailableBefore)
536 JLOG(
j_.warn()) <<
"LoanPay: Vault assets available unchanged after rounding: "
537 <<
"Before: " << assetsAvailableBefore
538 <<
", After: " << assetsAvailableAfter;
542 if (assetsTotalDelta !=
beast::kZero && assetsTotalAfter == assetsTotalBefore)
551 <<
"LoanPay: Vault assets expected change, but unchanged after rounding: "
552 <<
"Before: " << assetsTotalBefore
553 <<
", After: " << assetsTotalAfter
554 <<
", AssetsTotalDelta: " << assetsTotalDelta;
558 if (assetsTotalDelta ==
beast::kZero && assetsTotalAfter != assetsTotalBefore)
565 JLOG(
j_.fatal()) <<
"LoanPay: Vault assets changed unexpectedly after rounding: "
566 <<
"Before: " << assetsTotalBefore
567 <<
", After: " << assetsTotalAfter
568 <<
", AssetsTotalDelta: " << assetsTotalDelta;
572 if (assetsAvailableAfter > assetsTotalAfter)
576 JLOG(
j_.fatal()) <<
"LoanPay: Vault assets available must not be greater "
577 "than assets outstanding. Available: "
578 << assetsAvailableAfter <<
", Total: " << assetsTotalAfter;
592 auto const vaultBalanceBefore =
accountID_ == vaultPseudoAccount
602 auto const brokerBalanceBefore =
accountID_ == brokerPayee
625 ctx_.getApplyViewContext(),
627 brokerPayeeSle->at(sfBalance).value().xrp(),
645 {{vaultPseudoAccount, totalPaidToVaultRounded}, {brokerPayee, totalPaidToBroker}},
660 assetsAvailableAfter == pseudoAccountBalanceAfter,
661 "xrpl::LoanPay::doApply",
662 "vault pseudo balance agrees after");
675 auto const vaultBalanceAfter =
accountID_ == vaultPseudoAccount
694 auto const balanceScale = [&]() {
704 for (
auto const& a : {
705 accountBalanceBefore,
717 if (exponents.
empty())
719 UNREACHABLE(
"xrpl::LoanPay::doApply : all zeroes");
723 auto const min = *minItr;
724 auto const max = *maxItr;
725 JLOG(
j_.trace()) <<
"Min scale: " << min <<
", max scale: " << max;
736 "xrpl::LoanPay::doApply",
737 "Number rounding ToNearest");
740 auto const accountBalanceBeforeRounded =
roundToScale(accountBalanceBefore, balanceScale);
741 auto const vaultBalanceBeforeRounded =
roundToScale(vaultBalanceBefore, balanceScale);
742 auto const brokerBalanceBeforeRounded =
roundToScale(brokerBalanceBefore, balanceScale);
744 auto const totalBalanceBefore = accountBalanceBefore + vaultBalanceBefore + brokerBalanceBefore;
745 auto const totalBalanceBeforeRounded =
roundToScale(totalBalanceBefore, balanceScale);
747 JLOG(
j_.trace()) <<
"Before: "
748 <<
"account " <<
Number(accountBalanceBeforeRounded) <<
" ("
749 <<
Number(accountBalanceBefore) <<
")"
750 <<
", vault " <<
Number(vaultBalanceBeforeRounded) <<
" ("
751 <<
Number(vaultBalanceBefore) <<
")"
752 <<
", broker " <<
Number(brokerBalanceBeforeRounded) <<
" ("
753 <<
Number(brokerBalanceBefore) <<
")"
754 <<
", total " <<
Number(totalBalanceBeforeRounded) <<
" ("
755 <<
Number(totalBalanceBefore) <<
")";
757 auto const accountBalanceAfterRounded =
roundToScale(accountBalanceAfter, balanceScale);
758 auto const vaultBalanceAfterRounded =
roundToScale(vaultBalanceAfter, balanceScale);
759 auto const brokerBalanceAfterRounded =
roundToScale(brokerBalanceAfter, balanceScale);
761 auto const totalBalanceAfter = accountBalanceAfter + vaultBalanceAfter + brokerBalanceAfter;
762 auto const totalBalanceAfterRounded =
roundToScale(totalBalanceAfter, balanceScale);
764 JLOG(
j_.trace()) <<
"After: "
765 <<
"account " <<
Number(accountBalanceAfterRounded) <<
" ("
766 <<
Number(accountBalanceAfter) <<
")"
767 <<
", vault " <<
Number(vaultBalanceAfterRounded) <<
" ("
768 <<
Number(vaultBalanceAfter) <<
")"
769 <<
", broker " <<
Number(brokerBalanceAfterRounded) <<
" ("
770 <<
Number(brokerBalanceAfter) <<
")"
771 <<
", total " <<
Number(totalBalanceAfterRounded) <<
" ("
772 <<
Number(totalBalanceAfter) <<
")";
774 auto const accountBalanceChange = accountBalanceAfter - accountBalanceBefore;
775 auto const vaultBalanceChange = vaultBalanceAfter - vaultBalanceBefore;
776 auto const brokerBalanceChange = brokerBalanceAfter - brokerBalanceBefore;
778 auto const totalBalanceChange = accountBalanceChange + vaultBalanceChange + brokerBalanceChange;
779 auto const totalBalanceChangeRounded =
roundToScale(totalBalanceChange, balanceScale);
781 JLOG(
j_.trace()) <<
"Changes: "
782 <<
"account " <<
to_string(accountBalanceChange)
783 <<
", vault " <<
to_string(vaultBalanceChange)
784 <<
", broker " <<
to_string(brokerBalanceChange)
785 <<
", total " <<
to_string(totalBalanceChangeRounded) <<
" ("
786 <<
Number(totalBalanceChange) <<
")";
788 bool const goodRounding = totalBalanceBeforeRounded == totalBalanceAfterRounded ||
790 if (totalBalanceBeforeRounded != totalBalanceAfterRounded)
792 JLOG((goodRounding ?
j_.debug() :
j_.warn()))
793 <<
"Total rounded balances don't match"
794 << (totalBalanceChangeRounded ==
beast::kZero ?
", but total changes do" :
"");
798 JLOG((goodRounding ?
j_.debug() :
j_.warn()))
799 <<
"Total balance changes don't match"
800 << (totalBalanceBeforeRounded == totalBalanceAfterRounded ?
", but total balances do"
807 goodRounding,
"xrpl::LoanPay::doApply",
"funds are conserved (with rounding)");
810 accountBalanceAfter < accountBalanceBefore ||
accountID_ == asset.getIssuer(),
811 "xrpl::LoanPay::doApply",
812 "account balance decreased");
815 "xrpl::LoanPay::doApply",
816 "non-negative vault and broker balances");
818 vaultBalanceAfter >= vaultBalanceBefore,
819 "xrpl::LoanPay::doApply",
820 "vault balance did not decrease");
822 brokerBalanceAfter >= brokerBalanceBefore,
823 "xrpl::LoanPay::doApply",
824 "broker balance did not decrease");
826 vaultBalanceAfter > vaultBalanceBefore || brokerBalanceAfter > brokerBalanceBefore,
827 "xrpl::LoanPay::doApply",
828 "vault and/or broker balance increased");