57 auto const& tx = ctx.
tx;
61 !tx.isFieldPresent(sfCounterparty))
64 JLOG(ctx.
j.
debug()) <<
"BatchTrace[" << parentBatchId <<
"]: "
65 <<
"no Counterparty for inner LoanSet transaction.";
71 if (tx.isFieldPresent(sfCounterpartySignature))
72 return tx.getFieldObject(sfCounterpartySignature);
77 JLOG(ctx.
j.
warn()) <<
"LoanSet transaction must have a CounterpartySignature.";
87 if (
auto const data = tx[~sfData];
90 for (
auto const& field : {&sfLoanServiceFee, &sfLatePaymentFee, &sfClosePaymentFee})
96 auto const p = tx[sfPrincipalRequested];
109 if (!
validNumericRange(tx[~sfOverpaymentInterestRate], kMaxOverpaymentInterestRate))
112 if (
auto const paymentTotal = tx[~sfPaymentTotal]; paymentTotal && *paymentTotal <= 0)
115 auto const paymentInterval = tx[~sfPaymentInterval];
134 if (
auto const brokerID = ctx.
tx[~sfLoanBrokerID]; brokerID && *brokerID == beast::kZero)
219 auto const& tx = ctx.
tx;
227 using timeType =
decltype(sfNextPaymentDueDate)::type::value_type;
230 static_assert(kMaxTime == 4'294'967'295);
240 if (grace > timeAvailable)
242 JLOG(ctx.
j.
warn()) <<
"Grace period exceeds protocol time limit.";
246 if (interval > timeAvailable)
248 JLOG(ctx.
j.
warn()) <<
"Payment interval exceeds protocol time limit.";
252 if (total > timeAvailable)
254 JLOG(ctx.
j.
warn()) <<
"Payment total exceeds protocol time limit.";
258 auto const timeLastPayment = timeAvailable - grace;
260 if (timeLastPayment / interval < total)
262 JLOG(ctx.
j.
warn()) <<
"Last payment due date, or grace period for "
263 "last payment exceeds protocol time limit.";
268 auto const account = tx[sfAccount];
269 auto const brokerID = tx[sfLoanBrokerID];
276 JLOG(ctx.
j.
warn()) <<
"LoanBroker does not exist.";
279 auto const brokerOwner = brokerSle->at(sfOwner);
280 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
281 if (account != brokerOwner && counterparty != brokerOwner)
283 JLOG(ctx.
j.
warn()) <<
"Neither Account nor Counterparty are the owner "
284 "of the LoanBroker.";
287 auto const brokerPseudo = brokerSle->at(sfAccount);
289 auto const borrower = counterparty == brokerOwner ? account : counterparty;
294 JLOG(ctx.
j.
warn()) <<
"Borrower does not exist.";
305 if (vault->at(sfAssetsMaximum) != 0 && vault->at(sfAssetsTotal) >= vault->at(sfAssetsMaximum))
307 JLOG(ctx.
j.
warn()) <<
"Vault at maximum assets limit. Can't add another loan.";
311 Asset const asset = vault->at(sfAsset);
313 auto const vaultPseudo = vault->at(sfAccount);
321 if (
auto const value = tx[field]; value &&
STAmount{asset, *value} != *value)
323 JLOG(ctx.
j.
warn()) << field.f->getName() <<
" (" << *value
324 <<
") can not be represented as a(n) " <<
to_string(asset) <<
".";
335 JLOG(ctx.
j.
warn()) <<
"Vault pseudo-account is frozen.";
344 JLOG(ctx.
j.
warn()) <<
"Broker pseudo-account is frozen.";
354 JLOG(ctx.
j.
warn()) <<
"Borrower account is frozen.";
361 JLOG(ctx.
j.
warn()) <<
"Broker owner account is frozen.";
371 auto const& tx =
ctx_.tx;
374 auto const brokerID = tx[sfLoanBrokerID];
379 auto const brokerOwner = brokerSle->at(sfOwner);
384 auto const vaultSle =
view.peek(keylet ::vault(brokerSle->at(sfVaultID)));
387 auto const vaultPseudo = vaultSle->at(sfAccount);
388 Asset const vaultAsset = vaultSle->at(sfAsset);
390 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
391 auto const borrower = counterparty == brokerOwner ?
accountID_ : counterparty;
398 auto const brokerPseudo = brokerSle->at(sfAccount);
400 if (!brokerPseudoSle)
404 auto const principalRequested = tx[sfPrincipalRequested];
406 auto vaultAvailableProxy = vaultSle->at(sfAssetsAvailable);
407 auto vaultTotalProxy = vaultSle->at(sfAssetsTotal);
409 if (vaultAvailableProxy < principalRequested)
411 JLOG(
j_.warn()) <<
"Insufficient assets available in the Vault to fund the loan.";
415 TenthBips32 const interestRate{tx[~sfInterestRate].value_or(0)};
431 properties.loanState.valueOutstanding,
433 properties.loanState.managementFeeDue);
435 auto const vaultMaximum = *vaultSle->at(sfAssetsMaximum);
437 vaultMaximum == 0 || vaultMaximum > *vaultTotalProxy,
438 "xrpl::LoanSet::doApply",
439 "Vault is below maximum limit");
440 if (vaultMaximum != 0 && state.
interestDue > vaultMaximum - vaultTotalProxy)
442 JLOG(
j_.warn()) <<
"Loan would exceed the maximum assets of the vault";
449 if (
auto const value = tx[field];
450 value && !
isRounded(vaultAsset, *value, properties.loanScale))
452 JLOG(
j_.warn()) << field.f->getName() <<
" (" << *value
453 <<
") has too much precision. Total loan value is "
454 << properties.loanState.valueOutstanding <<
" with a scale of "
455 << properties.loanScale;
463 interestRate != beast::kZero,
470 if (properties.loanState.managementFeeDue < 0 || properties.loanState.valueOutstanding <= 0 ||
471 properties.periodicPayment <= 0)
474 JLOG(
j_.warn()) <<
"Computed loan properties are invalid. Does not compute."
475 <<
" Management fee: " << properties.loanState.managementFeeDue
476 <<
". Total Value: " << properties.loanState.valueOutstanding
477 <<
". PeriodicPayment: " << properties.periodicPayment;
482 auto const originationFee = tx[~sfLoanOriginationFee].value_or(
Number{});
484 auto const loanAssetsToBorrower = principalRequested - originationFee;
486 auto const newDebtDelta = principalRequested + state.
interestDue;
487 auto const newDebtTotal = brokerSle->at(sfDebtTotal) + newDebtDelta;
488 if (
auto const debtMaximum = brokerSle->at(sfDebtMaximum);
489 debtMaximum != 0 && debtMaximum < newDebtTotal)
491 JLOG(
j_.warn()) <<
"Loan would exceed the maximum debt limit of the LoanBroker.";
494 TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
496 auto const minCover = [&]() {
497 if (
ctx_.view().rules().enabled(fixCleanup3_2_0))
508 if (brokerSle->at(sfCoverAvailable) < minCover)
510 JLOG(
j_.warn()) <<
"Insufficient first-loss capital to cover the loan.";
517 auto const ownerCount = borrowerSle->at(sfOwnerCount);
520 if (balance <
view.fees().accountReserve(ownerCount))
531 borrower ==
accountID_ || borrower == counterparty,
532 "xrpl::LoanSet::doApply",
533 "borrower signed transaction");
535 view, borrower, borrowerSle->at(sfBalance).value().xrp(), vaultAsset,
j_);
548 if (originationFee != beast::kZero)
553 brokerOwner ==
accountID_ || brokerOwner == counterparty,
554 "xrpl::LoanSet::doApply",
555 "broker owner signed transaction");
558 view, brokerOwner, brokerOwnerSle->at(sfBalance).value().xrp(), vaultAsset,
j_);
574 {{borrower, loanAssetsToBorrower}, {brokerOwner, originationFee}},
581 auto loanSequenceProxy = brokerSle->at(sfLoanSequence);
587 auto setLoanField = [&loan, &tx](
auto const& field,
std::uint32_t const defValue = 0) {
590 loan->at(field) = tx[field].value_or(defValue);
594 loan->at(sfLoanScale) = properties.loanScale;
595 loan->at(sfStartDate) = startDate;
596 loan->at(sfPaymentInterval) = paymentInterval;
597 loan->at(sfLoanSequence) = *loanSequenceProxy;
598 loan->at(sfLoanBrokerID) = brokerID;
599 loan->at(sfBorrower) = borrower;
601 if (tx.isFlag(tfLoanOverpayment))
602 loan->setFlag(lsfLoanOverpayment);
603 setLoanField(~sfLoanOriginationFee);
604 setLoanField(~sfLoanServiceFee);
605 setLoanField(~sfLatePaymentFee);
606 setLoanField(~sfClosePaymentFee);
607 setLoanField(~sfOverpaymentFee);
608 setLoanField(~sfInterestRate);
609 setLoanField(~sfLateInterestRate);
610 setLoanField(~sfCloseInterestRate);
611 setLoanField(~sfOverpaymentInterestRate);
614 loan->at(sfPrincipalOutstanding) = principalRequested;
615 loan->at(sfPeriodicPayment) = properties.periodicPayment;
616 loan->at(sfTotalValueOutstanding) = properties.loanState.valueOutstanding;
617 loan->at(sfManagementFeeOutstanding) = properties.loanState.managementFeeDue;
618 loan->at(sfPreviousPaymentDueDate) = 0;
619 loan->at(sfNextPaymentDueDate) = startDate + paymentInterval;
620 loan->at(sfPaymentRemaining) = paymentTotal;
624 vaultAvailableProxy -= principalRequested;
627 *vaultAvailableProxy <= *vaultTotalProxy,
628 "xrpl::LoanSet::doApply",
629 "assets available must not be greater than assets outstanding");
630 view.update(vaultSle);
637 loanSequenceProxy += 1;
640 if (loanSequenceProxy == 0)
642 view.update(brokerSle);
645 if (
auto const ter =
dirLink(
view, brokerPseudo, loan, sfLoanBrokerNode))
648 if (
auto const ter =
dirLink(
view, borrower, loan, sfOwnerNode))