1#include <xrpld/app/tx/detail/LoanSet.h>
3#include <xrpld/app/misc/LendingHelpers.h>
5#include <xrpl/protocol/STTakesAsset.h>
6#include <xrpl/protocol/TxFlags.h>
25 using namespace Lending;
27 auto const& tx = ctx.
tx;
33 JLOG(ctx.
j.
debug()) <<
"BatchTrace[" << parentBatchId <<
"]: "
34 <<
"no Counterparty for inner LoanSet transaction.";
40 if (tx.isFieldPresent(sfCounterpartySignature))
41 return tx.getFieldObject(sfCounterpartySignature);
46 JLOG(ctx.
j.
warn()) <<
"LoanSet transaction must have a CounterpartySignature.";
58 for (
auto const& field : {&sfLoanServiceFee, &sfLatePaymentFee, &sfClosePaymentFee})
64 if (
auto const p = tx[sfPrincipalRequested]; p <= 0)
76 if (!
validNumericRange(tx[~sfOverpaymentInterestRate], maxOverpaymentInterestRate))
79 if (
auto const paymentTotal = tx[~sfPaymentTotal]; paymentTotal && *paymentTotal <= 0)
82 if (
auto const paymentInterval = tx[~sfPaymentInterval];
86 else if (
auto const gracePeriod = tx[~sfGracePeriod];
98 if (
auto const brokerID = ctx.
tx[~sfLoanBrokerID]; brokerID && *brokerID == beast::zero)
114 if (
auto const c = ctx.
tx.
at(~sfCounterparty))
118 return broker->at(sfOwner);
142 auto const counterSig = tx.
getFieldObject(sfCounterpartySignature);
150 return counterSig.isFieldPresent(sfSigners) ? counterSig.getFieldArray(sfSigners).size()
151 : (counterSig.isFieldPresent(sfTxnSignature) ? 1 : 0);
154 return normalCost + (signerCount * baseFee);
161 ~sfPrincipalRequested, ~sfLoanOriginationFee, ~sfLoanServiceFee, ~sfLatePaymentFee, ~sfClosePaymentFee
177 auto const& tx = ctx.
tx;
185 using timeType =
decltype(sfNextPaymentDueDate)::type::value_type;
188 static_assert(maxTime == 4'294'967'295);
198 if (grace > timeAvailable)
200 JLOG(ctx.
j.
warn()) <<
"Grace period exceeds protocol time limit.";
204 if (interval > timeAvailable)
206 JLOG(ctx.
j.
warn()) <<
"Payment interval exceeds protocol time limit.";
210 if (total > timeAvailable)
212 JLOG(ctx.
j.
warn()) <<
"Payment total exceeds protocol time limit.";
216 auto const timeLastPayment = timeAvailable - grace;
218 if (timeLastPayment / interval < total)
220 JLOG(ctx.
j.
warn()) <<
"Last payment due date, or grace period for "
221 "last payment exceeds protocol time limit.";
226 auto const account = tx[sfAccount];
227 auto const brokerID = tx[sfLoanBrokerID];
234 JLOG(ctx.
j.
warn()) <<
"LoanBroker does not exist.";
237 auto const brokerOwner = brokerSle->at(sfOwner);
238 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
239 if (account != brokerOwner && counterparty != brokerOwner)
241 JLOG(ctx.
j.
warn()) <<
"Neither Account nor Counterparty are the owner "
242 "of the LoanBroker.";
245 auto const brokerPseudo = brokerSle->at(sfAccount);
247 auto const borrower = counterparty == brokerOwner ? account : counterparty;
252 JLOG(ctx.
j.
warn()) <<
"Borrower does not exist.";
261 if (vault->at(sfAssetsMaximum) != 0 && vault->at(sfAssetsTotal) >= vault->at(sfAssetsMaximum))
263 JLOG(ctx.
j.
warn()) <<
"Vault at maximum assets limit. Can't add another loan.";
267 Asset const asset = vault->at(sfAsset);
269 auto const vaultPseudo = vault->at(sfAccount);
277 if (
auto const value = tx[field]; value &&
STAmount{asset, *value} != *value)
279 JLOG(ctx.
j.
warn()) << field.f->getName() <<
" (" << *value <<
") can not be represented as a(n) "
291 JLOG(ctx.
j.
warn()) <<
"Vault pseudo-account is frozen.";
300 JLOG(ctx.
j.
warn()) <<
"Broker pseudo-account is frozen.";
310 JLOG(ctx.
j.
warn()) <<
"Borrower account is frozen.";
317 JLOG(ctx.
j.
warn()) <<
"Broker owner account is frozen.";
330 auto const brokerID = tx[sfLoanBrokerID];
335 auto const brokerOwner = brokerSle->at(sfOwner);
340 auto const vaultSle =
view.
peek(keylet ::vault(brokerSle->at(sfVaultID)));
343 auto const vaultPseudo = vaultSle->at(sfAccount);
344 Asset const vaultAsset = vaultSle->at(sfAsset);
346 auto const counterparty = tx[~sfCounterparty].value_or(brokerOwner);
347 auto const borrower = counterparty == brokerOwner ?
account_ : counterparty;
354 auto const brokerPseudo = brokerSle->at(sfAccount);
356 if (!brokerPseudoSle)
360 auto const principalRequested = tx[sfPrincipalRequested];
362 auto vaultAvailableProxy = vaultSle->at(sfAssetsAvailable);
363 auto vaultTotalProxy = vaultSle->at(sfAssetsTotal);
365 if (vaultAvailableProxy < principalRequested)
367 JLOG(
j_.
warn()) <<
"Insufficient assets available in the Vault to fund the loan.";
371 TenthBips32 const interestRate{tx[~sfInterestRate].value_or(0)};
386 properties.loanState.valueOutstanding, principalRequested, properties.loanState.managementFeeDue);
388 auto const vaultMaximum = *vaultSle->at(sfAssetsMaximum);
390 vaultMaximum == 0 || vaultMaximum > *vaultTotalProxy,
"xrpl::LoanSet::doApply",
"Vault is below maximum limit");
391 if (vaultMaximum != 0 && state.interestDue > vaultMaximum - vaultTotalProxy)
393 JLOG(
j_.
warn()) <<
"Loan would exceed the maximum assets of the vault";
400 if (
auto const value = tx[field]; value && !
isRounded(vaultAsset, *value, properties.loanScale))
402 JLOG(
j_.
warn()) << field.f->getName() <<
" (" << *value <<
") has too much precision. Total loan value is "
403 << properties.loanState.valueOutstanding <<
" with a scale of " << properties.loanScale;
409 checkLoanGuards(vaultAsset, principalRequested, interestRate != beast::zero, paymentTotal, properties,
j_))
413 if (properties.loanState.managementFeeDue < 0 || properties.loanState.valueOutstanding <= 0 ||
414 properties.periodicPayment <= 0)
417 JLOG(
j_.
warn()) <<
"Computed loan properties are invalid. Does not compute."
418 <<
" Management fee: " << properties.loanState.managementFeeDue
419 <<
". Total Value: " << properties.loanState.valueOutstanding
420 <<
". PeriodicPayment: " << properties.periodicPayment;
425 auto const originationFee = tx[~sfLoanOriginationFee].value_or(
Number{});
427 auto const loanAssetsToBorrower = principalRequested - originationFee;
429 auto const newDebtDelta = principalRequested + state.interestDue;
430 auto const newDebtTotal = brokerSle->at(sfDebtTotal) + newDebtDelta;
431 if (
auto const debtMaximum = brokerSle->at(sfDebtMaximum); debtMaximum != 0 && debtMaximum < newDebtTotal)
433 JLOG(
j_.
warn()) <<
"Loan would exceed the maximum debt limit of the LoanBroker.";
436 TenthBips32 const coverRateMinimum{brokerSle->at(sfCoverRateMinimum)};
442 if (brokerSle->at(sfCoverAvailable) <
tenthBipsOfValue(newDebtTotal, coverRateMinimum))
444 JLOG(
j_.
warn()) <<
"Insufficient first-loss capital to cover the loan.";
451 auto const ownerCount = borrowerSle->at(sfOwnerCount);
464 borrower ==
account_ || borrower == counterparty,
"xrpl::LoanSet::doApply",
"borrower signed transaction");
465 if (
auto const ter =
addEmptyHolding(
view, borrower, borrowerSle->at(sfBalance).value().xrp(), vaultAsset,
j_);
476 if (originationFee != beast::zero)
481 brokerOwner ==
account_ || brokerOwner == counterparty,
482 "xrpl::LoanSet::doApply",
483 "broker owner signed transaction");
500 {{borrower, loanAssetsToBorrower}, {brokerOwner, originationFee}},
507 auto loanSequenceProxy = brokerSle->at(sfLoanSequence);
513 auto setLoanField = [&loan, &tx](
auto const& field,
std::uint32_t const defValue = 0) {
516 loan->at(field) = tx[field].value_or(defValue);
520 loan->at(sfLoanScale) = properties.loanScale;
521 loan->at(sfStartDate) = startDate;
522 loan->at(sfPaymentInterval) = paymentInterval;
523 loan->at(sfLoanSequence) = *loanSequenceProxy;
524 loan->at(sfLoanBrokerID) = brokerID;
525 loan->at(sfBorrower) = borrower;
529 setLoanField(~sfLoanOriginationFee);
530 setLoanField(~sfLoanServiceFee);
531 setLoanField(~sfLatePaymentFee);
532 setLoanField(~sfClosePaymentFee);
533 setLoanField(~sfOverpaymentFee);
534 setLoanField(~sfInterestRate);
535 setLoanField(~sfLateInterestRate);
536 setLoanField(~sfCloseInterestRate);
537 setLoanField(~sfOverpaymentInterestRate);
540 loan->at(sfPrincipalOutstanding) = principalRequested;
541 loan->at(sfPeriodicPayment) = properties.periodicPayment;
542 loan->at(sfTotalValueOutstanding) = properties.loanState.valueOutstanding;
543 loan->at(sfManagementFeeOutstanding) = properties.loanState.managementFeeDue;
544 loan->at(sfPreviousPaymentDueDate) = 0;
545 loan->at(sfNextPaymentDueDate) = startDate + paymentInterval;
546 loan->at(sfPaymentRemaining) = paymentTotal;
550 vaultAvailableProxy -= principalRequested;
551 vaultTotalProxy += state.interestDue;
553 *vaultAvailableProxy <= *vaultTotalProxy,
554 "xrpl::LoanSet::doApply",
555 "assets available must not be greater than assets outstanding");
563 loanSequenceProxy += 1;
566 if (loanSequenceProxy == 0)
571 if (
auto const ter =
dirLink(
view, brokerPseudo, loan, sfLoanBrokerNode))
574 if (
auto const ter =
dirLink(
view, borrower, loan, sfOwnerNode))
virtual void update(std::shared_ptr< SLE > const &sle)=0
Indicate changes to a peeked SLE.
virtual void insert(std::shared_ptr< SLE > const &sle)=0
Insert a new state SLE.
virtual std::shared_ptr< SLE > peek(Keylet const &k)=0
Prepare to modify the SLE associated with key.
static std::uint32_t getFlagsMask(PreflightContext const &ctx)
static TER preclaim(PreclaimContext const &ctx)
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
static std::uint32_t constexpr minPaymentInterval
static std::vector< OptionaledField< STNumber > > const & getValueFields()
static NotTEC preflight(PreflightContext const &ctx)
static std::uint32_t constexpr defaultPaymentInterval
static NotTEC checkSign(PreclaimContext const &ctx)
static std::uint32_t constexpr defaultPaymentTotal
static bool checkExtraFeatures(PreflightContext const &ctx)
static std::uint32_t constexpr defaultGracePeriod
Number is a floating point type that can represent a wide range of values.
virtual Fees const & fees() const =0
Returns the fees for the base ledger.
virtual LedgerHeader const & header() const =0
Returns information about the ledger.
virtual std::shared_ptr< SLE const > read(Keylet const &k) const =0
Return the state item associated with a key.
bool enabled(uint256 const &feature) const
Returns true if a feature is enabled.
T::value_type at(TypedField< T > const &f) const
Get the value of a field.
bool isFieldPresent(SField const &field) const
STObject getFieldObject(SField const &field) const
static NotTEC checkSign(PreclaimContext const &ctx)
static bool validNumericMinimum(std::optional< T > value, T min=T{})
Minimum will usually be zero.
static XRPAmount calculateBaseFee(ReadView const &view, STTx const &tx)
static bool validDataLength(std::optional< Slice > const &slice, std::size_t maxLength)
static bool validNumericRange(std::optional< T > value, T max, T min=T{})
constexpr value_type value() const
Returns the underlying value.
NotTEC preflightCheckSigningKey(STObject const &sigObject, beast::Journal j)
Checks the validity of the transactor signing key.
std::optional< NotTEC > preflightCheckSimulateKeys(ApplyFlags flags, STObject const &sigObject, beast::Journal j)
Checks the special signing key state needed for simulation.
Keylet loanbroker(AccountID const &owner, std::uint32_t seq) noexcept
Keylet loan(uint256 const &loanBrokerID, std::uint32_t loanSeq) noexcept
Keylet vault(AccountID const &owner, std::uint32_t seq) noexcept
Keylet account(AccountID const &id) noexcept
AccountID root.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
TER checkDeepFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
TER addEmptyHolding(ApplyView &view, AccountID const &accountID, XRPAmount priorBalance, Issue const &issue, beast::Journal journal)
Any transactors that call addEmptyHolding() in doApply must call canAddHolding() in preflight with th...
TER canAddHolding(ReadView const &view, Asset const &asset)
constexpr std::uint32_t tfInnerBatchTxn
std::string to_string(base_uint< Bits, Tag > const &a)
constexpr T tenthBipsOfValue(T value, TenthBips< TBips > bips)
int getAssetsTotalScale(SLE::const_ref vaultSle)
TER checkFrozen(ReadView const &view, AccountID const &account, Issue const &issue)
void adjustImpreciseNumber(NumberProxy value, Number const &adjustment, Asset const &asset, int vaultScale)
bool checkLendingProtocolDependencies(PreflightContext const &ctx)
constexpr std::uint32_t const tfLoanOverpayment
std::size_t constexpr maxDataPayloadLength
The maximum length of Data payload.
TER checkLoanGuards(Asset const &vaultAsset, Number const &principalRequested, bool expectInterest, std::uint32_t paymentTotal, LoanProperties const &properties, beast::Journal j)
TERSubset< CanCvtToTER > TER
void adjustOwnerCount(ApplyView &view, std::shared_ptr< SLE > const &sle, std::int32_t amount, beast::Journal j)
Adjust the owner count up or down.
TER requireAuth(ReadView const &view, Issue const &issue, AccountID const &account, AuthType authType=AuthType::Legacy)
Check if the account lacks required authorization.
constexpr std::uint32_t const tfLoanSetMask
static std::uint32_t getStartDate(ReadView const &view)
TER dirLink(ApplyView &view, AccountID const &owner, std::shared_ptr< SLE > &object, SF_UINT64 const &node=sfOwnerNode)
@ tecINSUFFICIENT_RESERVE
@ tecMAX_SEQUENCE_REACHED
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(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)
bool isRounded(Asset const &asset, Number const &value, std::int32_t scale)
XRPAmount accountReserve(std::size_t ownerCount) const
Returns the account reserve given the owner count, in drops.
This structure captures the parts of a loan state.
State information when determining if a tx is likely to claim a fee.
std::optional< uint256 const > const parentBatchId
State information when preflighting a tx.
std::optional< uint256 const > parentBatchId
T time_since_epoch(T... args)