Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions include/xrpl/ledger/helpers/LendingHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <xrpl/ledger/ApplyView.h>
#include <xrpl/ledger/ReadView.h>
#include <xrpl/protocol/AccountID.h>
#include <xrpl/protocol/Asset.h>
#include <xrpl/protocol/LedgerFormats.h> // IWYU pragma: keep
#include <xrpl/protocol/Protocol.h>
Expand All @@ -18,11 +19,13 @@
#include <xrpl/protocol/STTx.h>
#include <xrpl/protocol/TER.h>
#include <xrpl/protocol/Units.h>
#include <xrpl/protocol/XRPAmount.h>

#include <cstdint>
#include <expected>
#include <string_view>
#include <utility>
#include <vector>

namespace xrpl {

Expand Down Expand Up @@ -558,4 +561,116 @@ loanMakePayment(
LoanPaymentType const paymentType,
beast::Journal j);

//------------------------------------------------------------------------------
//
// Loan application helpers (shared by LoanSet and LoanAccept)
//
//------------------------------------------------------------------------------

/**
* Verify the loan asset can be held and that none of the accounts involved in
* disbursing the loan are frozen in a way that would block the fund flows.
*
* Checks, in order: that a holding for the asset can be created, that the vault
* pseudo-account (the sender) is not frozen, that the broker pseudo-account (a
* fallback fee recipient) is not deep frozen, that the borrower (a future payer
* and fund recipient) is not frozen, and that the broker owner (a fee
* recipient) is not deep frozen.
*/
[[nodiscard]] TER
checkLoanFreeze(
ReadView const& view,
Asset const& asset,
AccountID const& vaultPseudo,
AccountID const& brokerPseudo,
AccountID const& borrower,
AccountID const& brokerOwner,
beast::Journal j);

/**
* Validate a loan against the vault and broker limits.
*
* Checks the vault maximum, precision loss, loan guards, the computed loan
* properties, the broker debt maximum, and the broker's first-loss cover.
*/
[[nodiscard]] TER
checkLoanLimits(
ApplyView& view,
STTx const& tx,
SLE::ref brokerSle,
SLE::ref vaultSle,
Asset const& vaultAsset,
Number const& principalRequested,
TenthBips32 interestRate,
std::uint32_t paymentTotal,
LoanProperties const& properties,
LoanState const& state,
std::vector<OptionaledField<STNumber>> const& valueFields,
beast::Journal j);

/**
* Increment the borrower's owner count for the new loan object and verify the
* borrower still meets its reserve requirement.
*/
[[nodiscard]] TER
reserveLoanOwner(
ApplyView& view,
AccountID const& borrower,
SLE::ref borrowerSle,
AccountID const& signingAccount,
XRPAmount preFeeBalance,
beast::Journal j);

/**
* Transfer the loan principal to the borrower and the origination fee, if any,
* to the LoanBroker owner. Creates holdings as necessary.
*/
[[nodiscard]] TER
disburseLoan(
ApplyViewContext& viewContext,
AccountID const& borrower,
SLE::ref borrowerSle,
AccountID const& brokerOwner,
SLE::ref brokerOwnerSle,
AccountID const& vaultPseudo,
Asset const& vaultAsset,
Number const& loanAssetsToBorrower,
Number const& originationFee,
AccountID const& signingAccount,
AccountID const& counterparty,
beast::Journal j);

/**
* Update the LoanBroker ledger entry for a newly created loan.
*
* Adjusts the broker's outstanding debt total and owner count, advances the
* broker's loan sequence, and persists the entry.
*/
[[nodiscard]] TER
updateLoanBroker(
ApplyView& view,
SLE::ref brokerSle,
Number const& newDebtDelta,
Asset const& vaultAsset,
int vaultScale,
beast::Journal j);

/**
* Link the loan into the broker pseudo-account's directory.
*
* Done for both flows when the loan is created by LoanSet.
*/
[[nodiscard]] TER
linkLoanBroker(ApplyView& view, AccountID const& brokerPseudo, SLE::pointer& loan);

/**
* Make the borrower the owner of the loan by linking it into the borrower's
* directory.
*
* Done by LoanSet for the immediate flow, and deferred to LoanAccept for the
* two-step (pending) flow.
*/
[[nodiscard]] TER
linkLoanBorrower(ApplyView& view, AccountID const& borrower, SLE::pointer& loan);

} // namespace xrpl
3 changes: 2 additions & 1 deletion include/xrpl/protocol/LedgerFormats.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,8 @@ enum LedgerEntryType : std::uint16_t {
LEDGER_OBJECT(Loan, \
LSF_FLAG(lsfLoanDefault, 0x00010000) \
LSF_FLAG(lsfLoanImpaired, 0x00020000) \
LSF_FLAG(lsfLoanOverpayment, 0x00040000)) /* True, loan allows overpayments */ \
LSF_FLAG(lsfLoanOverpayment, 0x00040000) /* True, loan allows overpayments */ \
LSF_FLAG(lsfLoanPending, 0x00080000)) /* True, loan is pending acceptance by the borrower */ \
\
LEDGER_OBJECT(Sponsorship, \
LSF_FLAG(lsfSponsorshipRequireSignForFee, 0x00010000) \
Expand Down
3 changes: 2 additions & 1 deletion include/xrpl/protocol/detail/ledger_entries.macro
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,7 @@ LEDGER_ENTRY(ltVAULT, 0x0084, Vault, vault, ({
{sfShareMPTID, SoeRequired},
{sfWithdrawalPolicy, SoeRequired},
{sfScale, SoeDefault},
{sfAssetsReserved, SoeDefault},
// no SharesTotal ever (use MPTIssuance.sfOutstandingAmount)
// no PermissionedDomainID ever (use MPTIssuance.sfDomainID)
}))
Expand Down Expand Up @@ -542,7 +543,7 @@ LEDGER_ENTRY(ltLOAN_BROKER, 0x0088, LoanBroker, loan_broker, ({
LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
{sfPreviousTxnID, SoeRequired},
{sfPreviousTxnLgrSeq, SoeRequired},
{sfOwnerNode, SoeRequired},
{sfOwnerNode, SoeOptional},
{sfLoanBrokerNode, SoeRequired},
{sfLoanBrokerID, SoeRequired},
{sfLoanSequence, SoeRequired},
Expand Down
1 change: 1 addition & 0 deletions include/xrpl/protocol/detail/sfields.macro
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ TYPED_SFIELD(sfPrincipalRequested, NUMBER, 14)
TYPED_SFIELD(sfTotalValueOutstanding, NUMBER, 15, SField::kSmdNeedsAsset | SField::kSmdDefault)
TYPED_SFIELD(sfPeriodicPayment, NUMBER, 16)
TYPED_SFIELD(sfManagementFeeOutstanding, NUMBER, 17, SField::kSmdNeedsAsset | SField::kSmdDefault)
TYPED_SFIELD(sfAssetsReserved, NUMBER, 18, SField::kSmdNeedsAsset | SField::kSmdDefault)

// int32
TYPED_SFIELD(sfLoanScale, INT32, 1)
Expand Down
15 changes: 14 additions & 1 deletion include/xrpl/protocol/detail/transactions.macro
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,7 @@ TRANSACTION(ttLOAN_SET, 80, LoanSet,
MayAuthorizeMpt | MustModifyVault, ({
{sfLoanBrokerID, SoeRequired},
{sfData, SoeOptional},
{sfBorrower, SoeOptional},
{sfCounterparty, SoeOptional},
{sfCounterpartySignature, SoeOptional},
{sfLoanOriginationFee, SoeOptional},
Expand All @@ -1041,6 +1042,7 @@ TRANSACTION(ttLOAN_SET, 80, LoanSet,
{sfPaymentTotal, SoeOptional},
{sfPaymentInterval, SoeOptional},
{sfGracePeriod, SoeOptional},
{sfStartDate, SoeOptional},
}))

/** This transaction deletes an existing Loan */
Expand All @@ -1050,7 +1052,7 @@ TRANSACTION(ttLOAN_SET, 80, LoanSet,
TRANSACTION(ttLOAN_DELETE, 81, LoanDelete,
Delegation::NotDelegable,
featureLendingProtocol,
NoPriv, ({
MayModifyVault, ({
{sfLoanID, SoeRequired},
}))

Expand All @@ -1068,6 +1070,17 @@ TRANSACTION(ttLOAN_MANAGE, 82, LoanManage,
{sfLoanID, SoeRequired},
}))

/** The Borrower uses this transaction to accept a pending Loan. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/lending/LoanAccept.h>
#endif
TRANSACTION(ttLOAN_ACCEPT, 83, LoanAccept,
Delegation::NotDelegable,
featureLendingProtocolV1_1,
MayAuthorizeMpt | MustModifyVault, ({
{sfLoanID, SoeRequired},
}))

/** The Borrower uses this transaction to make a Payment on the Loan. */
#if TRANSACTION_INCLUDE
# include <xrpl/tx/transactors/lending/LoanPay.h>
Expand Down
27 changes: 19 additions & 8 deletions include/xrpl/protocol_autogen/ledger_entries/Loan.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,27 @@ class Loan : public LedgerEntryBase
}

/**
* @brief Get sfOwnerNode (SoeRequired)
* @return The field value.
* @brief Get sfOwnerNode (SoeOptional)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
SF_UINT64::type::value_type
protocol_autogen::Optional<SF_UINT64::type::value_type>
getOwnerNode() const
{
return this->sle_->at(sfOwnerNode);
if (hasOwnerNode())
return this->sle_->at(sfOwnerNode);
return std::nullopt;
}

/**
* @brief Check if sfOwnerNode is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasOwnerNode() const
{
return this->sle_->isFieldPresent(sfOwnerNode);
}

/**
Expand Down Expand Up @@ -578,7 +591,6 @@ class LoanBuilder : public LedgerEntryBuilderBase<LoanBuilder>
* @brief Construct a new LoanBuilder with required fields.
* @param previousTxnID The sfPreviousTxnID field value.
* @param previousTxnLgrSeq The sfPreviousTxnLgrSeq field value.
* @param ownerNode The sfOwnerNode field value.
* @param loanBrokerNode The sfLoanBrokerNode field value.
* @param loanBrokerID The sfLoanBrokerID field value.
* @param loanSequence The sfLoanSequence field value.
Expand All @@ -587,12 +599,11 @@ class LoanBuilder : public LedgerEntryBuilderBase<LoanBuilder>
* @param paymentInterval The sfPaymentInterval field value.
* @param periodicPayment The sfPeriodicPayment field value.
*/
LoanBuilder(std::decay_t<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq,std::decay_t<typename SF_UINT64::type::value_type> const& ownerNode,std::decay_t<typename SF_UINT64::type::value_type> const& loanBrokerNode,std::decay_t<typename SF_UINT256::type::value_type> const& loanBrokerID,std::decay_t<typename SF_UINT32::type::value_type> const& loanSequence,std::decay_t<typename SF_ACCOUNT::type::value_type> const& borrower,std::decay_t<typename SF_UINT32::type::value_type> const& startDate,std::decay_t<typename SF_UINT32::type::value_type> const& paymentInterval,std::decay_t<typename SF_NUMBER::type::value_type> const& periodicPayment)
LoanBuilder(std::decay_t<typename SF_UINT256::type::value_type> const& previousTxnID,std::decay_t<typename SF_UINT32::type::value_type> const& previousTxnLgrSeq,std::decay_t<typename SF_UINT64::type::value_type> const& loanBrokerNode,std::decay_t<typename SF_UINT256::type::value_type> const& loanBrokerID,std::decay_t<typename SF_UINT32::type::value_type> const& loanSequence,std::decay_t<typename SF_ACCOUNT::type::value_type> const& borrower,std::decay_t<typename SF_UINT32::type::value_type> const& startDate,std::decay_t<typename SF_UINT32::type::value_type> const& paymentInterval,std::decay_t<typename SF_NUMBER::type::value_type> const& periodicPayment)
: LedgerEntryBuilderBase<LoanBuilder>(ltLOAN)
{
setPreviousTxnID(previousTxnID);
setPreviousTxnLgrSeq(previousTxnLgrSeq);
setOwnerNode(ownerNode);
setLoanBrokerNode(loanBrokerNode);
setLoanBrokerID(loanBrokerID);
setLoanSequence(loanSequence);
Expand Down Expand Up @@ -643,7 +654,7 @@ class LoanBuilder : public LedgerEntryBuilderBase<LoanBuilder>
}

/**
* @brief Set sfOwnerNode (SoeRequired)
* @brief Set sfOwnerNode (SoeOptional)
* @return Reference to this builder for method chaining.
*/
LoanBuilder&
Expand Down
35 changes: 35 additions & 0 deletions include/xrpl/protocol_autogen/ledger_entries/Vault.h
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,30 @@ class Vault : public LedgerEntryBase
{
return this->sle_->isFieldPresent(sfScale);
}

/**
* @brief Get sfAssetsReserved (SoeDefault)
* @return The field value, or std::nullopt if not present.
*/
[[nodiscard]]
protocol_autogen::Optional<SF_NUMBER::type::value_type>
getAssetsReserved() const
{
if (hasAssetsReserved())
return this->sle_->at(sfAssetsReserved);
return std::nullopt;
}

/**
* @brief Check if sfAssetsReserved is present.
* @return True if the field is present, false otherwise.
*/
[[nodiscard]]
bool
hasAssetsReserved() const
{
return this->sle_->isFieldPresent(sfAssetsReserved);
}
};

/**
Expand Down Expand Up @@ -508,6 +532,17 @@ class VaultBuilder : public LedgerEntryBuilderBase<VaultBuilder>
return *this;
}

/**
* @brief Set sfAssetsReserved (SoeDefault)
* @return Reference to this builder for method chaining.
*/
VaultBuilder&
setAssetsReserved(std::decay_t<typename SF_NUMBER::type::value_type> const& value)
{
object_[sfAssetsReserved] = value;
return *this;
}

/**
* @brief Build and return the completed Vault wrapper.
* @param index The ledger entry index.
Expand Down
Loading
Loading