xrpld
Loading...
Searching...
No Matches
LoanSet.cpp
1#include <xrpl/tx/transactors/lending/LoanSet.h>
2
3#include <xrpl/basics/Log.h>
4#include <xrpl/basics/Number.h>
5#include <xrpl/beast/utility/Zero.h>
6#include <xrpl/beast/utility/instrumentation.h>
7#include <xrpl/core/ServiceRegistry.h>
8#include <xrpl/ledger/View.h>
9#include <xrpl/ledger/helpers/AccountRootHelpers.h>
10#include <xrpl/ledger/helpers/LendingHelpers.h>
11#include <xrpl/ledger/helpers/TokenHelpers.h>
12#include <xrpl/protocol/AccountID.h>
13#include <xrpl/protocol/Asset.h>
14#include <xrpl/protocol/Feature.h>
15#include <xrpl/protocol/Indexes.h>
16#include <xrpl/protocol/LedgerFormats.h>
17#include <xrpl/protocol/Protocol.h>
18#include <xrpl/protocol/SField.h>
19#include <xrpl/protocol/STLedgerEntry.h>
20#include <xrpl/protocol/STNumber.h>
21#include <xrpl/protocol/STObject.h>
22#include <xrpl/protocol/STTakesAsset.h>
23#include <xrpl/protocol/STTx.h>
24#include <xrpl/protocol/TER.h>
25#include <xrpl/protocol/TxFlags.h>
26#include <xrpl/protocol/Units.h>
27#include <xrpl/protocol/XRPAmount.h>
28#include <xrpl/tx/Transactor.h>
29
30#include <cstddef>
31#include <cstdint>
32#include <limits>
33#include <memory>
34#include <optional>
35#include <type_traits>
36#include <vector>
37
38namespace xrpl {
39
40bool
45
48{
49 return tfLoanSetMask;
50}
51
54{
55 using namespace Lending;
56
57 auto const& tx = ctx.tx;
58
59 // Special case for Batch inner transactions
60 if (tx.isFlag(tfInnerBatchTxn) && ctx.rules.enabled(featureBatch) &&
61 !tx.isFieldPresent(sfCounterparty))
62 {
63 auto const parentBatchId = ctx.parentBatchId.value_or(uint256{0});
64 JLOG(ctx.j.debug()) << "BatchTrace[" << parentBatchId << "]: "
65 << "no Counterparty for inner LoanSet transaction.";
66 return temBAD_SIGNER;
67 }
68
69 // These extra hoops are because STObjects cannot be Proxy'd from STObject.
70 auto const counterPartySig = [&tx]() -> std::optional<STObject const> {
71 if (tx.isFieldPresent(sfCounterpartySignature))
72 return tx.getFieldObject(sfCounterpartySignature);
73 return std::nullopt;
74 }();
75 if (!tx.isFlag(tfInnerBatchTxn) && !counterPartySig)
76 {
77 JLOG(ctx.j.warn()) << "LoanSet transaction must have a CounterpartySignature.";
78 return temBAD_SIGNER;
79 }
80
81 if (counterPartySig)
82 {
83 if (auto const ret = xrpl::detail::preflightCheckSigningKey(*counterPartySig, ctx.j))
84 return ret;
85 }
86
87 if (auto const data = tx[~sfData];
88 data && !data->empty() && !validDataLength(tx[~sfData], kMaxDataPayloadLength))
89 return temINVALID;
90 for (auto const& field : {&sfLoanServiceFee, &sfLatePaymentFee, &sfClosePaymentFee})
91 {
92 if (!validNumericMinimum(tx[~*field]))
93 return temINVALID;
94 }
95 // Principal Requested is required
96 auto const p = tx[sfPrincipalRequested];
97 if (p <= 0)
98 return temINVALID;
99 if (!validNumericRange(tx[~sfLoanOriginationFee], p))
100 return temINVALID;
101 if (!validNumericRange(tx[~sfInterestRate], kMaxInterestRate))
102 return temINVALID;
103 if (!validNumericRange(tx[~sfOverpaymentFee], kMaxOverpaymentFee))
104 return temINVALID;
105 if (!validNumericRange(tx[~sfLateInterestRate], kMaxLateInterestRate))
106 return temINVALID;
107 if (!validNumericRange(tx[~sfCloseInterestRate], kMaxCloseInterestRate))
108 return temINVALID;
109 if (!validNumericRange(tx[~sfOverpaymentInterestRate], kMaxOverpaymentInterestRate))
110 return temINVALID;
111
112 if (auto const paymentTotal = tx[~sfPaymentTotal]; paymentTotal && *paymentTotal <= 0)
113 return temINVALID;
114
115 auto const paymentInterval = tx[~sfPaymentInterval];
117 return temINVALID; // Grace period is between min default value and payment interval
118 if (auto const gracePeriod = tx[~sfGracePeriod]; !validNumericRange(
119 gracePeriod,
120 paymentInterval.value_or(LoanSet::kDefaultPaymentInterval),
122 {
123 return temINVALID;
124 }
125
126 // Copied from preflight2
127 if (counterPartySig)
128 {
129 if (auto const ret =
130 xrpl::detail::preflightCheckSimulateKeys(ctx.flags, *counterPartySig, ctx.j))
131 return *ret;
132 }
133
134 if (auto const brokerID = ctx.tx[~sfLoanBrokerID]; brokerID && *brokerID == beast::kZero)
135 return temINVALID;
136
137 return tesSUCCESS;
138}
139
140NotTEC
142{
143 if (auto ret = Transactor::checkSign(ctx))
144 return ret;
145
146 // Counter signer is optional. If it's not specified, it's assumed to be
147 // `LoanBroker.Owner`. Note that we have not checked whether the
148 // loanbroker exists at this point.
149 auto const counterSigner = [&]() -> std::optional<AccountID> {
150 if (auto const c = ctx.tx.at(~sfCounterparty))
151 return c;
152
153 if (auto const broker = ctx.view.read(keylet::loanBroker(ctx.tx[sfLoanBrokerID])))
154 return broker->at(sfOwner);
155 return std::nullopt;
156 }();
157 if (!counterSigner)
158 return temBAD_SIGNER;
159
160 // Counterparty signature is optional. Presence is checked in preflight.
161 if (!ctx.tx.isFieldPresent(sfCounterpartySignature))
162 return tesSUCCESS;
163 auto const counterSig = ctx.tx.getFieldObject(sfCounterpartySignature);
165 ctx.view, ctx.flags, ctx.parentBatchId, *counterSigner, counterSig, ctx.j);
166}
167
170{
171 auto const normalCost = Transactor::calculateBaseFee(view, tx);
172
173 // Compute the additional cost of each signature in the
174 // CounterpartySignature, whether a single signature or a multisignature
175 XRPAmount const baseFee = view.fees().base;
176
177 // Counterparty signature is optional, but getFieldObject will return an
178 // empty object if it's not present.
179 auto const counterSig = tx.getFieldObject(sfCounterpartySignature);
180 // Each signer adds one more baseFee to the minimum required fee
181 // for the transaction. Note that unlike the base class, the single signer
182 // is counted if present. It will only be absent in a batch inner
183 // transaction.
184 std::size_t const signerCount = [&counterSig]() -> int {
185 // Compute defensively.
186 // Assure that "tx" cannot be accessed and cause confusion or miscalculations.
187 if (counterSig.isFieldPresent(sfSigners))
188 return counterSig.getFieldArray(sfSigners).size();
189 return counterSig.isFieldPresent(sfTxnSignature) ? 1 : 0;
190 }();
191
192 return normalCost + (signerCount * baseFee);
193}
194
197{
198 static std::vector<OptionaledField<STNumber>> const kValueFields{
199 ~sfPrincipalRequested,
200 ~sfLoanOriginationFee,
201 ~sfLoanServiceFee,
202 ~sfLatePaymentFee,
203 ~sfClosePaymentFee
204 // Overpayment fee is really a rate. Don't check it here.
205 };
206
207 return kValueFields;
208}
209
210static std::uint32_t
212{
213 return view.header().closeTime.time_since_epoch().count();
214}
215
216TER
218{
219 auto const& tx = ctx.tx;
220
221 {
222 // Check for numeric overflow of the schedule before we load any
223 // objects. The Grace Period for the last payment ends at:
224 // startDate + (paymentInterval * paymentTotal) + gracePeriod.
225 // If that value is larger than "maxTime", the value
226 // overflows, and we kill the transaction.
227 using timeType = decltype(sfNextPaymentDueDate)::type::value_type;
229 constexpr timeType kMaxTime = std::numeric_limits<timeType>::max();
230 static_assert(kMaxTime == 4'294'967'295);
231
232 auto const timeAvailable = kMaxTime - getStartDate(ctx.view);
233
234 auto const interval = ctx.tx.at(~sfPaymentInterval).value_or(kDefaultPaymentInterval);
235 auto const total = ctx.tx.at(~sfPaymentTotal).value_or(kDefaultPaymentTotal);
236 auto const grace = ctx.tx.at(~sfGracePeriod).value_or(kDefaultGracePeriod);
237
238 // The grace period can't be larger than the interval. Check it first,
239 // mostly so that unit tests can test that specific case.
240 if (grace > timeAvailable)
241 {
242 JLOG(ctx.j.warn()) << "Grace period exceeds protocol time limit.";
243 return tecKILLED;
244 }
245
246 if (interval > timeAvailable)
247 {
248 JLOG(ctx.j.warn()) << "Payment interval exceeds protocol time limit.";
249 return tecKILLED;
250 }
251
252 if (total > timeAvailable)
253 {
254 JLOG(ctx.j.warn()) << "Payment total exceeds protocol time limit.";
255 return tecKILLED;
256 }
257
258 auto const timeLastPayment = timeAvailable - grace;
259
260 if (timeLastPayment / interval < total)
261 {
262 JLOG(ctx.j.warn()) << "Last payment due date, or grace period for "
263 "last payment exceeds protocol time limit.";
264 return tecKILLED;
265 }
266 }
267
268 auto const account = tx[sfAccount];
269 auto const brokerID = tx[sfLoanBrokerID];
270
271 auto const brokerSle = ctx.view.read(keylet::loanBroker(brokerID));
272 if (!brokerSle)
273 {
274 // This can only be hit if there's a counterparty specified, otherwise
275 // it'll fail in the signature check
276 JLOG(ctx.j.warn()) << "LoanBroker does not exist.";
277 return tecNO_ENTRY;
278 }
279 auto const brokerOwner = brokerSle->at(sfOwner);
280 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
281 if (account != brokerOwner && counterparty != brokerOwner)
282 {
283 JLOG(ctx.j.warn()) << "Neither Account nor Counterparty are the owner "
284 "of the LoanBroker.";
285 return tecNO_PERMISSION;
286 }
287 auto const brokerPseudo = brokerSle->at(sfAccount);
288
289 auto const borrower = counterparty == brokerOwner ? account : counterparty;
290 if (auto const borrowerSle = ctx.view.read(keylet::account(borrower)); !borrowerSle)
291 {
292 // It may not be possible to hit this case, because it'll fail the
293 // signature check with terNO_ACCOUNT.
294 JLOG(ctx.j.warn()) << "Borrower does not exist.";
295 return terNO_ACCOUNT;
296 }
297
298 auto const vault = ctx.view.read(keylet::vault(brokerSle->at(sfVaultID)));
299 if (!vault)
300 {
301 // Should be impossible
302 return tefBAD_LEDGER; // LCOV_EXCL_LINE
303 }
304
305 if (vault->at(sfAssetsMaximum) != 0 && vault->at(sfAssetsTotal) >= vault->at(sfAssetsMaximum))
306 {
307 JLOG(ctx.j.warn()) << "Vault at maximum assets limit. Can't add another loan.";
308 return tecLIMIT_EXCEEDED;
309 }
310
311 Asset const asset = vault->at(sfAsset);
312
313 auto const vaultPseudo = vault->at(sfAccount);
314
315 // Check that relevant values can be represented as the vault asset type.
316 // This check is almost duplicated in doApply, but that check is done after
317 // the overall loan scale is known. This is mostly only relevant for
318 // integral (non-IOU) types
319 for (auto const& field : getValueFields())
320 {
321 if (auto const value = tx[field]; value && STAmount{asset, *value} != *value)
322 {
323 JLOG(ctx.j.warn()) << field.f->getName() << " (" << *value
324 << ") can not be represented as a(n) " << to_string(asset) << ".";
325 return tecPRECISION_LOSS;
326 }
327 }
328
329 if (auto const ter = canAddHolding(ctx.view, asset))
330 return ter;
331
332 // vaultPseudo is going to send funds, so it can't be frozen.
333 if (auto const ret = checkFrozen(ctx.view, vaultPseudo, asset))
334 {
335 JLOG(ctx.j.warn()) << "Vault pseudo-account is frozen.";
336 return ret;
337 }
338
339 // brokerPseudo is the fallback account to receive LoanPay fees, even if the
340 // broker owner is unable to accept them. Don't create the loan if it is
341 // deep frozen.
342 if (auto const ret = checkDeepFrozen(ctx.view, brokerPseudo, asset))
343 {
344 JLOG(ctx.j.warn()) << "Broker pseudo-account is frozen.";
345 return ret;
346 }
347
348 // borrower is eventually going to have to pay back the loan, so it can't be
349 // frozen now. It is also going to receive funds, so it can't be deep
350 // frozen, but being frozen is a prerequisite for being deep frozen, so
351 // checking the one is sufficient.
352 if (auto const ret = checkFrozen(ctx.view, borrower, asset))
353 {
354 JLOG(ctx.j.warn()) << "Borrower account is frozen.";
355 return ret;
356 }
357 // brokerOwner is going to receive funds if there's an origination fee, so
358 // it can't be deep frozen
359 if (auto const ret = checkDeepFrozen(ctx.view, brokerOwner, asset))
360 {
361 JLOG(ctx.j.warn()) << "Broker owner account is frozen.";
362 return ret;
363 }
364
365 return tesSUCCESS;
366}
367
368TER
370{
371 auto const& tx = ctx_.tx;
372 auto& view = ctx_.view();
373
374 auto const brokerID = tx[sfLoanBrokerID];
375
376 auto const brokerSle = view.peek(keylet::loanBroker(brokerID));
377 if (!brokerSle)
378 return tefBAD_LEDGER; // LCOV_EXCL_LINE
379 auto const brokerOwner = brokerSle->at(sfOwner);
380 auto const brokerOwnerSle = view.peek(keylet::account(brokerOwner));
381 if (!brokerOwnerSle)
382 return tefBAD_LEDGER; // LCOV_EXCL_LINE
383
384 auto const vaultSle = view.peek(keylet ::vault(brokerSle->at(sfVaultID)));
385 if (!vaultSle)
386 return tefBAD_LEDGER; // LCOV_EXCL_LINE
387 auto const vaultPseudo = vaultSle->at(sfAccount);
388 Asset const vaultAsset = vaultSle->at(sfAsset);
389
390 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
391 auto const borrower = counterparty == brokerOwner ? accountID_ : counterparty;
392 auto const borrowerSle = view.peek(keylet::account(borrower));
393 if (!borrowerSle)
394 {
395 return tefBAD_LEDGER; // LCOV_EXCL_LINE
396 }
397
398 auto const brokerPseudo = brokerSle->at(sfAccount);
399 auto const brokerPseudoSle = view.peek(keylet::account(brokerPseudo));
400 if (!brokerPseudoSle)
401 {
402 return tefBAD_LEDGER; // LCOV_EXCL_LINE
403 }
404 auto const principalRequested = tx[sfPrincipalRequested];
405
406 auto vaultAvailableProxy = vaultSle->at(sfAssetsAvailable);
407 auto vaultTotalProxy = vaultSle->at(sfAssetsTotal);
408 auto const vaultScale = getAssetsTotalScale(vaultSle);
409 if (vaultAvailableProxy < principalRequested)
410 {
411 JLOG(j_.warn()) << "Insufficient assets available in the Vault to fund the loan.";
413 }
414
415 TenthBips32 const interestRate{tx[~sfInterestRate].value_or(0)};
416
417 auto const paymentInterval = tx[~sfPaymentInterval].value_or(kDefaultPaymentInterval);
418 auto const paymentTotal = tx[~sfPaymentTotal].value_or(kDefaultPaymentTotal);
419
420 auto const properties = computeLoanProperties(
421 view.rules(),
422 vaultAsset,
423 principalRequested,
424 interestRate,
425 paymentInterval,
426 paymentTotal,
427 TenthBips16{brokerSle->at(sfManagementFeeRate)},
428 vaultScale);
429
430 LoanState const state = constructLoanState(
431 properties.loanState.valueOutstanding,
432 principalRequested,
433 properties.loanState.managementFeeDue);
434
435 auto const vaultMaximum = *vaultSle->at(sfAssetsMaximum);
436 XRPL_ASSERT_PARTS(
437 vaultMaximum == 0 || vaultMaximum > *vaultTotalProxy,
438 "xrpl::LoanSet::doApply",
439 "Vault is below maximum limit");
440 if (vaultMaximum != 0 && state.interestDue > vaultMaximum - vaultTotalProxy)
441 {
442 JLOG(j_.warn()) << "Loan would exceed the maximum assets of the vault";
443 return tecLIMIT_EXCEEDED;
444 }
445 // Check that relevant values won't lose precision. This is mostly only
446 // relevant for IOU assets.
447 for (auto const& field : getValueFields())
448 {
449 if (auto const value = tx[field];
450 value && !isRounded(vaultAsset, *value, properties.loanScale))
451 {
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;
456 return tecPRECISION_LOSS;
457 }
458 }
459
460 if (auto const ret = checkLoanGuards(
461 vaultAsset,
462 principalRequested,
463 interestRate != beast::kZero,
464 paymentTotal,
465 properties,
466 j_))
467 return ret;
468
469 // Check that the other computed values are valid
470 if (properties.loanState.managementFeeDue < 0 || properties.loanState.valueOutstanding <= 0 ||
471 properties.periodicPayment <= 0)
472 {
473 // LCOV_EXCL_START
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;
478 return tecINTERNAL;
479 // LCOV_EXCL_STOP
480 }
481
482 auto const originationFee = tx[~sfLoanOriginationFee].value_or(Number{});
483
484 auto const loanAssetsToBorrower = principalRequested - originationFee;
485
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)
490 {
491 JLOG(j_.warn()) << "Loan would exceed the maximum debt limit of the LoanBroker.";
492 return tecLIMIT_EXCEEDED;
493 }
494 TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
495 {
496 auto const minCover = [&]() {
497 if (ctx_.view().rules().enabled(fixCleanup3_2_0))
498 {
499 return minimumBrokerCover(newDebtTotal, coverRateMinimum, vaultSle);
500 }
501
502 // Round the minimum required cover up to be conservative. This ensures
503 // CoverAvailable never drops below the theoretical minimum, protecting
504 // the broker's solvency.
506 return tenthBipsOfValue(newDebtTotal, coverRateMinimum);
507 }();
508 if (brokerSle->at(sfCoverAvailable) < minCover)
509 {
510 JLOG(j_.warn()) << "Insufficient first-loss capital to cover the loan.";
512 }
513 }
514
515 adjustOwnerCount(view, borrowerSle, 1, j_);
516 {
517 auto const ownerCount = borrowerSle->at(sfOwnerCount);
518 auto const balance =
519 accountID_ == borrower ? preFeeBalance_ : borrowerSle->at(sfBalance).value().xrp();
520 if (balance < view.fees().accountReserve(ownerCount))
522 }
523
524 // Account for the origination fee using two payments
525 //
526 // 1. Transfer loanAssetsAvailable (principalRequested - originationFee)
527 // from vault pseudo-account to the borrower.
528 // Create a holding for the borrower if one does not already exist.
529
530 XRPL_ASSERT_PARTS(
531 borrower == accountID_ || borrower == counterparty,
532 "xrpl::LoanSet::doApply",
533 "borrower signed transaction");
534 if (auto const ter = addEmptyHolding(
535 view, borrower, borrowerSle->at(sfBalance).value().xrp(), vaultAsset, j_);
536 ter && ter != tecDUPLICATE)
537 {
538 // ignore tecDUPLICATE. That means the holding already exists, and
539 // is fine here
540 return ter;
541 }
542
543 if (auto const ter = requireAuth(view, vaultAsset, borrower, AuthType::StrongAuth))
544 return ter;
545
546 // 2. Transfer originationFee, if any, from vault pseudo-account to
547 // LoanBroker owner.
548 if (originationFee != beast::kZero)
549 {
550 // Create the holding if it doesn't already exist (necessary for MPTs).
551 // The owner may have deleted their MPT / line at some point.
552 XRPL_ASSERT_PARTS(
553 brokerOwner == accountID_ || brokerOwner == counterparty,
554 "xrpl::LoanSet::doApply",
555 "broker owner signed transaction");
556
557 if (auto const ter = addEmptyHolding(
558 view, brokerOwner, brokerOwnerSle->at(sfBalance).value().xrp(), vaultAsset, j_);
559 ter && ter != tecDUPLICATE)
560 {
561 // ignore tecDUPLICATE. That means the holding already exists,
562 // and is fine here
563 return ter;
564 }
565 }
566
567 if (auto const ter = requireAuth(view, vaultAsset, brokerOwner, AuthType::StrongAuth))
568 return ter;
569
570 if (auto const ter = accountSendMulti(
571 view,
572 vaultPseudo,
573 vaultAsset,
574 {{borrower, loanAssetsToBorrower}, {brokerOwner, originationFee}},
575 j_,
577 return ter;
578
579 // Get shortcuts to the loan property values
580 auto const startDate = getStartDate(view);
581 auto loanSequenceProxy = brokerSle->at(sfLoanSequence);
582
583 // Create the loan
584 auto loan = std::make_shared<SLE>(keylet::loan(brokerID, *loanSequenceProxy));
585
586 // Prevent copy/paste errors
587 auto setLoanField = [&loan, &tx](auto const& field, std::uint32_t const defValue = 0) {
588 // at() is smart enough to unseat a default field set to the default
589 // value
590 loan->at(field) = tx[field].value_or(defValue);
591 };
592
593 // Set required and fixed tx fields
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;
600 // Set all other transaction fields directly from the transaction
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);
612 setLoanField(~sfGracePeriod, kDefaultGracePeriod);
613 // Set dynamic / computed fields to their initial values
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;
621 view.insert(loan);
622
623 // Update the balances in the vault
624 vaultAvailableProxy -= principalRequested;
625 vaultTotalProxy += state.interestDue;
626 XRPL_ASSERT_PARTS(
627 *vaultAvailableProxy <= *vaultTotalProxy,
628 "xrpl::LoanSet::doApply",
629 "assets available must not be greater than assets outstanding");
630 view.update(vaultSle);
631
632 // Update the balances in the loan broker
633 adjustImpreciseNumber(brokerSle->at(sfDebtTotal), newDebtDelta, vaultAsset, vaultScale);
634 // The broker's owner count is solely for the number of outstanding loans,
635 // and is distinct from the broker's pseudo-account's owner count
636 adjustOwnerCount(view, brokerSle, 1, j_);
637 loanSequenceProxy += 1;
638 // The sequence should be extremely unlikely to roll over, but fail if it
639 // does
640 if (loanSequenceProxy == 0)
642 view.update(brokerSle);
643
644 // Put the loan into the pseudo-account's directory
645 if (auto const ter = dirLink(view, brokerPseudo, loan, sfLoanBrokerNode))
646 return ter;
647 // Borrower is the owner of the loan
648 if (auto const ter = dirLink(view, borrower, loan, sfOwnerNode))
649 return ter;
650
651 associateAsset(*vaultSle, vaultAsset);
652 associateAsset(*brokerSle, vaultAsset);
653 associateAsset(*loan, vaultAsset);
654
655 return tesSUCCESS;
656}
657
658void
660{
661 // No transaction-specific invariants yet (future work).
662}
663
664bool
666{
667 // No transaction-specific invariants yet (future work).
668 return true;
669}
670
671//------------------------------------------------------------------------------
672
673} // namespace xrpl
A generic endpoint for log messages.
Definition Journal.h:38
Stream debug() const
Definition Journal.h:297
Stream warn() const
Definition Journal.h:309
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
Definition LoanSet.cpp:47
static TER preclaim(PreclaimContext const &ctx)
Definition LoanSet.cpp:217
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
Definition LoanSet.cpp:169
static constexpr std::uint32_t kDefaultPaymentTotal
static std::vector< OptionaledField< STNumber > > const & getValueFields()
Definition LoanSet.cpp:196
static NotTEC preflight(PreflightContext const &ctx)
Definition LoanSet.cpp:53
static NotTEC checkSign(PreclaimContext const &ctx)
Definition LoanSet.cpp:141
static constexpr std::uint32_t kDefaultPaymentInterval
static constexpr std::uint32_t kMinPaymentInterval
bool finalizeInvariants(STTx const &tx, TER result, XRPAmount fee, ReadView const &view, beast::Journal const &j) override
Check transaction-specific post-conditions after all entries have been visited.
Definition LoanSet.cpp:665
static bool checkExtraFeatures(PreflightContext const &ctx)
Definition LoanSet.cpp:41
TER doApply() override
Definition LoanSet.cpp:369
void visitInvariantEntry(bool isDelete, SLE::const_ref before, SLE::const_ref after) override
Inspect a single ledger entry modified by this transaction.
Definition LoanSet.cpp:659
static constexpr std::uint32_t kDefaultGracePeriod
Number is a floating point type that can represent a wide range of values.
Definition Number.h:306
A view into a ledger.
Definition ReadView.h:31
virtual SLE::const_pointer read(Keylet const &k) const =0
Return the state item associated with a key.
virtual LedgerHeader const & header() const =0
Returns information about the ledger.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
Definition Rules.cpp:171
std::shared_ptr< STLedgerEntry const > const & const_ref
T::value_type at(TypedField< T > const &f) const
Get the value of a field.
Definition STObject.h:1069
bool isFieldPresent(SField const &field) const
Definition STObject.cpp:454
STObject getFieldObject(SField const &field) const
Definition STObject.cpp:668
static NotTEC checkSign(PreclaimContext const &ctx)
static bool validNumericMinimum(std::optional< T > value, T min=T{})
Minimum will usually be zero.
Definition Transactor.h:578
beast::Journal const j_
Definition Transactor.h:118
ApplyView & view()
Definition Transactor.h:136
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
AccountID const accountID_
Definition Transactor.h:120
XRPAmount preFeeBalance_
Definition Transactor.h:121
static bool validDataLength(std::optional< Slice > const &slice, std::size_t maxLength)
ApplyContext & ctx_
Definition Transactor.h:116
static bool validNumericRange(std::optional< T > value, T max, T min=T{})
Definition Transactor.h:559
T is_same_v
T make_shared(T... args)
T max(T... args)
std::optional< NotTEC > preflightCheckSimulateKeys(ApplyFlags flags, STObject const &sigObject, beast::Journal j)
Checks the special signing key state needed for simulation.
NotTEC preflightCheckSigningKey(STObject const &sigObject, beast::Journal j)
Checks the validity of the transactor signing key.
Keylet loanBroker(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:557
Keylet loan(uint256 const &loanBrokerID, std::uint32_t loanSeq) noexcept
Definition Indexes.cpp:563
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Definition Indexes.cpp:551
Keylet account(AccountID const &id) noexcept
AccountID root.
Definition Indexes.cpp:186
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
@ terNO_ACCOUNT
Definition TER.h:209
constexpr FlagValue tfInnerBatchTxn
Definition TxFlags.h:43
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
Definition Protocol.h:111
int getAssetsTotalScale(SLE::const_ref vaultSle)
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, MPTIssue const &mptIssue, beast::Journal journal)
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
void adjustImpreciseNumber(NumberProxy value, Number const &adjustment, Asset const &asset, int vaultScale)
@ tefBAD_LEDGER
Definition TER.h:160
Number minimumBrokerCover(Number const &debtTotal, TenthBips32 coverRateMinimum, SLE::const_ref vaultSle)
TenthBips< std::uint32_t > TenthBips32
Definition Units.h:439
TenthBips< std::uint16_t > TenthBips16
Definition Units.h:438
std::string to_string(BaseUInt< Bits, Tag > const &a)
Definition base_uint.h:633
constexpr std::size_t kMaxDataPayloadLength
The maximum length of Data payload.
Definition Protocol.h:242
TER checkLoanGuards(Asset const &vaultAsset, Number const &principalRequested, bool expectInterest, std::uint32_t paymentTotal, LoanProperties const &properties, beast::Journal j)
TER canAddHolding(ReadView const &view, MPTIssue const &mptIssue)
TERSubset< CanCvtToNotTEC > NotTEC
Definition TER.h:594
TER dirLink(ApplyView &view, AccountID const &owner, SLE::pointer &object, SF_UINT64 const &node=sfOwnerNode)
Definition View.cpp:334
void adjustOwnerCount(ApplyView &view, SLE::ref sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
static std::uint32_t getStartDate(ReadView const &view)
Definition LoanSet.cpp:211
@ temINVALID
Definition TER.h:96
@ temBAD_SIGNER
Definition TER.h:101
TERSubset< CanCvtToTER > TER
Definition TER.h:634
TER requireAuth(ReadView const &view, MPTIssue const &mptIssue, AccountID const &account, AuthType authType=AuthType::Legacy, std::uint8_t depth=0)
Check if the account lacks required authorization for MPT.
@ tecNO_ENTRY
Definition TER.h:304
@ tecINTERNAL
Definition TER.h:308
@ tecINSUFFICIENT_FUNDS
Definition TER.h:323
@ tecPRECISION_LOSS
Definition TER.h:361
@ tecINSUFFICIENT_RESERVE
Definition TER.h:305
@ tecKILLED
Definition TER.h:314
@ tecMAX_SEQUENCE_REACHED
Definition TER.h:318
@ tecLIMIT_EXCEEDED
Definition TER.h:359
@ tecNO_PERMISSION
Definition TER.h:303
@ tecDUPLICATE
Definition TER.h:313
void associateAsset(STLedgerEntry &sle, Asset const &asset)
Associate an Asset with all sMD_NeedsAsset fields in a ledger entry.
TER accountSendMulti(ApplyView &view, AccountID const &senderID, Asset const &asset, MultiplePaymentDestinations const &receivers, beast::Journal j, WaiveTransferFee waiveFee=WaiveTransferFee::No)
Like accountSend, except one account is sending multiple payments (with the same asset!...
LoanProperties computeLoanProperties(Rules const &rules, Asset const &asset, Number const &principalOutstanding, TenthBips32 interestRate, std::uint32_t paymentInterval, std::uint32_t paymentsRemaining, TenthBips32 managementFeeRate, std::int32_t minimumScale)
LoanState constructLoanState(Number const &totalValueOutstanding, Number const &principalOutstanding, Number const &managementFeeOutstanding)
BaseUInt< 256 > uint256
Definition base_uint.h:562
@ tesSUCCESS
Definition TER.h:240
bool checkLendingProtocolDependencies(Rules const &rules, STTx const &tx)
bool isRounded(Asset const &asset, Number const &value, std::int32_t scale)
NetClock::time_point closeTime
This structure captures the parts of a loan state.
State information when determining if a tx is likely to claim a fee.
Definition Transactor.h:61
ReadView const & view
Definition Transactor.h:64
beast::Journal const j
Definition Transactor.h:69
std::optional< uint256 const > const parentBatchId
Definition Transactor.h:68
State information when preflighting a tx.
Definition Transactor.h:18
beast::Journal const j
Definition Transactor.h:25
std::optional< uint256 const > parentBatchId
Definition Transactor.h:24
T time_since_epoch(T... args)